Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH RFC v3 3/6] arm64: dts: qcom: sm8550-qrd: add SPMI ADC channels and thermal nodes
From: Dmitry Baryshkov @ 2026-06-15 23:59 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Neil Armstrong, devicetree, conor+dt, robh
In-Reply-To: <20260615171519.F2A851F00A3A@smtp.kernel.org>

On Mon, Jun 15, 2026 at 05:15:19PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Missing `qcom,hw-settle-time` for the `channel@749` 100kΩ pull-up thermistor (`pm8550b_wls_therm`), leading to inaccurate temperature readings.
> - [Low] The ADC channel nodes are not sorted by their unit address, violating standard device tree conventions.
> --
> 
> commit 4a45cdf2d6ed588ab63165c08752b51ef046be4f
> Author: Neil Armstrong <neil.armstrong@linaro.org>
> 
> arm64: dts: qcom: sm8550-qrd: add SPMI ADC channels and thermal nodes
>     
> Add the SPMI ADC channels on the PMK8550 SPMI5 ADC3 for the
> other PMICS on the system.
> 
> The thermal nodes are sorted by the sensor channel to be
> coherent with the system thermal nodes ordering.
> 
> 
> [Severity: Low]
> Similar to the above, this isn't a bug, but should channel@78e and
> channel@78f be placed after channel@747 and channel@749 to maintain
> proper unit address sorting?

Yes, sorting might need some improvements.

> 
> > +
> > +	channel@749 {
> > +		reg = <PM8550B_ADC5_GEN3_AMUX_THM6_GPIO10_100K_PU(7)>;
> > +		label = "pm8550b_wls_therm";
> > +		qcom,ratiometric;
> > +		qcom,pre-scaling = <1 1>;
> > +		qcom,adc-tm;
> > +	};
> 
> [Severity: Medium]
> Does this node need qcom,hw-settle-time = <200> like the other 100K
> pull-up thermistor channels in this patch?
> 
> Without it, the ADC measurement falls back to the default settle time,
> which might be insufficient for this high-impedance path and lead to
> inaccurate temperature readings for the pm8550b_wls_therm channel.

I'd also guess so. Neil, are you sure?

-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH RFC v3 2/6] arm64: dts: qcom: pmk8550: add VADC node
From: Dmitry Baryshkov @ 2026-06-15 23:58 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <20260615-topic-sm8x50-adc5-gen3-v3-2-216a2b5ccb85@linaro.org>

On Mon, Jun 15, 2026 at 07:00:08PM +0200, Neil Armstrong wrote:
> Add the VADC node and the initial pmk8550 adc channels.

Nit: PMK8550, ADC.

> 
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
> ---
>  arch/arm64/boot/dts/qcom/pmk8550.dtsi | 30 ++++++++++++++++++++++++++++++
>  1 file changed, 30 insertions(+)
> 

Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>


-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH RFC v3 1/6] arm64: dts: qcom: add PMIC5 Gen3 macros for channel numbers
From: Dmitry Baryshkov @ 2026-06-15 23:58 UTC (permalink / raw)
  To: Neil Armstrong
  Cc: Bjorn Andersson, Konrad Dybcio, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-arm-msm, devicetree, linux-kernel
In-Reply-To: <20260615-topic-sm8x50-adc5-gen3-v3-1-216a2b5ccb85@linaro.org>

On Mon, Jun 15, 2026 at 07:00:07PM +0200, Neil Armstrong wrote:
> Add the PMIC5 Gen3 macros to calculate the channel numbers which
> is a combination of SPMI bus number and a constant for the sensor
> type and configuration.
> 
> The macros definitions were taken out of [1] where it was initially
> in the dt-bindings include directory but since those are not hardware
> bindings but logical numbers, they can be moved to local includes
> instead to make the DT source more readable.
> 
> [1] https://lore.kernel.org/all/20250826083657.4005727-4-jishnu.prakash@oss.qualcomm.com/
> 
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
> ---
>  arch/arm64/boot/dts/qcom/qcom,adc5-gen3-channels.h | 88 ++++++++++++++++++++++
>  arch/arm64/boot/dts/qcom/qcom,pm8550-adc5-gen3.h   | 46 +++++++++++
>  arch/arm64/boot/dts/qcom/qcom,pm8550b-adc5-gen3.h  | 85 +++++++++++++++++++++
>  arch/arm64/boot/dts/qcom/qcom,pm8550vx-adc5-gen3.h | 22 ++++++
>  arch/arm64/boot/dts/qcom/qcom,pmk8550-adc5-gen3.h  | 52 +++++++++++++
>  5 files changed, 293 insertions(+)
> 

After 's/ACD5/ADC5/':


Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>



-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH net-next v2 1/2] dt-bindings: net: pse-pd: add bindings for Realtek/Broadcom PSE MCU
From: Daniel Golle @ 2026-06-15 23:50 UTC (permalink / raw)
  To: Rob Herring
  Cc: Jonas Jelonek, Oleksij Rempel, Kory Maincent, Andrew Lunn,
	David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Krzysztof Kozlowski, Conor Dooley, netdev, devicetree,
	linux-kernel, Bjørn Mork
In-Reply-To: <20260615212959.GA1679454-robh@kernel.org>

On Mon, Jun 15, 2026 at 04:29:59PM -0500, Rob Herring wrote:
> On Fri, Jun 12, 2026 at 01:29:41PM +0000, Jonas Jelonek wrote:
> > +properties:
> > +  compatible:
> > +    enum:
> > +      - realtek,pse-mcu-rtk
> 
> The "rtk" feels redundant.
> 
> > +      - realtek,pse-mcu-bcm
> 
> "brcm" is the standard vendor prefix, so use that instead of "bcm". 
> Though who defined the protocol in this case? Realtek or Broadcom? In 
> the latter case, I'd argue that "brcm" should be the vendor prefix.

The microcontroller firmware, and hence the protocol, is designed
by RealTek in both cases. However, they chose to design two incmpatible
protocol dialects based on the features of the PSE(s) connected to the
MCU.

^ permalink raw reply

* Re: [PATCH net v5 0/4] MAC-PHY interrupt changed to level triggered interrupt
From: patchwork-bot+netdevbpf @ 2026-06-15 23:40 UTC (permalink / raw)
  To: Selvamani Rajagopal
  Cc: parthiban.veerasooran, andrew+netdev, davem, edumazet, kuba,
	pabeni, robh, krzk+dt, conor+dt, pier.beruto, andrew, netdev,
	linux-kernel, conor.dooley, devicetree, Parthiban.Veerasooran,
	Selvamani.Rajagopal
In-Reply-To: <20260611-level-trigger-v5-0-4533a9e85ce2@onsemi.com>

Hello:

This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Thu, 11 Jun 2026 14:55:37 -0700 you wrote:
> According to OPEN Alliance 10BASE-T1x MAC-PHY Serial Interface
> specification, MAC-PHY interrupt is "active low, level triggered".
> The specification mentions about the conditions in which the IRQ
> is asserted and deasserted.
> 
> Bug is inadvertently introduced by treating the IRQ in the OA TC6
> framework driver and in dt-binding YAML file as edge triggered.
> 
> [...]

Here is the summary with links:
  - [net,v5,1/4] net: ethernet: oa_tc6: Interrupt is active low, level triggered.
    https://git.kernel.org/netdev/net/c/b542d13fab0f
  - [net,v5,2/4] net: ethernet: oa_tc6: mdiobus->parent initialized with NULL
    https://git.kernel.org/netdev/net/c/a221d3f7e3f3
  - [net,v5,3/4] net: ethernet: oa_tc6: Remove FCS size in RX frame
    https://git.kernel.org/netdev/net/c/a5a1d11dd372
  - [net,v5,4/4] dt-bindings: net: updated interrupt type to be active low, level triggered
    https://git.kernel.org/netdev/net/c/31e56112e654

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: (subset) [PATCH v11 0/9] Add support for NXP P3H2x4x I3C hub driver
From: Alexandre Belloni @ 2026-06-15 23:29 UTC (permalink / raw)
  To: linux-kernel, linux-i3c, krzk+dt, robh, conor+dt, devicetree,
	broonie, lee, Frank.Li, lgirdwood, Lakshay Piplani
  Cc: vikash.bansal, priyanka.jain, aman.kumarpandey
In-Reply-To: <20260612111816.3688240-1-lakshay.piplani@nxp.com>

On Fri, 12 Jun 2026 16:48:07 +0530, Lakshay Piplani wrote:
> This series adds a driver for the NXP P3H2x4x family of multiport I3C hub
> devices.
> 
> This is an MFD driver integrating I3C hub and on-die regulators.
> 
> The series introduces:
> - Core I3C master enhancements required for hub support
> - Generic I3C hub framework
> - MFD driver with regulator and I3C hub child drivers for the P3H2x4x I3C hub
> 
> [...]

Applied, thanks!

[1/9] i3c: master: rename i3c_master_reattach_i3c_dev() to *_locked
      https://git.kernel.org/i3c/c/79c41666b397
[2/9] i3c: master: Expose the APIs to support I3C hub
      https://git.kernel.org/i3c/c/8d8afa428318

Best regards,

-- 
Alexandre Belloni, co-owner and COO, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply

* Re: [PATCH 3/4] mfd: mt6397-core: add mt6323 AUXADC support
From: Roman Vivchar @ 2026-06-15 23:10 UTC (permalink / raw)
  To: Lee Jones
  Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-iio, devicetree, linux-kernel,
	linux-arm-kernel, linux-mediatek, Ben Grisdale
In-Reply-To: <20260611163748.GB1212816@google.com>

Hi Lee,

On Thursday, June 11th, 2026 at 7:37 PM, Lee Jones <lee@kernel.org> wrote:
> Interesting.  Has this been considered?
> 
> /* Sashiko Automation: Issues Found (1 Findings) */
> 
> On Tue, 02 Jun 2026, Roman Vivchar via B4 Relay wrote:
> 
> > From: Roman Vivchar <rva333@protonmail.com>
> >
> > The mt6323 PMIC includes an AUXADC. Register the AUXADC in the mt6323
> > devices array to allow the corresponding driver to probe using compatible
> > string.
> >
> > Tested-by: Ben Grisdale <bengris32@protonmail.ch> # Amazon Echo Dot (2nd Generation)
> > Signed-off-by: Roman Vivchar <rva333@protonmail.com>
> > ---
> >  drivers/mfd/mt6397-core.c | 3 +++
> >  1 file changed, 3 insertions(+)
> >
> > diff --git a/drivers/mfd/mt6397-core.c b/drivers/mfd/mt6397-core.c
> > index 3e58d0764c7e..013b0857fb54 100644
> > --- a/drivers/mfd/mt6397-core.c
> > +++ b/drivers/mfd/mt6397-core.c
> > @@ -125,6 +125,9 @@ static const struct resource mt6323_pwrc_resources[] = {
> >
> >  static const struct mfd_cell mt6323_devs[] = {
> >  	{
> > +		.name = "mt6323-auxadc",
> > +		.of_compatible = "mediatek,mt6323-auxadc",
> 
> [Severity: High]
> Does adding this compatible string expose an unhandled edge case in the
> auxadc driver?
> 
> If this device is instantiated directly from a Device Tree without an MFD
> parent (e.g., at the DT root), dev->parent could be the platform bus
> (which has a NULL parent) or NULL itself.
> 
> Looking at mt6323_auxadc_probe() in drivers/iio/adc/mt6323-auxadc.c:
> 
>     regmap = dev_get_regmap(dev->parent->parent, NULL);
> 
> Could blindly dereferencing dev->parent->parent here, or passing a NULL
> device to dev_get_regmap() (which calls devres_find()), result in a
> kernel oops if probed as a root node?

This is intended behavior, ADC always should be probed as a child of
the mt6323 MFD because it doesn't have own MMIO space. Otherwise the
devicetree is malformed. Recent mt6359 ADC driver has same thing with
dev->parent->parent regmap.

Best regards,
Roman

^ permalink raw reply

* Re: [PATCH v2 2/4] iio: adc: mt6323-auxadc: add mt6323 PMIC AUXADC driver
From: Roman Vivchar @ 2026-06-15 22:52 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Roman Vivchar via B4 Relay, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Matthias Brugger, AngeloGioacchino Del Regno, Lee Jones,
	linux-iio, devicetree, linux-kernel, linux-arm-kernel,
	linux-mediatek, Ben Grisdale
In-Reply-To: <20260614182214.65d052e4@jic23-huawei>

Hi Jonathan,

On Sunday, June 14th, 2026 at 8:22 PM, Jonathan Cameron <jic23@kernel.org> wrote:
> On Tue, 09 Jun 2026 16:31:59 +0300
> Roman Vivchar via B4 Relay <devnull+rva333.protonmail.com@kernel.org> wrote:

...
 
> > +
> > +#define MTK_PMIC_IIO_CHAN(_name, _chan, _addr)                  \
> > +{                                                               \
> > +	.type = IIO_VOLTAGE,                                    \
> > +	.indexed = 1,                                           \
> > +	.channel = _chan,                                       \
> > +	.address = _addr,                                       \
> > +	.datasheet_name = __stringify(_name),                   \
> > +	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |          \
> > +			      BIT(IIO_CHAN_INFO_SCALE),         \
> > +}
> > +
> > +static const struct iio_chan_spec mt6323_auxadc_channels[] = {
> > +	MTK_PMIC_IIO_CHAN(baton2,    MT6323_AUXADC_BATON2,    MT6323_AUXADC_ADC6),
> > +	MTK_PMIC_IIO_CHAN(ch6,       MT6323_AUXADC_CH6,       MT6323_AUXADC_ADC11),
> > +	MTK_PMIC_IIO_CHAN(bat_temp,  MT6323_AUXADC_BAT_TEMP,  MT6323_AUXADC_ADC5),
> 
> Reasonable query from Sashiko on why temperature channels are presented as voltages.
> If for some reason that is the right choice, then maybe a comment here.

mt6323 ADC always returns voltage. The thermal driver (which was in the
previous series and will be sent later) is required to map these to the
actual temperature. Ack.

...

> > +/*
> > + * The MediaTek MT6323 (as well as a lot of other PMICs) has the following hierarchy:
> > + * PMIC AUXADC <- PMIC MFD <- SoC PWRAP (wrapper for PWRAP FSM)
> > + *
> > + * Therefore, PWRAP regmap should be obtained using dev->parent->parent.
> > + */
> > +struct mt6323_auxadc {
> > +	struct regmap *regmap;
> > +	struct mutex lock;
> Locks should always have a comment on what data they are protecting.
> I think this one is about protecting the state of a device during a channel read
> by serializing those reads.

Nuno said kerneldoc looks unnecessary on v1 [1]. How the comment should
look?

...

> > +static int mt6323_auxadc_request(struct mt6323_auxadc *auxadc,
> > +				 unsigned long channel)
> > +{
> > +	struct regmap *map = auxadc->regmap;
> > +	int ret;
> > +
> > +	ret = regmap_set_bits(map, MT6323_AUXADC_CON11, AUXADC_CON11_VBUF_EN);
> > +	if (ret)
> > +		return ret;
> > +
> > +	return regmap_set_bits(map, MT6323_AUXADC_CON22, BIT(channel));
> 
> I'm not sure whether the sashiko question on this is valid or not. Make sure to take
> a look.
> 
> https://sashiko.dev/#/patchset/20260609-mt6323-adc-v2-0-aa93a22309f9%40protonmail.com
> You may have carefully selected the numbering so the channel numbering matches
> the bits in this register.  If so, it is probably worth a comment in the header
> to provide a cross reference.  No idea if Sashiko will notice that, but at least
> humans should!

The hardware is pretty weird, but dt-bindings have correct numbers.
I have double checked with the vendor driver and the logic is the same.

'If regmap_set_bits() fails to set MT6323_AUXADC_CON22, does this leave the
AUXADC voltage buffer (VBUF) permanently enabled?' - if this happens,
then there's something really wrong with PWRAP and disabling VBUF may
not be possible. Same about the 'mt6323_auxadc_release' comment.

...

> > +	case IIO_CHAN_INFO_RAW:
> 
> What Andy suggested here is the preferred path in IIO at least.
> Mainly because it reduced indent without hurting readability.
> Just be careful to define the scope with { }

Ack.

> 
> 
> > +		scoped_guard(mutex, &auxadc->lock) {
> > +			ret = mt6323_auxadc_prepare_channel(auxadc);
> > +			if (ret)
> > +				return ret;
> > +
> > +			ret = mt6323_auxadc_request(auxadc, chan->channel);
> > +			if (ret)
> > +				return ret;
> > +
> > +			/* Hardware limitation: the AUXADC needs a delay to become ready. */
> > +			fsleep(300);
> > +
> > +			ret = mt6323_auxadc_read(auxadc, chan, val);
> > +
> > +			if (mt6323_auxadc_release(auxadc, chan->channel))
> > +				dev_err(&indio_dev->dev,
> > +					"failed to release channel %d\n", chan->channel);
> > +
> > +			if (ret)
> > +				return ret;
> > +		}
> > +		return IIO_VAL_INT;
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> 
> 

After these changes, should I keep or drop Andy's Reviewed-by?

[1]: https://lore.kernel.org/linux-iio/2df4cad5e29fbcb4c5c5f59ea0bf322c7a301bdc.camel@gmail.com/

Best regards,
Roman

^ permalink raw reply

* Re: [PATCH v3 3/3] iio: adc: Fix incorrect reading when datarate changed in single mode
From: David Lechner @ 2026-06-15 22:38 UTC (permalink / raw)
  To: Jakub Szczudlo, linux-iio
  Cc: andy, antoniu.miclaus, conor+dt, devicetree, duje, jic23,
	jishnu.prakash, jorge.marques, krzk+dt, linusw, linux-kernel,
	marcelo.schmitt, mazziesaccount, mike.looijmans, nuno.sa, robh,
	sakari.ailus, wens, joshua.crofts1
In-Reply-To: <20260613190957.654798-4-jakubszczudlo40@gmail.com>

On 6/13/26 2:09 PM, Jakub Szczudlo wrote:
> When device is suspended and it is in single mode then changing
> datarate doesn't make it actual wait for new measurement, so to

s/actual/actually/

> be sure that read after change is correct functions that changes
> datarate and gain will wait for new data.

s/datarate/data rate/g


The commit message isn't very clear to me. Is the point that the
changes don't actually take effect in the ADC until a conversion
is done?

> 
> Signed-off-by: Jakub Szczudlo <jakubszczudlo40@gmail.com>
> ---
>  drivers/iio/adc/ti-ads1100.c | 55 ++++++++++++++++++++++++++++++++++--
>  1 file changed, 52 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c
> index 76de2466dc53..195394665cd1 100644
> --- a/drivers/iio/adc/ti-ads1100.c
> +++ b/drivers/iio/adc/ti-ads1100.c
> @@ -123,6 +123,36 @@ static int ads1100_get_voltage_microvolts(struct ads1100_data *data)
>  	return ads1100_get_voltage_milivolts(data) * MICRO / MILLI;
>  }
>  
> +static bool ads1100_new_data_ready(struct ads1100_data *data)
> +{
> +	int ret;
> +	u8 buffer[3];
> +
> +	ret = i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer));
> +	if (ret < 3) {
> +		dev_err(&data->client->dev, "I2C read fail: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return FIELD_GET(ADS1100_CFG_ST_BSY, buffer[2]);
> +}
> +
> +static int ads1100_poll_data_ready(struct ads1100_data *data)
> +{
> +	u8 buffer[3];
> +	bool data_ready;
> +	int datarate = data->ads_config->data_rate[FIELD_GET(ADS1100_DR_MASK, data->config)];
> +   // To be sure we wait 5 times more than datarate
> +	unsigned long wait_time = DIV_ROUND_CLOSEST(MICRO, 5 * datarate);
> +
> +	/* To be sure that polled value will have value after config change */
> +	i2c_master_recv(data->client, (char *)&buffer, sizeof(buffer));
> +
> +	return read_poll_timeout(ads1100_new_data_ready, data_ready,
> +				 !data_ready, wait_time,
> +				 ADS1100_MAX_DRDY_TIMEOUT, false, data);
> +}
> +
>  static int ads1100_data_bits(struct ads1100_data *data)
>  {
>  	return ads1100_data_rate_bits[FIELD_GET(ADS1100_DR_MASK, data->config)];
> @@ -165,6 +194,7 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2)
>  {
>  	int microvolts;
>  	int gain;
> +	int ret;
>  
>  	/* With Vdd between 2.7 and 5V, the scale is always below 1 */
>  	if (val)
> @@ -185,21 +215,40 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2)
>  	if (gain < BIT(0) || gain > BIT(3))
>  		return -EINVAL;
>  
> +	ret = pm_runtime_resume_and_get(&data->client->dev);
> +	if (ret < 0)
> +		return ret;
> +
>  	ads1100_set_config_bits(data, ADS1100_PGA_MASK, ffs(gain) - 1);
>  
> -	return 0;
> +	ret = ads1100_poll_data_ready(data);
> +
> +	pm_runtime_put_autosuspend(&data->client->dev);
> +
> +	return ret;
>  }
>  
>  static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate)
>  {
>  	unsigned int i;
>  	unsigned int size;
> +	int ret;
>  
>  	size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1;
>  	for (i = 0; i < size; i++) {
> -		if (data->ads_config->data_rate[i] == rate)
> -			return ads1100_set_config_bits(data, ADS1100_DR_MASK,
> +		if (data->ads_config->data_rate[i] != rate)
> +			continue;
> +
> +		ret = pm_runtime_resume_and_get(&data->client->dev);
> +		if (ret < 0)
> +			return ret;
> +
> +		ads1100_set_config_bits(data, ADS1100_DR_MASK,
>  					FIELD_PREP(ADS1100_DR_MASK, i));

Why no longer checking the return value of ads1100_set_config_bits()?

> +		ret = ads1100_poll_data_ready(data);
> +
> +		pm_runtime_put_autosuspend(&data->client->dev);
> +		return ret;
>  	}
>  
>  	return -EINVAL;


^ permalink raw reply

* Re: [PATCH v3 2/3] iio: adc: Add ti-ads1110 support to ti-ads1100 driver
From: David Lechner @ 2026-06-15 22:29 UTC (permalink / raw)
  To: Jakub Szczudlo, linux-iio
  Cc: andy, antoniu.miclaus, conor+dt, devicetree, duje, jic23,
	jishnu.prakash, jorge.marques, krzk+dt, linusw, linux-kernel,
	marcelo.schmitt, mazziesaccount, mike.looijmans, nuno.sa, robh,
	sakari.ailus, wens, joshua.crofts1
In-Reply-To: <20260613190957.654798-3-jakubszczudlo40@gmail.com>

On 6/13/26 2:09 PM, Jakub Szczudlo wrote:
> Add ADS1110 support that have faster datarate than ADS1100, it also uses
> internal voltage reference of 2.048V for measurement.
> 
> Signed-off-by: Jakub Szczudlo <jakubszczudlo40@gmail.com>
> ---
>  drivers/iio/adc/Kconfig      |  6 +--
>  drivers/iio/adc/ti-ads1100.c | 83 +++++++++++++++++++++++++++---------
>  2 files changed, 65 insertions(+), 24 deletions(-)
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index a9dedbb8eb46..54a0149a3838 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1747,11 +1747,11 @@ config TI_ADS1018
>           called ti-ads1018.
>  
>  config TI_ADS1100
> -	tristate "Texas Instruments ADS1100 and ADS1000 ADC"
> +	tristate "Texas Instruments ADS1100 and similar single channel I2C ADC"
>  	depends on I2C
>  	help
> -	  If you say yes here you get support for Texas Instruments ADS1100 and
> -	  ADS1000 ADC chips.
> +	  If you say yes here you get support TI ADS1100 and similar single
> +	  channel I2C Analog to Digital Converters
>  
>  	  This driver can also be built as a module. If so, the module will be
>  	  called ti-ads1100.
> diff --git a/drivers/iio/adc/ti-ads1100.c b/drivers/iio/adc/ti-ads1100.c
> index aa8946063c7d..76de2466dc53 100644
> --- a/drivers/iio/adc/ti-ads1100.c
> +++ b/drivers/iio/adc/ti-ads1100.c
> @@ -5,7 +5,7 @@
>   * Copyright (c) 2023, Topic Embedded Products
>   *
>   * Datasheet: https://www.ti.com/lit/gpn/ads1100
> - * IIO driver for ADS1100 and ADS1000 ADC 16-bit I2C
> + * IIO driver for ADS1100 and similar single channel ADC 16-bit I2C
>   */
>  
>  #include <linux/bitfield.h>
> @@ -15,6 +15,7 @@
>  #include <linux/module.h>
>  #include <linux/init.h>
>  #include <linux/i2c.h>
> +#include <linux/iopoll.h>
>  #include <linux/mutex.h>
>  #include <linux/property.h>
>  #include <linux/pm_runtime.h>
> @@ -39,17 +40,41 @@
>  #define	ADS1100_SINGLESHOT	ADS1100_CFG_SC
>  
>  #define ADS1100_SLEEP_DELAY_MS	2000
> +#define ADS1110_REFERENCE_VOLTAGE_MILIVOLTS 2048

maybe a better name?

ADS1110_INTERNAL_REF_mV

> +
> +/* Timeout based on the minimum sample rate of 8 SPS (7500000us) */

I would make the value in the comment an easier to read number, e.g.
7.5 s


> +#define ADS1100_MAX_DRDY_TIMEOUT	7500000

Always nice to include the units in the identifier name.

ADS1100_MAX_DRDY_TIMEOUT_us

>  
>  static const int ads1100_data_rate[] = { 128, 32, 16, 8 };
> +static const int ads1110_data_rate[] = { 240, 60, 30, 15 };
>  static const int ads1100_data_rate_bits[] = { 12, 14, 15, 16 };
>  
> +struct ads1100_config {
> +	const char *name;
> +	const int *data_rate;
> +	bool has_reference_voltage;

It would be helpful if this name was more specific. I think this means
has_internal_vref_only?

> +};
> +
> +static const struct ads1100_config ads1100_config = {
> +	.name = "ads1100",
> +	.data_rate = ads1100_data_rate,
> +	.has_reference_voltage = false,
> +};
> +
> +static const struct ads1100_config ads1110_config = {
> +	.name = "ads1110",
> +	.data_rate = ads1110_data_rate,
> +	.has_reference_voltage = true,
> +};
> +
>  struct ads1100_data {
>  	struct i2c_client *client;
>  	struct regulator *reg_vdd;
>  	struct mutex lock;
>  	int scale_avail[2 * 4]; /* 4 gain settings */
> +	struct ads1100_config *ads_config;
>  	u8 config;
> -	bool supports_data_rate; /* Only the ADS1100 can select the rate */
> +	bool supports_data_rate; /* Only the ADS1100/ADS1110 can select the rate */

I would just drop this comment since it is runtime detected.
Otherwise, it makes it sound like this belongs in struct ads1100_config.

>  };
>  
>  static const struct iio_chan_spec ads1100_channel = {
> @@ -85,6 +110,19 @@ static int ads1100_set_config_bits(struct ads1100_data *data, u8 mask, u8 value)
>  	return 0;
>  };
>  
> +static int ads1100_get_voltage_milivolts(struct ads1100_data *data)

I could call this ads1100_get_vref_milivolts() or
ads1100_get_reference_milivolts().

> +{
> +	if (data->ads_config->has_reference_voltage)
> +		return ADS1110_REFERENCE_VOLTAGE_MILIVOLTS;
> +	else

else is not necessary here

> +		return regulator_get_voltage(data->reg_vdd) / MILLI;

Why not keeping (MICRO / MILLI)?

> +}
> +
> +static int ads1100_get_voltage_microvolts(struct ads1100_data *data)
> +{
> +	return ads1100_get_voltage_milivolts(data) * MICRO / MILLI;

	return ads1100_get_voltage_milivolts(data) * (MICRO / MILLI);

> +}

Although this is only used once, so don't really need the helper function.

> +
>  static int ads1100_data_bits(struct ads1100_data *data)
>  {
>  	return ads1100_data_rate_bits[FIELD_GET(ADS1100_DR_MASK, data->config)];
> @@ -107,9 +145,9 @@ static int ads1100_get_adc_result(struct ads1100_data *data, int chan, int *val)
>  
>  	pm_runtime_put_autosuspend(&data->client->dev);
>  
> -	if (ret < 0) {
> +	if (ret < 2) {
>  		dev_err(&data->client->dev, "I2C read fail: %d\n", ret);
> -		return ret;
> +		return -EIO;

I think someone else mentioned this already, but ret < 0 should be propagated
rather than replaced with -EIO.

>  	}
>  
>  	/* Value is always 16-bit 2's complement */
> @@ -135,7 +173,7 @@ static int ads1100_set_scale(struct ads1100_data *data, int val, int val2)
>  	if (!val2)
>  		return -EINVAL;
>  
> -	microvolts = regulator_get_voltage(data->reg_vdd);
> +	microvolts = ads1100_get_voltage_microvolts(data);
>  	/*
>  	 * val2 is in 'micro' units, n = val2 / 1000000
>  	 * result must be millivolts, d = microvolts / 1000
> @@ -159,22 +197,17 @@ static int ads1100_set_data_rate(struct ads1100_data *data, int chan, int rate)
>  
>  	size = data->supports_data_rate ? ARRAY_SIZE(ads1100_data_rate) : 1;

This is a bit fragile now assuming that all configs have a list the same size
as ads1100_data_rate. It's a bit more verbose, but should probably also include
a size field in the config.

>  	for (i = 0; i < size; i++) {
> -		if (ads1100_data_rate[i] == rate)
> +		if (data->ads_config->data_rate[i] == rate)
>  			return ads1100_set_config_bits(data, ADS1100_DR_MASK,
> -						       FIELD_PREP(ADS1100_DR_MASK, i));
> +					FIELD_PREP(ADS1100_DR_MASK, i));
>  	}
>  
>  	return -EINVAL;
>  }
>  
> -static int ads1100_get_vdd_millivolts(struct ads1100_data *data)
> -{
> -	return regulator_get_voltage(data->reg_vdd) / (MICRO / MILLI);
> -}
> -
>  static void ads1100_calc_scale_avail(struct ads1100_data *data)
>  {
> -	int millivolts = ads1100_get_vdd_millivolts(data);
> +	int millivolts = ads1100_get_voltage_milivolts(data);
>  	unsigned int i;
>  
>  	for (i = 0; i < ARRAY_SIZE(data->scale_avail) / 2; i++) {
> @@ -196,7 +229,7 @@ static int ads1100_read_avail(struct iio_dev *indio_dev,
>  	switch (mask) {
>  	case IIO_CHAN_INFO_SAMP_FREQ:
>  		*type = IIO_VAL_INT;
> -		*vals = ads1100_data_rate;
> +		*vals = data->ads_config->data_rate;
>  		if (data->supports_data_rate)
>  			*length = ARRAY_SIZE(ads1100_data_rate);

Same here about the array size.

>  		else
> @@ -233,12 +266,11 @@ static int ads1100_read_raw(struct iio_dev *indio_dev,
>  		return IIO_VAL_INT;
>  	case IIO_CHAN_INFO_SCALE:
>  		/* full-scale is the supply voltage in millivolts */
> -		*val = ads1100_get_vdd_millivolts(data);
> +		*val = ads1100_get_voltage_milivolts(data);
>  		*val2 = 15 + FIELD_GET(ADS1100_PGA_MASK, data->config);
>  		return IIO_VAL_FRACTIONAL_LOG2;
>  	case IIO_CHAN_INFO_SAMP_FREQ:
> -		*val = ads1100_data_rate[FIELD_GET(ADS1100_DR_MASK,
> -						   data->config)];
> +		*val = data->ads_config->data_rate[FIELD_GET(ADS1100_DR_MASK, data->config)];

This line seems a bit long now.

>  		return IIO_VAL_INT;
>  	default:
>  		return -EINVAL;
> @@ -307,6 +339,7 @@ static int ads1100_probe(struct i2c_client *client)
>  	struct iio_dev *indio_dev;
>  	struct ads1100_data *data;
>  	struct device *dev = &client->dev;
> +	const struct ads1100_config *model;
>  	int ret;
>  
>  	indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
> @@ -338,6 +371,12 @@ static int ads1100_probe(struct i2c_client *client)
>  	if (ret)
>  		return ret;
>  
> +	model = device_get_match_data(dev);

Should be i2c_get_match_data().

> +	if (!model)
> +		return dev_err_probe(dev, -EINVAL,
> +				     "Can't get device data from firmware\n");
> +
> +	data->ads_config = (struct ads1100_config *)model;
>  	ret = ads1100_setup(data);
>  	if (ret)
>  		return dev_err_probe(dev, ret,
> @@ -400,16 +439,18 @@ static DEFINE_RUNTIME_DEV_PM_OPS(ads1100_pm_ops,
>  				 NULL);
>  
>  static const struct i2c_device_id ads1100_id[] = {
> -	{ "ads1100" },
> -	{ "ads1000" },
> +	{ .name = "ads1000", .driver_data = (kernel_ulong_t)&ads1100_config },
> +	{ .name = "ads1100", .driver_data = (kernel_ulong_t)&ads1100_config },
> +	{ .name = "ads1110", .driver_data = (kernel_ulong_t)&ads1110_config },
>  	{ }
>  };
>  
>  MODULE_DEVICE_TABLE(i2c, ads1100_id);
>  
>  static const struct of_device_id ads1100_of_match[] = {
> -	{.compatible = "ti,ads1100" },
> -	{.compatible = "ti,ads1000" },
> +	{ .compatible = "ti,ads1000", .data = &ads1100_config },
> +	{ .compatible = "ti,ads1100", .data = &ads1100_config },
> +	{ .compatible = "ti,ads1110", .data = &ads1110_config },
>  	{ }
>  };
>  


^ permalink raw reply

* Re: [PATCH v2] spi: dt-bindings: octeon: Convert to DT schema
From: Rob Herring @ 2026-06-15 22:19 UTC (permalink / raw)
  To: Ninad Naik
  Cc: broonie, krzk+dt, conor+dt, david.daney, linux-spi, devicetree,
	linux-kernel, me, linux-kernel-mentees, skhan
In-Reply-To: <20260613192233.2229933-1-ninadnaik07@gmail.com>

On Sun, Jun 14, 2026 at 12:52:33AM +0530, Ninad Naik wrote:
> Convert octeon-3010 to DT schema
> 
> Signed-off-by: Ninad Naik <ninadnaik07@gmail.com>
> ---
> Changes in v2:
> - Change the maintainer from Mark Brown to David Daney.
> - Use soc node wrapper instead of root node in the example to handle
> address-cells and size-cells requirements.
> - Remove interrupt controller provider from the example.
> 
>  .../bindings/spi/cavium,octeon-3010-spi.yaml  | 61 +++++++++++++++++++
>  .../devicetree/bindings/spi/spi-octeon.txt    | 33 ----------
>  2 files changed, 61 insertions(+), 33 deletions(-)
>  create mode 100644 Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml
>  delete mode 100644 Documentation/devicetree/bindings/spi/spi-octeon.txt
> 
> diff --git a/Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml b/Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml
> new file mode 100644
> index 000000000000..e35e661d0a58
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/spi/cavium,octeon-3010-spi.yaml
> @@ -0,0 +1,61 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/spi/cavium,octeon-3010-spi.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Cavium, Inc. OCTEON SoC SPI master controller
> +
> +description:
> +  The Cavium OCTEON SPI controller is an SPI master controller found in
> +  OCTEON SoCs.
> +
> +maintainers:
> +  - David Daney <david.daney@cavium.com>

David doesn't seem to be active and I'm pretty sure this email doesn't 
work any more. Just put me if there is no one else.

Rob

^ permalink raw reply

* Re: [PATCH v3 1/8] dt-bindings: remoteproc: qcom,pas: add thermal mitigation properties
From: Dmitry Baryshkov @ 2026-06-15 22:15 UTC (permalink / raw)
  To: Daniel Lezcano
  Cc: Gaurav Kohli, Krzysztof Kozlowski, Bjorn Andersson,
	Mathieu Poirier, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Amit Kucheria, Manivannan Sadhasivam, Konrad Dybcio, Kees Cook,
	Gustavo A. R. Silva, cros-qcom-dts-watchers, linux-arm-msm,
	linux-remoteproc, devicetree, linux-kernel, linux-pm,
	linux-hardening, Manaf Meethalavalappu Pallikunhi
In-Reply-To: <dcdfe9f4-aeec-4d85-92a1-a42592fe11c8@oss.qualcomm.com>

On Mon, Jun 15, 2026 at 05:33:15PM +0200, Daniel Lezcano wrote:
> 
> 
> Le 15/06/2026 à 17:14, Dmitry Baryshkov a écrit :
> > On Mon, Jun 15, 2026 at 04:33:38PM +0200, Daniel Lezcano wrote:
> > > 
> > > 
> > > Le 15/06/2026 à 16:11, Dmitry Baryshkov a écrit :
> > > > On Mon, Jun 15, 2026 at 02:30:49PM +0200, Daniel Lezcano wrote:
> > > > > Hi Gaurav,
> > > > > 
> > > > > Le 15/06/2026 à 14:12, Gaurav Kohli a écrit :
> > > > > > 
> > > > > > 
> > > > > > On 6/15/2026 4:04 PM, Daniel Lezcano wrote:
> > > > > > > On 6/13/26 13:05, Gaurav Kohli wrote:
> > > > > > > > 
> > > > > > > > 
> > > > > > > > On 6/13/2026 1:11 PM, Krzysztof Kozlowski wrote:
> > > > > > > > > On 12/06/2026 15:52, Gaurav Kohli wrote:
> > > > > > > > > > 
> > > > > > > > > > 
> > > > > > > > > > On 6/11/2026 5:53 PM, Krzysztof Kozlowski wrote:
> > > > > > > > > > > On 11/06/2026 13:12, Gaurav Kohli wrote:
> > > > > > > > > > > > > Why? And where is this generic property defined? You cannot just
> > > > > > > > > > > > > sprinkle generic properties in random bindings.
> > > > > > > > > > > > > 
> > > > > > > > > > > > 
> > > > > > > > > > > > Ack, will add why part.
> > > > > > > > > > > > These names are matched with the thermal
> > > > > > > > > > > > mitigation device identifiers
> > > > > > > > > > > > populated by remote firmware over QMI and define
> > > > > > > > > > > > mitigation devices are
> > > > > > > > > > > > exposed as cooling devices.
> > > > > > > > > > > 
> > > > > > > > > > > No, -names correspond to values passed via DT, not
> > > > > > > > > > > some remote firmware.
> > > > > > > > > > > The remote firmware should give you interface which
> > > > > > > > > > > is explicit and does
> > > > > > > > > > > not need such properties.
> > > > > > > > > > 
> > > > > > > > > > thanks Krzysztof for review, We need tmd-names because
> > > > > > > > > > of following reasons:
> > > > > > > > > > 
> > > > > > > > > > Following Daniel's series [1], the thermal framework supports
> > > > > > > > > > mapping multiple cooling devices per remoteproc/device via indexed
> > > > > > > > > > cooling-cells.
> > > > > > > > > > 
> > > > > > > > > > 1) The thermal framework's cooling-maps reference
> > > > > > > > > > cooling devices by index (for #cooling-cells = <3>).
> > > > > > > > > > Without tmd- names,
> > > > > > > > > > there's no way to know which index corresponds to which
> > > > > > > > > > TMD, as firmware
> > > > > > > > > > may return tmd-names in any order.
> > > > > > > > > > 
> > > > > > > > > > below are the changes post new thermal mapping changes:
> > > > > > > > > > DT: tmd-names = "cdsp_sw", "xyz";
> > > > > > > > > > Firmware: ["cdsp_sw", "xyz1", "xyz2",]
> > > > > > > > > > Driver registers: Only "cdsp_sw" (index 0) and "xyz" (index 1)
> > > > > > > > > 
> > > > > > > > > names property are not to instruct drivers to register or not to
> > > > > > > > > register something.
> > > > > > > > > 
> > > > > > > > > I don't understand the problem and explanation in the binding is
> > > > > > > > > basically non-existing.
> > > > > > > > > 
> > > > > > > > > Remember that all lists and indices ARE FIXED, so driver knows exactly
> > > > > > > > > which index means what.
> > > > > > > > > 
> > > > > > > > 
> > > > > > > > thanks for review, shall i use driver data, which is basically
> > > > > > > > pas data structure like below:
> > > > > > > > 
> > > > > > > > static const struct qcom_pas_data {
> > > > > > > >        .crash_reason_smem = 601,
> > > > > > > >        .firmware_name = "cdsp.mdt",
> > > > > > > >        .tmd_names = (const char *[]){"xyz", NULL},
> > > > > > > >        .num_tmds = 1,
> > > > > > > > 
> > > > > > > > Is something like above acceptable? and this will also help to
> > > > > > > > filter tmd names as well?
> > > > > > > 
> > > > > > > 
> > > > > > > How the thermal framework will bind the thermal zone with the TMD ?
> > > > > > > (node pointer, id) ?
> > > > > > > 
> > > > > > 
> > > > > > Hi Daniel,
> > > > > > 
> > > > > > thanks for review.
> > > > > > 
> > > > > > With id only, in this case instead of taking tmd names from device tree,
> > > > > > qmi_tmd will take tmd name from pas_data(driver) and register with the
> > > > > > cooling framework with id only. Please let us know if this looks fine.
> > > > > May be I'm missing something but:
> > > > > 
> > > > >    - The QMI TMD returns a list of names, not ids
> > > > >    - The QMI TMD may return the list in different order than assumed
> > > > >    - The cooling map index points to the name of the TMD in the DT
> > > > >    - This name is used to match the name in the aformentionned list
> > > > >    - The index in the list and the id in the DT can differ
> > > > 
> > > > Would it be better if we define standard indices for the standard names?
> > > > This way we decouple the actual firmware strings from the DT.
> > > 
> > > I don't think so, it seems to me too fragile and prone to error.
> > > 
> > > It is a remote proc, an external subsystem. The contract between the client
> > > and the server is the protocol. The protocol specifies the identifier as
> > > named strings, the TMD names, not numerical identifiers.
> > > 
> > > When asking for the list of TMDs, we get a list of strings. But as it is an
> > > external subsystems, may be tomorrow someone decide to send list ordered
> > > alphabetically, or per number of states, or whatever.
> > > 
> > > With hardcoded id the QMI TMD clients break
> > 
> > I was thinking about something like:
> > 
> > #define QCOM_TMD_DSP	0
> > #define QCOM_TMD_PA	1
> 
> Ah ok, it is correct if:
> 
> tmd-names = "dsp", "pa"
> 
> Or
> 
> #define QCOM_TMD_PA	0
> #define QCOM_TMD_DSP	1
> 
> tmd-names = "pa", "dsp"

I was more inclined about having the standard indices for the standard
mitigations.

BTW, I checked, which mitigations are being returned by the DSPs. Few
examples, just to provide some context.

SC8280XP, X13s:

TMD service: instance=0x01 (adsp) node=5 port=9
  1 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1

TMD service: instance=0x53 (slpi) node=9 port=9
  1 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1

TMD service: instance=0x43 (cdsp) node=10 port=8
  3 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1
    [ 1] cdsp_hw                  max_mitigation_level=1
    [ 2] cdsp_sw                  max_mitigation_level=7

SM6115, RB2:

TMD service: instance=0x00 (modem) node=0 port=20
  9 mitigation device(s):
    [ 0] pa                       max_mitigation_level=3
    [ 1] modem                    max_mitigation_level=3
    [ 2] cpuv_restriction_cold    max_mitigation_level=1
    [ 3] modem_current            max_mitigation_level=3
    [ 4] vbatt_low                max_mitigation_level=3
    [ 5] modem_skin               max_mitigation_level=3
    [ 6] modem_bw                 max_mitigation_level=5
    [ 7] wlan                     max_mitigation_level=1
    [ 8] wlan_bw                  max_mitigation_level=1

TMD service: instance=0x01 (adsp) node=5 port=8
  1 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1

TMD service: instance=0x43 (cdsp) node=10 port=8
  3 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1
    [ 1] cdsp_hw                  max_mitigation_level=1
    [ 2] cdsp_sw                  max_mitigation_level=5


SM8350, HDK:

TMD service: instance=0x00 (modem) node=0 port=22
  28 mitigation device(s):
    [ 0] pa                       max_mitigation_level=3
    [ 1] pa_fr1                   max_mitigation_level=3
    [ 2] modem                    max_mitigation_level=3
    [ 3] cpuv_restriction_cold    max_mitigation_level=1
    [ 4] modem_current            max_mitigation_level=3
    [ 5] vbatt_low                max_mitigation_level=3
    [ 6] charge_state             max_mitigation_level=3
    [ 7] modem_skin               max_mitigation_level=3
    [ 8] modem_bw                 max_mitigation_level=5
    [ 9] mmw0                     max_mitigation_level=3
    [10] mmw1                     max_mitigation_level=3
    [11] mmw2                     max_mitigation_level=3
    [12] mmw3                     max_mitigation_level=3
    [13] mmw_skin0                max_mitigation_level=3
    [14] mmw_skin1                max_mitigation_level=3
    [15] mmw_skin2                max_mitigation_level=3
    [16] mmw_skin3                max_mitigation_level=3
    [17] mmw_skin0_dsc            max_mitigation_level=15
    [18] mmw_skin1_dsc            max_mitigation_level=15
    [19] mmw_skin2_dsc            max_mitigation_level=15
    [20] mmw_skin3_dsc            max_mitigation_level=15
    [21] wlan                     max_mitigation_level=4
    [22] wlan_bw                  max_mitigation_level=1
    [23] modem_skin_lte_dsc       max_mitigation_level=255
    [24] modem_skin_nr_dsc        max_mitigation_level=255
    [25] pa_dsc                   max_mitigation_level=255
    [26] pa_fr1_dsc               max_mitigation_level=255
    [27] cpr_cold                 max_mitigation_level=3

TMD service: instance=0x01 (adsp) node=5 port=9
  1 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1

TMD service: instance=0x43 (cdsp) node=10 port=9
  3 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1
    [ 1] cdsp_hw                  max_mitigation_level=1
    [ 2] cdsp_sw                  max_mitigation_level=7

SM8150, HDK:

TMD service: instance=0x00 (modem) node=0 port=21
  6 mitigation device(s):
    [ 0] pa                       max_mitigation_level=3
    [ 1] modem                    max_mitigation_level=3
    [ 2] cpuv_restriction_cold    max_mitigation_level=1
    [ 3] modem_current            max_mitigation_level=3
    [ 4] vbatt_low                max_mitigation_level=3
    [ 5] modem_skin               max_mitigation_level=3

TMD service: instance=0x01 node=5 port=8
  1 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1

TMD service: instance=0x53 node=9 port=8
  1 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1

TMD service: instance=0x43 (cdsp) node=10 port=8
  1 mitigation device(s):
    [ 0] cpuv_restriction_cold    max_mitigation_level=1


-- 
With best wishes
Dmitry

^ permalink raw reply

* Re: [PATCH v4 06/31] dt-bindings: firmware: arm,scmi: Add support for telemetry protocol
From: Rob Herring (Arm) @ 2026-06-15 22:14 UTC (permalink / raw)
  To: Cristian Marussi
  Cc: linux-doc, Conor Dooley, puranjay, usama.arif, philip.radford,
	devicetree, souvik.chakravarty, linux-kernel, jic23, elif.topuz,
	lukasz.luba, sudeep.holla, leitao, vincent.guittot, james.quinlan,
	kernel-team, linux-arm-kernel, kas, arm-scmi, peng.fan,
	linux-fsdevel, michal.simek, brauner, etienne.carriere, d-gole,
	Krzysztof Kozlowski, f.fainelli
In-Reply-To: <20260612223802.1337232-7-cristian.marussi@arm.com>


On Fri, 12 Jun 2026 23:37:36 +0100, Cristian Marussi wrote:
> Add new SCMI v4.0 Telemetry protocol bindings definitions.
> 
> Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
> ---
> v3 --> v4
>  - changed protocol number to lowercase 1b
>  - fixed misplaced block for protocol 0x1b
> 
> Cc: Rob Herring <robh@kernel.org>
> Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
> Cc: Conor Dooley <conor+dt@kernel.org>
> Cc: devicetree@vger.kernel.org
> ---
>  Documentation/devicetree/bindings/firmware/arm,scmi.yaml | 8 ++++++++
>  1 file changed, 8 insertions(+)
> 

Acked-by: Rob Herring (Arm) <robh@kernel.org>


^ permalink raw reply

* Re: [PATCH 3/4] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
From: sashiko-bot @ 2026-06-15 22:14 UTC (permalink / raw)
  Cc: robh, conor+dt, devicetree
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-3-e6bdadf7cb2b@baylibre.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `write_raw` and `read_avail` IIO callbacks completely ignore the `long mask` argument.
- [Medium] Data race when reading `sys_mon_chan_short_gain_val` without locking or READ_ONCE().
--

commit 7a8163396f906f80891d134c7fb8faa25cc0a10a
Author: David Lechner (TI) <dlechner@baylibre.com>

iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
    
Implement support for the programmable gain amplifier on the internal
short SYS_MON channel. This channel is used for calibration, so it is
useful to be able to set the PGA to the same gain as the external
channels. The gain setting is implemented via the _scale attribute.

> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index 97097ae2a4872..202a449b4234f 100644
> --- a/drivers/iio/adc/ti-ads112c14.c
> +++ b/drivers/iio/adc/ti-ads112c14.c
[ ... ]
> @@ -366,6 +395,17 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
>  			return IIO_VAL_FRACTIONAL_LOG2;
>  		}
>  
> +		if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
> +			int *scale_avail = data->sys_mon_chan_short_scale_available[
> +				data->sys_mon_chan_short_gain_val
> +			];

[Severity: Medium]
Is there a potential data race when reading data->sys_mon_chan_short_gain_val
here?

This value is modified in ads112c14_write_raw() while holding the direct mode
lock, but it appears to be read here without locking or READ_ONCE(). Could a
concurrent sysfs write cause an issue while looking up the scale available
array?

> +
> +			*val = scale_avail[0];
> +			*val2 = scale_avail[1];
> +
> +			return IIO_VAL_INT_PLUS_NANO;
> +		}
> +
>  		*val = vref_uV / (MICRO / MILLI);
>  		/*
>  		 * Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to be
> @@ -387,6 +427,61 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
>  	}
>  }
>  
> +static int ads112c14_read_avail(struct iio_dev *indio_dev,
> +				const struct iio_chan_spec *chan, const int **vals,
> +				int *type, int *length, long mask)
> +{
> +	struct ads112c14_data *data = iio_priv(indio_dev);
> +
> +	switch (chan->channel) {
> +	case ADS112C14_SYS_MON_CHANNEL_SHORT:
> +		*vals = (const int *)data->sys_mon_chan_short_scale_available;
> +		*length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
> +		*type = IIO_VAL_INT_PLUS_NANO;
> +		return IIO_AVAIL_LIST;

[Severity: High]
Does this function need to check the mask parameter?

Currently, it returns the scale availability array unconditionally. If other
_available attributes are queried, this might incorrectly return scale data
for those non-scale queries.

> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ads112c14_write_raw(struct iio_dev *indio_dev,
> +			       struct iio_chan_spec const *chan, int val,
> +			       int val2, long mask)
> +{
> +	struct ads112c14_data *data = iio_priv(indio_dev);
> +
> +	switch (chan->channel) {
> +	case ADS112C14_SYS_MON_CHANNEL_SHORT: {
> +		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +		if (IIO_DEV_ACQUIRE_FAILED(claim))
> +			return -EBUSY;

[Severity: High]
Should this function also verify that mask == IIO_CHAN_INFO_SCALE?

Since there is no mask check, if the IIO core creates a writable _raw
attribute, any writes to it might be incorrectly interpreted as scale updates.

> +
> +		for (u32 i = 0; i < ARRAY_SIZE(data->sys_mon_chan_short_scale_available); i++) {
> +			if (val == data->sys_mon_chan_short_scale_available[i][0] &&
> +			    val2 == data->sys_mon_chan_short_scale_available[i][1]) {
> +				data->sys_mon_chan_short_gain_val = i;
> +				return 0;
> +			}
> +		}
> +		return -EINVAL;
> +	}
> +	default:
> +		return -EINVAL;
> +	}
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com?part=3

^ permalink raw reply

* Re: [PATCH 4/4] iio: adc: ti-ads112c14: add measurement channel support
From: sashiko-bot @ 2026-06-15 22:13 UTC (permalink / raw)
  Cc: robh, conor+dt, devicetree
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-4-e6bdadf7cb2b@baylibre.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:

New issues:
- [Critical] Heap buffer overflow in `ads112c14_populate_tables` due to incorrectly incremented `data->num_measurements`.
- [High] Incorrect error check for `devm_regulator_get_enable_read_voltage` causes valid probe successes to fail.
- [High] Uninitialized variable dereference in `ads112c14_read_raw()` for system monitor channels.
- [High] Integer overflow in `ads112c14_populate_scale_available()` breaks scaling factor calculation on 32-bit platforms.

Pre-existing issues:
- [High] Probe exits early and silently fails to register the IIO device if the reset command succeeds.
- [High] Short reads from I2C block reads leak uninitialized stack memory.
--

commit 6b05c003c593f091ad7d3f52a3dc20d0fab7fc26
Author: David Lechner (TI) <dlechner@baylibre.com>

iio: adc: ti-ads112c14: add measurement channel support

This patch adds support for parsing devicetree properties to dynamically
create measurement channels. It also adds the required algorithms and state
to do direct reads on these channels.

> diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
> index 202a449b4234f..d1297445025f7 100644
> --- a/drivers/iio/adc/ti-ads112c14.c
> +++ b/drivers/iio/adc/ti-ads112c14.c
[ ... ]
> @@ -383,7 +512,8 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
>  			return -EINVAL;
>  		}

[Severity: High]
This isn't a bug introduced by this patch, but can a short read from
ads112c14_single_conversion() leak uninitialized stack memory?

If i2c_smbus_read_i2c_block_data() returns fewer bytes than requested
(e.g., 1 instead of 3), the return value is a positive integer. The check
`if (ret < 0)` earlier in ads112c14_read_raw() passes, and extracting the
value will read uninitialized bytes from the stack-allocated buffer.

>  
> -		*val = sign_extend32(*val, fsr_bits);
> +		if (!measurement || measurement->bipolar)

[Severity: High]
Can `measurement` be uninitialized when dereferenced here?

If the requested channel is a system monitor channel (`chan->channel >= 100`),
the `measurement` pointer is left uninitialized at the start of
ads112c14_read_raw(). It is then evaluated in this condition, which could
lead to undefined behavior.

> +			*val = sign_extend32(*val, fsr_bits);
>  
>  		return IIO_VAL_INT;
>  	}
[ ... ]
> @@ -519,26 +687,187 @@ static const struct iio_info ads112c14_info = {
>  	.read_label = ads112c14_read_label,
>  };
>  
> +static int ads112c14_parse_channels(struct iio_dev *indio_dev,
> +				    bool *need_avdd_ref, bool *need_ext_ref)
> +{
[ ... ]
> +	data->num_measurements = device_get_named_child_node_count(dev, "channel");
> +
> +	data->measurements = devm_kcalloc(dev, data->num_measurements,
> +					  sizeof(*data->measurements), GFP_KERNEL);
> +	if (!data->measurements)
> +		return -ENOMEM;
> +
> +	data->num_measurements += ARRAY_SIZE(ads112c14_sys_mon_channels);

[Severity: Critical]
Does this incorrectly inflate the measurement count?

By incrementing `data->num_measurements` by the size of the system monitor
channels here, the boundary for the measurements array becomes too large.
Later in ads112c14_populate_tables(), looping over `data->num_measurements`
will access `data->measurements` out-of-bounds, potentially corrupting
adjacent heap memory.

> +
> +	channels = devm_kcalloc(dev, data->num_measurements +
> +				ARRAY_SIZE(ads112c14_sys_mon_channels),
> +				sizeof(*channels), GFP_KERNEL);
[ ... ]
> +static void ads112c14_populate_scale_available(int scale_avail[][2],
> +					       u32 vref_uV, u32 fsr_bits)
> +{
> +	for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
> +		int *entry = scale_avail[i];
> +		u32 gain_x10 = ads112c14_pga_gains_x10[i];
> +
> +		entry[0] = div_u64_rem(div64_u64((u64)(NANO * 10 /
> +						       (MICRO / MILLI)) * vref_uV,

[Severity: High]
Does this calculation overflow on 32-bit platforms?

`NANO` is defined as a 32-bit unsigned long, so multiplying it by 10 overflows
before the cast to `u64`. This truncates the value used to compute the scale
factor, leading to incorrect scaling values on 32-bit architectures.

> +						 (u64)gain_x10 * BIT(fsr_bits)),
> +				       NANO, &entry[1]);
> +	}
> +}
> +
>  static void ads112c14_populate_tables(struct ads112c14_data *data)
>  {
>  	u32 vref_uV, fsr_bits;
> -	int i;
> +
> +	for (u32 i = 0; i < data->num_measurements; i++) {
> +		struct ads112c14_measurement *measurement = &data->measurements[i];

[Severity: Critical]
Does this loop cause an out-of-bounds access?

Because `data->num_measurements` was artificially inflated in
ads112c14_parse_channels(), this loop goes past the allocated elements for
`data->measurements`. Writing to `measurement->scale_available` further down
in the loop will result in a heap buffer overflow.

[ ... ]
> @@ -560,13 +892,75 @@ static int ads112c14_probe(struct i2c_client *client)
>  	data->chip_info = info;
>  	data->client = client;
>  
> +	ret = ads112c14_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref);
> +	if (ret)
> +		return ret;
> +
>  	ret = devm_regulator_get_enable(&client->dev, "dvdd");
>  	if (ret)
>  		return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
>  
> -	ret = devm_regulator_get_enable(&client->dev, "avdd");
> -	if (ret)
> -		return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
> +	if (need_avdd_ref) {
> +		ret = devm_regulator_get_enable_read_voltage(&client->dev, "avdd");
> +		if (ret)
> +			return dev_err_probe(dev, ret, "failed to get avdd voltage\n");

[Severity: High]
Does this incorrectly fail on success?

`devm_regulator_get_enable_read_voltage()` returns the voltage in microvolts on
success, which is a positive integer. Checking `if (ret)` will evaluate to true
and abort the probe when the voltage is successfully read. This same pattern
appears below for the 'refp' and 'refn' regulators.

[ ... ]
>  	data->regmap = devm_regmap_init_i2c(client, &ads112c14_regmap_config);
>  	if (IS_ERR(data->regmap))

[Severity: High]
This isn't a bug introduced by this patch, but does the probe fail to
register the device if the reset command succeeds?

Just below this context, during probe, a software reset is sent via
regmap_write() and the return value is checked with:

	if (ret != -EREMOTEIO)
		return ret;

If the device acknowledges the write and regmap_write() successfully returns 0,
the check evaluates to true (`0 != -EREMOTEIO`), causing the driver to
prematurely return 0 without ever calling devm_iio_device_register(), leaving
the device entirely unusable.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com?part=4

^ permalink raw reply

* Re: [PATCH 2/4] iio: adc: add ti-ads112c14 driver
From: sashiko-bot @ 2026-06-15 22:11 UTC (permalink / raw)
  Cc: robh, conor+dt, devicetree
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-2-e6bdadf7cb2b@baylibre.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Incorrect error checking and status validation in `ads112c14_probe` causes the driver to always fail probing or exit early.
- [High] The polling timeout for data readiness is significantly shorter than the minimum conversion time, causing all reads to fail.
- [High] Short reads from the I2C block transfer cause uninitialized kernel stack memory to be exposed to userspace.
- [Medium] Setting the `CONV_MODE` bit places the device in continuous mode instead of the intended single-shot mode.
- [Low] The driver uses floating-point literals, which is prohibited in the kernel.
--

commit cac55fa736d39d53faf1e07aa41f7aa5d0909c7f
Author: David Lechner (TI) <dlechner@baylibre.com>
Subject: iio: adc: add ti-ads112c14 driver

Adds a basic driver for TI ADS112C14/ADS122C14 ADC chips supporting
power on/reset and system monitor channels.

diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
new file mode 100644
index 0000000000000..97097ae2a4872
--- /dev/null
+++ b/drivers/iio/adc/ti-ads112c14.c
[ ... ]
> +struct ads112c14_data {
> +	const struct ads112c14_chip_info *chip_info;
> +	struct i2c_client *client;
> +	struct regmap *regmap;
> +};
> +
> +/* Maps REF_VAL to microvolts. */
> +static const u32 ads112c14_internal_ref_uV[] = {
> +	[ADS112C14_REFERENCE_CFG_REF_VAL_1_25V] = 1.25 * MICRO,
> +	[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V] = 2.5 * MICRO,
> +};

[Severity: Low]
Does this use floating-point literals which are prohibited in the kernel?

The kernel environment does not support floating-point arithmetic, so
using 1.25 and 2.5 could cause compilation errors or link failures on
architectures without a hardware FPU. Could integer arithmetic be used
instead, such as 1250000?

[ ... ]
> +static int ads112c14_single_conversion(struct ads112c14_data *data,
> +				       const struct iio_chan_spec *chan,
> +				       u8 *buf)
> +{
> +	u32 reg_val;
> +	int ret;
[ ... ]
> +	ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
> +			   ADS112C14_CONVERSION_CTRL_START);
> +	if (ret)
> +		return ret;
> +
> +	ret = regmap_read_poll_timeout(data->regmap,
> +				       ADS112C14_REG_STATUS_MSB, reg_val,
> +				       FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
> +				       1 * USEC_PER_MSEC, 10 * USEC_PER_MSEC);

[Severity: High]
Is the 10ms timeout duration here long enough?

The ADC's data rate is left at its default value representing 20 SPS,
which means a single conversion would take approximately 50ms. Because
the maximum timeout here is 10ms, won't every read attempt timeout and
fail with -ETIMEDOUT before the DRDY bit can be set?

> +	if (ret)
> +		return ret;
> +
> +	return i2c_smbus_read_i2c_block_data(data->client, ADS112C14_CMD_RDATA,
> +					     BITS_TO_BYTES(data->chip_info->resolution_bits),
> +					     buf);
> +}
> +
> +static int ads112c14_read_raw(struct iio_dev *indio_dev,
> +			      struct iio_chan_spec const *chan, int *val,
> +			      int *val2, long mask)
> +{
> +	struct ads112c14_data *data = iio_priv(indio_dev);
> +	u32 vref_uV, fsr_bits;
> +
> +	/* Selecting V_REF source is not implemented yet. */
> +	vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V];
> +
> +	/* Currently, everything is using signed data. */
> +	fsr_bits = data->chip_info->resolution_bits - 1;
> +
> +	switch (mask) {
> +	case IIO_CHAN_INFO_RAW: {
> +		u8 buf[3];
> +		int ret;
> +
> +		if (!iio_device_claim_direct(indio_dev))
> +			return -EBUSY;
> +
> +		ret = ads112c14_single_conversion(data, chan, buf);
> +		iio_device_release_direct(indio_dev);
> +		if (ret < 0)
> +			return ret;
> +
> +		switch (data->chip_info->resolution_bits) {
> +		case 16:
> +			*val = get_unaligned_be16(buf);

[Severity: High]
Can this lead to uninitialized kernel stack memory being leaked to userspace?

In ads112c14_single_conversion(), i2c_smbus_read_i2c_block_data() returns
the number of bytes successfully read. If a short read occurs (e.g., it
returns 1 byte), ret will be a positive value.

Since the check here only looks for ret < 0, it treats a short read as a
success. This would cause the code to parse the partially uninitialized buf
array using get_unaligned_be16() or get_unaligned_be24(), exposing
uninitialized stack data.

Should this check verify that the expected number of bytes were actually
read?

[ ... ]
> +static int ads112c14_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	const struct ads112c14_chip_info *info;
> +	struct iio_dev *indio_dev;
> +	struct ads112c14_data *data;
> +	u32 reg_val;
> +	int ret;
[ ... ]
> +	/* Write magic reset value (0x16) to ensure known state.*/
> +	ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
> +			   FIELD_PREP(ADS112C14_CONVERSION_CTRL_RESET, 0x16));
> +	/*
> +	 * The reset may cause an -EREMOTEIO error because of failing to get the
> +	 * I2C ACK at the end of the message. The device still gets reset.
> +	 */
> +	if (ret != -EREMOTEIO)
> +		return ret;

[Severity: High]
Will this logic cause the probe to exit early with success instead of
continuing?

If the regmap_write() successfully resets the device, it returns 0. The
condition (0 != -EREMOTEIO) evaluates to true, causing the driver to
immediately return 0 and incorrectly skip the rest of the device registration
entirely.

> +
> +	fsleep(ADS112C14_DELAY_RESET_us);
> +
> +	ret = regmap_read(data->regmap, ADS112C14_REG_STATUS_MSB, &reg_val);
> +	if (ret)
> +		return ret;
> +
> +	if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
> +		return dev_err_probe(dev, -EIO, "reset failed\n");

[Severity: High]
Is this reset status check inverted?

The ADS112C14_STATUS_MSB_RESETN bit is set to 1 upon a successful power-on
reset, which is corroborated by the subsequent comment mentioning clearing
the reset bit. If the reset is successful, this check will actively fail
the probe with -EIO.

> +
> +	/*
> +	 * Clear reset bit to prepare for next probe. And clear AVDD fault since
> +	 * that happens on every reset.
> +	 */
> +	ret = regmap_write(data->regmap, ADS112C14_REG_STATUS_MSB,
> +			   ADS112C14_STATUS_MSB_RESETN |
> +			   ADS112C14_STATUS_MSB_AVDD_UVN);
> +	if (ret)
> +		return ret;
> +
> +	/* Place in single-shot conversion mode to make ready for raw read. */
> +	ret = regmap_set_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
> +			      ADS112C14_DEVICE_CFG_CONV_MODE);

[Severity: Medium]
Does this configure the device for continuous conversion mode rather than
single-shot?

The comment notes the intention to place the device in single-shot
conversion mode, but setting the ADS112C14_DEVICE_CFG_CONV_MODE bit to 1
enables continuous mode instead. The default register value of 0 is
single-shot mode.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com?part=2

^ permalink raw reply

* Re: [PATCH 1/4] dt-bindings: iio: adc: add ti,ads122c14
From: sashiko-bot @ 2026-06-15 22:10 UTC (permalink / raw)
  Cc: robh, devicetree, conor+dt
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-1-e6bdadf7cb2b@baylibre.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] Invalid schema constraint applied directly to an array property (`reg`) in the child nodes, and it contradicts the `patternProperties` regex.
- [Low] Mismatched and inflexible constraints between `interrupts` and `interrupt-names`.
--

commit 73a67f643701ff236b57cf5c513f2db2525ad034
Author: David Lechner (TI) <dlechner@baylibre.com>

dt-bindings: iio: adc: add ti,ads122c14

This commit adds new bindings for the Texas Instruments ADS122C14 and similar
ADC chips. The devices are primarily intended for temperature sensors, and the
bindings include channel-specific reference voltage and current output
properties.

> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
> new file mode 100644
> index 0000000000000..dc7f37cad7721
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
[ ... ]
> +  interrupts:
> +    minItems: 1
> +    items:
> +      - description: FAULT interrupt (GPIO2 pin)
> +      - description: DRDY interrupt (GPIO3 pin)
> +
> +  interrupt-names:
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      enum: [fault, drdy]

[Severity: Low]
Is it possible that this unordered enum for interrupt-names allows mismatched
arrays like [drdy, fault] to pass validation without enforcing order?

Also, the interrupts array strictly defines index 0 as FAULT and index 1 as
DRDY. If a board only wires up the DRDY interrupt, would it be forced to place
it at index 0 of the interrupts array, contradicting the description here?

Could this be improved by defining valid combinations using oneOf blocks with
exact items arrays for both interrupts and interrupt-names?

[ ... ]
> +patternProperties:
> +  ^channel@[0-7]$:
> +    $ref: adc.yaml
> +
> +    unevaluatedProperties: false
> +
> +    properties:
> +      reg:
> +        maximum: 16 # arbitrary limit, channel@ can be any combination of AIN0-AIN7

[Severity: Low]
Since the reg property is an array in device tree schemas, will applying the
JSON Schema maximum keyword directly to it be ignored by standard validators?
Should it define the maximum limit under an items list instead, similar to how
other array constraints are specified?

Additionally, does a limit of 16 contradict the regex ^channel@[0-7]$ on the
pattern properties node? The regex restricts the unit address to 0-7, which
prevents any reg value of 8 or higher from matching, rendering the maximum of
16 logically unreachable.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com?part=1

^ permalink raw reply

* Re: [PATCH v3 1/3] dt-bindings: iio: adc: ti,ads1100: add support for ADS1110
From: David Lechner @ 2026-06-15 22:07 UTC (permalink / raw)
  To: Jakub Szczudlo, linux-iio
  Cc: andy, antoniu.miclaus, conor+dt, devicetree, duje, jic23,
	jishnu.prakash, jorge.marques, krzk+dt, linusw, linux-kernel,
	marcelo.schmitt, mazziesaccount, mike.looijmans, nuno.sa, robh,
	sakari.ailus, wens, joshua.crofts1
In-Reply-To: <20260613190957.654798-2-jakubszczudlo40@gmail.com>

On 6/13/26 2:09 PM, Jakub Szczudlo wrote:
> Register layouts are the same as for ADS1100 but ADS1110 have different
> datarates and have internal voltage reference that is always 2.048V

If we do a v4, would be nice to mention here that fixing the order
of ti,ads1100 was intentional while we are touching this.

> 
> Signed-off-by: Jakub Szczudlo <jakubszczudlo40@gmail.com>
> ---
>  .../devicetree/bindings/iio/adc/ti,ads1100.yaml        | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml
> index 970ccab15e1e..28c5e2dd0ad6 100644
> --- a/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1100.yaml
> @@ -4,19 +4,23 @@
>  $id: http://devicetree.org/schemas/iio/adc/ti,ads1100.yaml#
>  $schema: http://devicetree.org/meta-schemas/core.yaml#
>  
> -title: TI ADS1100/ADS1000 single channel I2C analog to digital converter
> +title: TI ADS1100 and similar single channel I2C Analog to Digital Converters
>  
>  maintainers:
>    - Mike Looijmans <mike.looijmans@topic.nl>
>  
>  description: |
> -  Datasheet at: https://www.ti.com/lit/gpn/ads1100
> +  Datasheets:
> +    - https://www.ti.com/lit/gpn/ads1000
> +    - https://www.ti.com/lit/gpn/ads1100
> +    - https://www.ti.com/lit/gpn/ads1110
>  
>  properties:
>    compatible:
>      enum:
> -      - ti,ads1100
>        - ti,ads1000
> +      - ti,ads1100
> +      - ti,ads1110
>  
>    reg:
>      maxItems: 1


^ permalink raw reply

* Re: [PATCH v3 1/2] dt-bindings: Add GPIO-locked fixed clock
From: Brian Masney @ 2026-06-15 22:01 UTC (permalink / raw)
  To: V.Yurkov.EXT
  Cc: Michael Turquette, Stephen Boyd, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, linux-kernel, linux-clk, devicetree,
	Vyacheslav Yurkov
In-Reply-To: <20260603-feature-clock-guard-v3-1-01cca0aa04a5@bruker.com>

Hi Vyacheslav,

On Wed, Jun 03, 2026 at 11:16:42AM +0000, Vyacheslav Yurkov via B4 Relay wrote:
> From: Vyacheslav Yurkov <V.Yurkov.EXT@bruker.com>
> 
> Some hardware designs provide fixed-frequency clocks generated outside
> software control, such as by FPGA-resident PLLs. While the clock rate is
> fixed, a separate GPIO signal indicates whether the clock source is
> locked and producing a valid output.
> 
> Describe a GPIO-locked fixed clock provider that exposes a fixed-rate
> clock whose availability depends on one or more GPIO lock-status
> signals.
> 
> Signed-off-by: Vyacheslav Yurkov <uvv.mail@gmail.com>
> Signed-off-by: Vyacheslav Yurkov <V.Yurkov.EXT@bruker.com>
> ---
>  .../bindings/clock/gpio-locked-fixed-clock.yaml    | 70 ++++++++++++++++++++++
>  1 file changed, 70 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/clock/gpio-locked-fixed-clock.yaml b/Documentation/devicetree/bindings/clock/gpio-locked-fixed-clock.yaml
> new file mode 100644
> index 000000000000..9106b800b673
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/clock/gpio-locked-fixed-clock.yaml
> @@ -0,0 +1,70 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/clock/gpio-locked-fixed-clock.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: GPIO Locked Fixed Clock
> +
> +maintainers:
> +  - Vyacheslav Yurkov <V.Yurkov.EXT@bruker.com>
> +
> +description: |
> +  Provides a clock output whose availability depends on a set of
> +  prerequisite conditions. These conditions include the presence of
> +  one or more parent clocks and the asserted state of one or more
> +  GPIO lock indicators. An example of such clocks is FPGA clock that
> +  are outside CPU control, with the lock status exposed through GPIO
> +  signal.
> +
> +  The output clock is considered available only when all configured
> +  prerequisites are satisfied.

I'm stepping outside my usual review of just the clk drivers. Krzysztof
in v1 and v2 asked for more detailed hardware explanation. This feels to
me like this is a policy that says to not use these clocks until the
GPIO says they are ready. My gut feeling is that details like this
should live in a clk driver instead of a dt-binding.

Alternatively, if this is generic enough, then could
Documentation/devicetree/bindings/clock/gated-fixed-clock.yaml be
extended?

Brian


> +
> +properties:
> +  compatible:
> +    const: gpio-locked-fixed-clock
> +
> +  "#clock-cells":
> +    const: 0
> +
> +  clocks:
> +    description: Input clocks whose validity is monitored by this provider.
> +
> +  clock-output-names:
> +    description: Names of the clock provided by this controller.
> +    maxItems: 1
> +
> +  locked-gpios:
> +    description: |
> +      GPIOs to check the lock state.
> +    minItems: 1
> +    maxItems: 32
> +
> +required:
> +  - compatible
> +  - "#clock-cells"
> +
> +anyOf:
> +  - required:
> +      - clocks
> +  - required:
> +      - locked-gpios
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +
> +    clk_gpio_locked: gpio-locked-fixed-clock {
> +        compatible = "gpio-locked-fixed-clock";
> +        #clock-cells = <0>;
> +
> +        clocks = <&clk0 0>, <&pll 0>;
> +
> +        locked-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>,
> +                <&gpio0 5 GPIO_ACTIVE_HIGH>,
> +                <&gpio1 2 GPIO_ACTIVE_LOW>;
> +
> +        clock-output-names = "clkout0";
> +    };
> 
> -- 
> 2.34.1
> 
> 


^ permalink raw reply

* [PATCH 4/4] iio: adc: ti-ads112c14: add measurement channel support
From: David Lechner (TI) @ 2026-06-15 22:00 UTC (permalink / raw)
  To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel,
	David Lechner
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com>

Add support for parsing devicetree properties for measurement channels
and doing direct reads on these.

There are quite a lot of conditions that have to be met for each
measurement to be made, so quite a bit of state and algorithms are
required to handle it.

Channels are created dynamically since the number of possibilities is
unreasonably large.

Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
 drivers/iio/adc/ti-ads112c14.c | 486 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 439 insertions(+), 47 deletions(-)

diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 202a449b4234..d1297445025f 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -9,8 +9,10 @@
  */
 
 #include <linux/bitfield.h>
+#include <linux/cleanup.h>
 #include <linux/delay.h>
 #include <linux/dev_printk.h>
+#include <linux/device/devres.h>
 #include <linux/i2c.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/types.h>
@@ -26,6 +28,8 @@
 #include <linux/unaligned.h>
 #include <linux/units.h>
 
+#include <dt-bindings/iio/adc/ti,ads112c14.h>
+
 /* Datasheet t_d(RST) - time to wait after reset before next I2C use. */
 #define ADS112C14_DELAY_RESET_us 500
 
@@ -89,6 +93,9 @@
 #define   ADS112C14_REFERENCE_CFG_REF_VAL_1_25V		  0
 #define   ADS112C14_REFERENCE_CFG_REF_VAL_2_5V		  1
 #define ADS112C14_REFERENCE_CFG_REF_SEL			GENMASK(1, 0)
+#define   ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL	  0
+#define   ADS112C14_REFERENCE_CFG_REF_SEL_EXTERNAL	  1
+#define   ADS112C14_REFERENCE_CFG_REF_SEL_AVDD		  2
 
 #define ADS112C14_REG_DIGITAL_CFG			0x0A
 #define ADS112C14_DIGITAL_CFG_REG_MAP_CRC_EN		BIT(6)
@@ -197,10 +204,30 @@ struct ads112c14_chip_info {
 	u32 resolution_bits;
 };
 
+struct ads112c14_measurement {
+	const char *label;
+	u32 vref_source;
+	u32 idac_current_uA;
+	u8 iadc_count;
+	u8 idac1_mux;
+	u8 idac2_mux;
+	bool current_chop;
+	bool bipolar;
+	u8 gain_val;
+	int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
+};
+
 struct ads112c14_data {
 	const struct ads112c14_chip_info *chip_info;
 	struct i2c_client *client;
 	struct regmap *regmap;
+	u32 avdd_uV;
+	u32 ext_ref_uV;
+	bool refp_is_avdd;
+	bool refn_is_gnd;
+	u32 ext_ref_ohms;
+	struct ads112c14_measurement *measurements;
+	u32 num_measurements;
 	u8 sys_mon_chan_short_gain_val;
 	int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
 };
@@ -265,12 +292,106 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
 	},
 };
 
+static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
+						 const struct iio_chan_spec *chan)
+{
+	struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index];
+	bool iunit = measurement->idac_current_uA > 100;
+	u32 idac1_val = measurement->iadc_count > 0 ?
+		measurement->idac_current_uA / (iunit ? 100 : 10) : 0;
+	u32 idac2_val = measurement->iadc_count > 1 ? idac1_val : 0;
+	u32 refp_buf_en, refn_buf_en;
+	u32 ref_val, ref_sel;
+	int ret;
+
+	ret = regmap_write(data->regmap, ADS112C14_REG_MUX_CFG,
+			   FIELD_PREP(ADS112C14_MUX_CFG_AINP, chan->channel) |
+			   FIELD_PREP(ADS112C14_MUX_CFG_AINN, chan->channel2));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG,
+				 ADS112C14_DIGITAL_CFG_CODING,
+				 FIELD_PREP(ADS112C14_DIGITAL_CFG_CODING,
+					    measurement->bipolar ? 0 : 1));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
+				 ADS112C14_GAIN_CFG_SYS_MON | ADS112C14_GAIN_CFG_GAIN,
+				 FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, 0) |
+				 FIELD_PREP(ADS112C14_GAIN_CFG_GAIN,
+					    measurement->gain_val));
+	if (ret)
+		return ret;
+
+	ret = regmap_write(data->regmap, ADS112C14_REG_IDAC_MAG_CFG,
+			   FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I2MAG, idac2_val) |
+			   FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I1MAG, idac1_val));
+	if (ret)
+		return ret;
+
+	ret = regmap_write(data->regmap, ADS112C14_REG_IDAC_MUX_CFG,
+			   (iunit ? ADS112C14_IDAC_MUX_CFG_IUNIT : 0) |
+			   FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I2MUX,
+				      measurement->idac2_mux) |
+			   FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I1MUX,
+				      measurement->idac1_mux));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
+				 ADS112C14_DATA_RATE_CFG_GC_EN,
+				 FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
+					    measurement->current_chop));
+	if (ret)
+		return ret;
+
+	refp_buf_en = !data->refp_is_avdd &&
+		      measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL;
+	refn_buf_en = !data->refn_is_gnd &&
+		      measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL;
+
+	ref_val = measurement->vref_source == ADS112C14_VREF_SOURCE_INTERNAL_2_5V;
+
+	switch (measurement->vref_source) {
+	case ADS112C14_VREF_SOURCE_AVDD:
+		ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_AVDD;
+		break;
+	case ADS112C14_VREF_SOURCE_EXTERNAL:
+		ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_EXTERNAL;
+		break;
+	default:
+		ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL;
+		break;
+	}
+
+	return regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG,
+				  ADS112C14_REFERENCE_CFG_REFP_BUF_EN |
+				  ADS112C14_REFERENCE_CFG_REFN_BUF_EN |
+				  ADS112C14_REFERENCE_CFG_REF_VAL |
+				  ADS112C14_REFERENCE_CFG_REF_SEL,
+				  FIELD_PREP(ADS112C14_REFERENCE_CFG_REFP_BUF_EN,
+					     refp_buf_en) |
+				  FIELD_PREP(ADS112C14_REFERENCE_CFG_REFN_BUF_EN,
+					     refn_buf_en) |
+				  FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL,
+					     ref_val) |
+				  FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL,
+					     ref_sel));
+}
+
 static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
 					     const struct iio_chan_spec *chan)
 {
 	u32 gain_val;
 	int ret;
 
+	/*
+	 * NB: IDAC registers are left as-is in case they are generating current
+	 * needed for the external reference measurement.
+	 */
+
 	/*
 	 * All SYS_MON channels use GAIN of 1 to keep it simple. Other than
 	 * the internal short channel, where it is useful in practice.
@@ -320,13 +441,14 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
 	u32 reg_val;
 	int ret;
 
-	if (chan->channel >= 100) {
-		ret = ads112c14_prepare_sys_mon_channel(data, chan);
+	if (chan->channel < 100) {
+		ret = ads112c14_prepare_measurement_channel(data, chan);
 		if (ret)
 			return ret;
 	} else {
-		/* Not implemented yet. */
-		return -EINVAL;
+		ret = ads112c14_prepare_sys_mon_channel(data, chan);
+		if (ret)
+			return ret;
 	}
 
 	ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
@@ -351,13 +473,20 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
 			      int *val2, long mask)
 {
 	struct ads112c14_data *data = iio_priv(indio_dev);
+	struct ads112c14_measurement *measurement;
 	u32 vref_uV, fsr_bits;
+	int *scale_avail;
 
 	/* Selecting V_REF source is not implemented yet. */
 	vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V];
 
-	/* Currently, everything is using signed data. */
-	fsr_bits = data->chip_info->resolution_bits - 1;
+	if (chan->channel < 100) {
+		measurement = &data->measurements[chan->scan_index];
+		fsr_bits = data->chip_info->resolution_bits - measurement->bipolar;
+	} else {
+		/* All SYS_MON channels are using signed coding. */
+		fsr_bits = data->chip_info->resolution_bits - 1;
+	}
 
 	switch (mask) {
 	case IIO_CHAN_INFO_RAW: {
@@ -383,7 +512,8 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
 			return -EINVAL;
 		}
 
-		*val = sign_extend32(*val, fsr_bits);
+		if (!measurement || measurement->bipolar)
+			*val = sign_extend32(*val, fsr_bits);
 
 		return IIO_VAL_INT;
 	}
@@ -396,7 +526,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
 		}
 
 		if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
-			int *scale_avail = data->sys_mon_chan_short_scale_available[
+			scale_avail = data->sys_mon_chan_short_scale_available[
 				data->sys_mon_chan_short_gain_val
 			];
 
@@ -406,13 +536,23 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
 			return IIO_VAL_INT_PLUS_NANO;
 		}
 
-		*val = vref_uV / (MICRO / MILLI);
-		/*
-		 * Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to be
-		 * multiplied by 8 to account for internal attenuation of / 8.
-		 */
-		*val2 = fsr_bits - (chan->address >= 3 ? 3 : 0);
-		return IIO_VAL_FRACTIONAL_LOG2;
+		if (chan->channel > 100) {
+			*val = vref_uV / (MICRO / MILLI);
+			/*
+			 * Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to
+			 * be multiplied by 8 to account for internal attenuation
+			 * of / 8.
+			 */
+			*val2 = fsr_bits - (chan->address >= 3 ? 3 : 0);
+
+			return IIO_VAL_FRACTIONAL_LOG2;
+		}
+
+		scale_avail = measurement->scale_available[measurement->gain_val];
+		*val = scale_avail[0];
+		*val2 = scale_avail[1];
+
+		return IIO_VAL_INT_PLUS_NANO;
 	case IIO_CHAN_INFO_OFFSET:
 		/* Only the temperature channel has an offset. */
 		if (chan->type != IIO_TEMP)
@@ -433,6 +573,16 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
 {
 	struct ads112c14_data *data = iio_priv(indio_dev);
 
+	if (chan->channel < 100) {
+		struct ads112c14_measurement *measurement =
+			&data->measurements[chan->scan_index];
+
+		*vals = (const int *)measurement->scale_available;
+		*length = 2 * ARRAY_SIZE(measurement->scale_available);
+		*type = IIO_VAL_INT_PLUS_NANO;
+		return IIO_AVAIL_LIST;
+	}
+
 	switch (chan->channel) {
 	case ADS112C14_SYS_MON_CHANNEL_SHORT:
 		*vals = (const int *)data->sys_mon_chan_short_scale_available;
@@ -449,25 +599,31 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
 			       int val2, long mask)
 {
 	struct ads112c14_data *data = iio_priv(indio_dev);
+	const int (*scale_avail)[2];
+	u8 *gain_val;
+
+	if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+		scale_avail = data->sys_mon_chan_short_scale_available;
+		gain_val = &data->sys_mon_chan_short_gain_val;
+	} else if (chan->channel < 100) {
+		scale_avail = data->measurements[chan->scan_index].scale_available;
+		gain_val = &data->measurements[chan->scan_index].gain_val;
+	} else {
+		return -EINVAL;
+	}
 
-	switch (chan->channel) {
-	case ADS112C14_SYS_MON_CHANNEL_SHORT: {
-		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
-		if (IIO_DEV_ACQUIRE_FAILED(claim))
-			return -EBUSY;
+	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+	if (IIO_DEV_ACQUIRE_FAILED(claim))
+		return -EBUSY;
 
-		for (u32 i = 0; i < ARRAY_SIZE(data->sys_mon_chan_short_scale_available); i++) {
-			if (val == data->sys_mon_chan_short_scale_available[i][0] &&
-			    val2 == data->sys_mon_chan_short_scale_available[i][1]) {
-				data->sys_mon_chan_short_gain_val = i;
-				return 0;
-			}
+	for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+		if (val == scale_avail[i][0] && val2 == scale_avail[i][1]) {
+			*gain_val = i;
+			return 0;
 		}
-		return -EINVAL;
-	}
-	default:
-		return -EINVAL;
 	}
+
+	return -EINVAL;
 }
 
 static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
@@ -485,8 +641,20 @@ static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
 static int ads112c14_read_label(struct iio_dev *indio_dev,
 				struct iio_chan_spec const *chan, char *label)
 {
+	struct ads112c14_data *data = iio_priv(indio_dev);
 	const char *label_source;
 
+	/* measurement channels */
+	if (chan->channel < 100) {
+		struct ads112c14_measurement *measurement =
+			&data->measurements[chan->scan_index];
+
+		if (!measurement->label)
+			return -EINVAL;
+
+		return sysfs_emit(label, "%s\n", measurement->label);
+	}
+
 	/* System monitor channels. */
 	switch (chan->channel) {
 	case ADS112C14_SYS_MON_CHANNEL_TEMP:
@@ -519,26 +687,187 @@ static const struct iio_info ads112c14_info = {
 	.read_label = ads112c14_read_label,
 };
 
+static int ads112c14_parse_channels(struct iio_dev *indio_dev,
+				    bool *need_avdd_ref, bool *need_ext_ref)
+{
+	struct ads112c14_data *data = iio_priv(indio_dev);
+	struct device *dev = &data->client->dev;
+	struct iio_chan_spec *channels;
+	u32 pair[2];
+	int i = 0;
+	int ret;
+
+	*need_avdd_ref = false;
+	*need_ext_ref = false;
+
+	data->num_measurements = device_get_named_child_node_count(dev, "channel");
+
+	data->measurements = devm_kcalloc(dev, data->num_measurements,
+					  sizeof(*data->measurements), GFP_KERNEL);
+	if (!data->measurements)
+		return -ENOMEM;
+
+	data->num_measurements += ARRAY_SIZE(ads112c14_sys_mon_channels);
+
+	channels = devm_kcalloc(dev, data->num_measurements +
+				ARRAY_SIZE(ads112c14_sys_mon_channels),
+				sizeof(*channels), GFP_KERNEL);
+	if (!channels)
+		return -ENOMEM;
+
+	device_for_each_named_child_node_scoped(dev, child, "channel") {
+		struct ads112c14_measurement *measurement = &data->measurements[i];
+		struct iio_chan_spec *spec = &channels[i];
+
+		if (!fwnode_device_is_available(child))
+			continue;
+
+		spec->type = IIO_VOLTAGE;
+		spec->indexed = 1;
+		spec->scan_index = i;
+		measurement->gain_val = 1;
+
+		fwnode_property_read_string(child, "label", &measurement->label);
+
+		if (fwnode_property_present(child, "single-channel")) {
+			ret = fwnode_property_read_u32(child, "single-channel", &spec->channel);
+			if (ret)
+				return dev_err_probe(dev, ret,
+						     "failed to read single-channel property\n");
+
+			if (spec->channel >= 8)
+				return dev_err_probe(dev, -EINVAL,
+						     "single-channel value must be between 0 and 7\n");
+		} else if (fwnode_property_present(child, "diff-channels")) {
+			ret = fwnode_property_read_u32_array(child, "diff-channels", pair, ARRAY_SIZE(pair));
+			if (ret)
+				return dev_err_probe(dev, ret,
+						     "failed to read diff-channels property\n");
+
+			if (pair[0] >= 8 || pair[1] >= 8)
+				return dev_err_probe(dev, -EINVAL,
+						     "diff-channels values must be between 0 and 7\n");
+
+			spec->channel = pair[0];
+			spec->channel2 = pair[1];
+			spec->differential = 1;
+		} else {
+			return dev_err_probe(dev, -EINVAL,
+					     "channel node missing channel type property\n");
+		}
+
+		if (fwnode_property_present(child, "excitation-channels")) {
+			ret = fwnode_property_count_u32(child, "excitation-channels");
+			if (ret < 0)
+				return dev_err_probe(dev, ret,
+						     "failed to read excitation-channels property\n");
+
+			if (ret < 1 || ret > 2)
+				return dev_err_probe(dev, -EINVAL,
+						     "excitation-channels property must have 1 or 2 values\n");
+
+			measurement->iadc_count = ret;
+			pair[1] = 0;
+
+			ret = fwnode_property_read_u32_array(child, "excitation-channels", pair, measurement->iadc_count);
+			if (ret)
+				return dev_err_probe(dev, ret,
+						     "failed to read excitation-channels property\n");
+
+			if (pair[0] >= 8 || pair[1] >= 8)
+				return dev_err_probe(dev, -EINVAL,
+						     "excitation-channels values must be between 0 and 7\n");
+
+			measurement->idac1_mux = pair[0];
+			measurement->idac2_mux = measurement->iadc_count > 1 ? pair[1] : 0;
+
+			ret = fwnode_property_read_u32(child, "excitation-current-microamp",
+						       &measurement->idac_current_uA);
+			if (ret)
+				return dev_err_probe(dev, ret,
+						     "failed to read excitation-current-microamp property\n");
+
+			measurement->current_chop = fwnode_property_read_bool(child, "current-chopping");
+		}
+
+		measurement->bipolar = fwnode_property_read_bool(child, "bipolar");
+
+		fwnode_property_read_u32(child, "ti,vref-source", &measurement->vref_source);
+		if (measurement->vref_source > ADS112C14_VREF_SOURCE_AVDD)
+			return dev_err_probe(dev, -EINVAL,
+					     "invalid vref-source value\n");
+
+		if (measurement->vref_source == ADS112C14_VREF_SOURCE_AVDD)
+			*need_avdd_ref = true;
+		if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL)
+			*need_ext_ref = true;
+
+		spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE);
+		spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE);
+
+		i++;
+	}
+
+	memcpy(channels + i, ads112c14_sys_mon_channels, sizeof(ads112c14_sys_mon_channels));
+
+	indio_dev->channels = channels;
+	indio_dev->num_channels = i + ARRAY_SIZE(ads112c14_sys_mon_channels);
+
+	return 0;
+}
+
+static void ads112c14_populate_scale_available(int scale_avail[][2],
+					       u32 vref_uV, u32 fsr_bits)
+{
+	for (u32 i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+		int *entry = scale_avail[i];
+		u32 gain_x10 = ads112c14_pga_gains_x10[i];
+
+		entry[0] = div_u64_rem(div64_u64((u64)(NANO * 10 /
+						       (MICRO / MILLI)) * vref_uV,
+						 (u64)gain_x10 * BIT(fsr_bits)),
+				       NANO, &entry[1]);
+	}
+}
+
 static void ads112c14_populate_tables(struct ads112c14_data *data)
 {
 	u32 vref_uV, fsr_bits;
-	int i;
+
+	for (u32 i = 0; i < data->num_measurements; i++) {
+		struct ads112c14_measurement *measurement = &data->measurements[i];
+
+		switch (measurement->vref_source) {
+		case ADS112C14_VREF_SOURCE_EXTERNAL:
+			if (data->ext_ref_ohms)
+				vref_uV = measurement->idac_current_uA *
+					  measurement->iadc_count * data->ext_ref_ohms;
+			else
+				vref_uV = data->ext_ref_uV;
+			break;
+		case ADS112C14_VREF_SOURCE_AVDD:
+			vref_uV = data->avdd_uV;
+			break;
+		case ADS112C14_VREF_SOURCE_INTERNAL_1_25V:
+			vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_1_25V];
+			break;
+		default:
+			vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V];
+			break;
+		}
+
+		fsr_bits = data->chip_info->resolution_bits - measurement->bipolar;
+
+		ads112c14_populate_scale_available(measurement->scale_available,
+						   vref_uV, fsr_bits);
+	}
 
 	/* For now, assuming we are using 2.5V reference. */
 	vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V];
 	fsr_bits = data->chip_info->resolution_bits - 1;
 
-	for (i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
-		int *scale_avail = &data->sys_mon_chan_short_scale_available[i][0];
-		u32 gain_x10 = ads112c14_pga_gains_x10[i];
-
-		/* NB: slightly odd arrangement to avoid overflow. */
-		scale_avail[0] = div_u64_rem(div_u64((u64)NANO * 10 /
-						     (MICRO / MILLI) * vref_uV /
-						     gain_x10,
-						     BIT(fsr_bits)),
-					     NANO, &scale_avail[1]);
-	}
+	ads112c14_populate_scale_available(data->sys_mon_chan_short_scale_available,
+					   vref_uV, fsr_bits);
 }
 
 static int ads112c14_probe(struct i2c_client *client)
@@ -547,6 +876,9 @@ static int ads112c14_probe(struct i2c_client *client)
 	const struct ads112c14_chip_info *info;
 	struct iio_dev *indio_dev;
 	struct ads112c14_data *data;
+	bool need_avdd_ref, need_ext_ref;
+	u32 refp_uV = 0;
+	u32 refn_uV = 0;
 	u32 reg_val;
 	int ret;
 
@@ -560,13 +892,75 @@ static int ads112c14_probe(struct i2c_client *client)
 	data->chip_info = info;
 	data->client = client;
 
+	ret = ads112c14_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref);
+	if (ret)
+		return ret;
+
 	ret = devm_regulator_get_enable(&client->dev, "dvdd");
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
 
-	ret = devm_regulator_get_enable(&client->dev, "avdd");
-	if (ret)
-		return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+	if (need_avdd_ref) {
+		ret = devm_regulator_get_enable_read_voltage(&client->dev, "avdd");
+		if (ret)
+			return dev_err_probe(dev, ret, "failed to get avdd voltage\n");
+
+		data->avdd_uV = ret;
+	} else {
+		ret = devm_regulator_get_enable(&client->dev, "avdd");
+		if (ret)
+			return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+	}
+
+	if (device_property_present(dev, "refp-supply")) {
+		ret = devm_regulator_get_enable_read_voltage(&client->dev, "refp");
+		if (ret)
+			return dev_err_probe(dev, ret, "failed to get refp voltage\n");
+
+		refp_uV = ret;
+
+		struct fwnode_handle *refp_fwnode __free(fwnode_handle) =
+			fwnode_find_reference(dev->fwnode, "refp-supply", 0);
+		if (IS_ERR(refp_fwnode))
+			return dev_err_probe(dev, PTR_ERR(refp_fwnode),
+					     "failed to get refp fwnode\n");
+
+		struct fwnode_handle *avdd_fwnode __free(fwnode_handle) =
+			fwnode_find_reference(dev->fwnode, "avdd-supply", 0);
+		if (IS_ERR(avdd_fwnode))
+			return dev_err_probe(dev, PTR_ERR(avdd_fwnode),
+					     "failed to get avdd fwnode\n");
+
+		data->refp_is_avdd = refp_fwnode == avdd_fwnode;
+	}
+
+	if (device_property_present(dev, "refn-supply")) {
+		ret = devm_regulator_get_enable_read_voltage(&client->dev, "refn");
+		if (ret)
+			return dev_err_probe(dev, ret, "failed to get refn voltage\n");
+
+		refn_uV = ret;
+	} else {
+		data->refn_is_gnd = true;
+	}
+
+	data->ext_ref_uV = refp_uV - refn_uV;
+
+	if (device_property_present(dev, "refp-refn-resistor-ohms")) {
+		if (refp_uV != 0 || refn_uV != 0)
+			return dev_err_probe(dev, -EINVAL,
+					     "refp-refn-resistor-ohms property should not be present when refp-supply or refn-supply is present\n");
+
+		ret = device_property_read_u32(dev, "refp-refn-resistor-ohms",
+					       &data->ext_ref_ohms);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to read refp-refn-resistor-ohms property\n");
+	} else {
+		if (need_ext_ref && data->ext_ref_uV == 0)
+			return dev_err_probe(dev, -EINVAL,
+					     "external reference measurements require either refp-supply or refp-refn-resistor-ohms property\n");
+	}
 
 	data->regmap = devm_regmap_init_i2c(client, &ads112c14_regmap_config);
 	if (IS_ERR(data->regmap))
@@ -615,8 +1009,6 @@ static int ads112c14_probe(struct i2c_client *client)
 
 	indio_dev->name = info->name;
 	indio_dev->modes = INDIO_DIRECT_MODE;
-	indio_dev->channels = ads112c14_sys_mon_channels;
-	indio_dev->num_channels = ARRAY_SIZE(ads112c14_sys_mon_channels);
 	indio_dev->info = &ads112c14_info;
 
 	return devm_iio_device_register(&client->dev, indio_dev);

-- 
2.43.0


^ permalink raw reply related

* [PATCH 2/4] iio: adc: add ti-ads112c14 driver
From: David Lechner (TI) @ 2026-06-15 22:00 UTC (permalink / raw)
  To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel,
	David Lechner
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com>

Add a new driver for the TI ADS112C14/ADS122C14 ADC chips.

This first step is adding a very basic driver that only supports power
on/reset and reading the system monitor channels.

ADS112C14_SYS_MON_CHANNEL_SHORT is the last channel rather than being in
logical order by address to keep the voltage channels together and in
case we find we need to add variants of this channel with different
voltage reference later.

Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---

A few other notes for review that didn't seem worth putting in the
commit message:
* I intentionally did not use bulk regmap because later we may need to
  get the voltage of the avdd supply.
* I left some comments in the code where the code might look funny (e.g.
  to reduce future diff) or does not exactly match the datasheet, in
  which case later changes will address that.
---
 MAINTAINERS                    |   1 +
 drivers/iio/adc/Kconfig        |  12 +
 drivers/iio/adc/Makefile       |   1 +
 drivers/iio/adc/ti-ads112c14.c | 536 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 550 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 9ce7c61b0c14..084146a0f01e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26916,6 +26916,7 @@ M:	David Lechner <dlechner@baylibre.com>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
+F:	drivers/iio/adc/ti-ads112c14.c
 F:	include/dt-bindings/iio/adc/ti,ads112c14.h
 
 TI ADS1018 ADC DRIVER
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 6fb0766ca27a..bfcaecc85f24 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1787,6 +1787,18 @@ config TI_ADS1119
          This driver can also be built as a module. If so, the module will be
          called ti-ads1119.
 
+config TI_ADS112C14
+	tristate "Texas Instruments ADS112C14/ADS122C14"
+	depends on I2C
+	select REGMAP
+	select REGMAP_I2C
+	help
+	  If you say yes here you get support for Texas Instruments ADS112C14,
+	  ADS122C14 ADC chips.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called ti-ads112c14.
+
 config TI_ADS124S08
 	tristate "Texas Instruments ADS124S08"
 	depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 707dd708912f..72f4c6b33ca8 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -153,6 +153,7 @@ obj-$(CONFIG_TI_ADS1015) += ti-ads1015.o
 obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o
 obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
 obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
+obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
 obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
 obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
 obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
new file mode 100644
index 000000000000..97097ae2a487
--- /dev/null
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for Texas Instruments ADS112C14 and similar ADCs.
+ *
+ * Copyright (C) 2026 Texas Instruments Incorporated - https://www.ti.com/
+ * Copyright (C) 2026 Baylibre Inc.
+ *
+ * Datasheet: https://www.ti.com/lit/ds/symlink/ads122c14.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/i2c.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
+#include <linux/time64.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/units.h>
+
+/* Datasheet t_d(RST) - time to wait after reset before next I2C use. */
+#define ADS112C14_DELAY_RESET_us 500
+
+#define ADS112C14_CMD_RDATA	0x00
+#define ADS112C14_CMD_RREG	0x40
+#define ADS112C14_CMD_WREG	0x80
+
+#define ADS112C14_REG_DEVICE_ID				0x00
+#define ADS112C14_DEVICE_ID_BITS			GENMASK(3, 0)
+
+#define ADS112C14_REG_REVISION_ID			0x01
+
+#define ADS112C14_REG_STATUS_MSB			0x02
+#define ADS112C14_STATUS_MSB_RESETN			BIT(7)
+#define ADS112C14_STATUS_MSB_AVDD_UVN			BIT(6)
+#define ADS112C14_STATUS_MSB_REF_UVN			BIT(5)
+#define ADS112C14_STATUS_MSB_REG_MAP_CRC_FAULTN		BIT(3)
+#define ADS112C14_STATUS_MSB_MEM_FAULTN			BIT(2)
+#define ADS112C14_STATUS_MSB_REG_WRITE_FAULTN		BIT(1)
+#define ADS112C14_STATUS_MSB_DRDY			BIT(0)
+
+#define ADS112C14_REG_STATUS_LSB			0x03
+#define ADS112C14_STATUS_LSB_CONV_COUNT			GENMASK(7, 4)
+#define ADS112C14_STATUS_LSB_GPIO3_DAT_IN		BIT(3)
+#define ADS112C14_STATUS_LSB_GPIO2_DAT_IN		BIT(2)
+#define ADS112C14_STATUS_LSB_GPIO1_DAT_IN		BIT(1)
+#define ADS112C14_STATUS_LSB_GPIO0_DAT_IN		BIT(0)
+
+#define ADS112C14_REG_CONVERSION_CTRL			0x04
+#define ADS112C14_CONVERSION_CTRL_RESET			GENMASK(7, 2)
+#define ADS112C14_CONVERSION_CTRL_START			BIT(1)
+#define ADS112C14_CONVERSION_CTRL_STOP			BIT(0)
+
+#define ADS112C14_REG_DEVICE_CFG			0x05
+#define ADS112C14_DEVICE_CFG_PWDN			BIT(7)
+#define ADS112C14_DEVICE_CFG_STBY_MODE			BIT(6)
+#define ADS112C14_DEVICE_CFG_BOCS			GENMASK(5, 4)
+#define ADS112C14_DEVICE_CFG_CLK_SEL			BIT(3)
+#define ADS112C14_DEVICE_CFG_CONV_MODE			BIT(2)
+#define ADS112C14_DEVICE_CFG_SPEED_MODE			GENMASK(1, 0)
+
+#define ADS112C14_REG_DATA_RATE_CFG			0x06
+#define ADS112C14_DATA_RATE_CFG_DELAY			GENMASK(7, 4)
+#define ADS112C14_DATA_RATE_CFG_GC_EN			BIT(3)
+#define ADS112C14_DATA_RATE_CFG_FLTR_OSR		GENMASK(2, 0)
+
+#define ADS112C14_REG_MUX_CFG				0x07
+#define ADS112C14_MUX_CFG_AINP				GENMASK(7, 4)
+#define ADS112C14_MUX_CFG_AINN				GENMASK(3, 0)
+
+#define ADS112C14_REG_GAIN_CFG				0x08
+#define ADS112C14_GAIN_CFG_SPARE			BIT(7)
+#define ADS112C14_GAIN_CFG_SYS_MON			GENMASK(6, 4)
+#define ADS112C14_GAIN_CFG_GAIN				GENMASK(3, 0)
+
+#define ADS112C14_REG_REFERENCE_CFG			0x09
+#define ADS112C14_REFERENCE_CFG_REF_UV_EN		BIT(7)
+#define ADS112C14_REFERENCE_CFG_REFP_BUF_EN		BIT(5)
+#define ADS112C14_REFERENCE_CFG_REFN_BUF_EN		BIT(4)
+#define ADS112C14_REFERENCE_CFG_REF_VAL			BIT(2)
+#define   ADS112C14_REFERENCE_CFG_REF_VAL_1_25V		  0
+#define   ADS112C14_REFERENCE_CFG_REF_VAL_2_5V		  1
+#define ADS112C14_REFERENCE_CFG_REF_SEL			GENMASK(1, 0)
+
+#define ADS112C14_REG_DIGITAL_CFG			0x0A
+#define ADS112C14_DIGITAL_CFG_REG_MAP_CRC_EN		BIT(6)
+#define ADS112C14_DIGITAL_CFG_I2C_CRC_EN		BIT(5)
+#define ADS112C14_DIGITAL_CFG_STATUS_EN			BIT(4)
+#define ADS112C14_DIGITAL_CFG_FAULT_PIN_BEHAVIOR	BIT(3)
+#define ADS112C14_DIGITAL_CFG_CODING			BIT(1)
+
+#define ADS112C14_REG_GPIO_CFG				0x0B
+#define ADS112C14_GPIO_CFG_GPIO3_CFG			GENMASK(7, 6)
+#define ADS112C14_GPIO_CFG_GPIO2_CFG			GENMASK(5, 4)
+#define ADS112C14_GPIO_CFG_GPIO1_CFG			GENMASK(3, 2)
+#define ADS112C14_GPIO_CFG_GPIO0_CFG			GENMASK(1, 0)
+
+#define ADS112C14_REG_GPIO_DATA_OUTPUT			0x0C
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_SRC		BIT(7)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_SRC		BIT(6)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO3_DAT_OUT	BIT(3)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO2_DAT_OUT	BIT(2)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO1_DAT_OUT	BIT(1)
+#define ADS112C14_GPIO_DATA_OUTPUT_GPIO0_DAT_OUT	BIT(0)
+
+#define ADS112C14_REG_IDAC_MAG_CFG			0x0D
+#define ADS112C14_IDAC_MAG_CFG_I2MAG			GENMASK(7, 4)
+#define ADS112C14_IDAC_MAG_CFG_I1MAG			GENMASK(3, 0)
+
+#define ADS112C14_REG_IDAC_MUX_CFG			0x0E
+#define ADS112C14_IDAC_MUX_CFG_IUNIT			BIT(7)
+#define ADS112C14_IDAC_MUX_CFG_I2MUX			GENMASK(6, 4)
+#define ADS112C14_IDAC_MUX_CFG_I1MUX			GENMASK(2, 0)
+
+#define ADS112C14_REG_REG_MAP_CRC			0x0F
+
+static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ADS112C14_REG_DEVICE_ID:
+	case ADS112C14_REG_REVISION_ID:
+	case ADS112C14_REG_STATUS_LSB:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool ads112c14_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ADS112C14_REG_STATUS_MSB:
+	case ADS112C14_REG_STATUS_LSB:
+	case ADS112C14_REG_CONVERSION_CTRL:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct reg_default ads112c14_reg_defaults[] = {
+	{ ADS112C14_REG_DEVICE_CFG, 0x00 },
+	{ ADS112C14_REG_DATA_RATE_CFG, 0x00 },
+	{ ADS112C14_REG_MUX_CFG, 0x00 },
+	{ ADS112C14_REG_GAIN_CFG, 0x01 },
+	{ ADS112C14_REG_REFERENCE_CFG, 0x00 },
+	{ ADS112C14_REG_DIGITAL_CFG, 0x00 },
+	{ ADS112C14_REG_GPIO_CFG, 0x00 },
+	{ ADS112C14_REG_GPIO_DATA_OUTPUT, 0x00 },
+	{ ADS112C14_REG_IDAC_MAG_CFG, 0x00 },
+	{ ADS112C14_REG_IDAC_MUX_CFG, 0x10 },
+};
+
+static const struct regmap_config ads112c14_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.read_flag_mask = ADS112C14_CMD_RREG,
+	.write_flag_mask = ADS112C14_CMD_WREG,
+	.max_register = ADS112C14_REG_REG_MAP_CRC,
+	.writeable_reg = ads112c14_writeable_reg,
+	.volatile_reg = ads112c14_volatile_reg,
+	.reg_defaults = ads112c14_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(ads112c14_reg_defaults),
+	.cache_type = REGCACHE_MAPLE,
+};
+
+struct ads112c14_chip_info {
+	const char *name;
+	u32 resolution_bits;
+};
+
+struct ads112c14_data {
+	const struct ads112c14_chip_info *chip_info;
+	struct i2c_client *client;
+	struct regmap *regmap;
+};
+
+/* Maps REF_VAL to microvolts. */
+static const u32 ads112c14_internal_ref_uV[] = {
+	[ADS112C14_REFERENCE_CFG_REF_VAL_1_25V] = 1.25 * MICRO,
+	[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V] = 2.5 * MICRO,
+};
+
+/* Fixed channels for system monitor measurements. */
+#define ADS112C14_SYS_MON_CHANNEL_TEMP		100
+#define ADS112C14_SYS_MON_CHANNEL_EXT_REF	101
+#define ADS112C14_SYS_MON_CHANNEL_AVDD		102
+#define ADS112C14_SYS_MON_CHANNEL_DVDD		103
+#define ADS112C14_SYS_MON_CHANNEL_SHORT		104
+
+static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
+	{
+		.type = IIO_TEMP,
+		.indexed = 1,
+		.channel = ADS112C14_SYS_MON_CHANNEL_TEMP,
+		.address = 2,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+				    | BIT(IIO_CHAN_INFO_SCALE)
+				    | BIT(IIO_CHAN_INFO_OFFSET),
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.indexed = 1,
+		.channel = ADS112C14_SYS_MON_CHANNEL_EXT_REF,
+		.address = 3,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+				    | BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.indexed = 1,
+		.channel = ADS112C14_SYS_MON_CHANNEL_AVDD,
+		.address = 4,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+				    | BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.indexed = 1,
+		.channel = ADS112C14_SYS_MON_CHANNEL_DVDD,
+		.address = 5,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+				    | BIT(IIO_CHAN_INFO_SCALE),
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.indexed = 1,
+		.channel = ADS112C14_SYS_MON_CHANNEL_SHORT,
+		.channel2 = ADS112C14_SYS_MON_CHANNEL_SHORT,
+		.differential = 1,
+		.address = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
+				    | BIT(IIO_CHAN_INFO_SCALE),
+	},
+};
+
+static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
+					     const struct iio_chan_spec *chan)
+{
+	int ret;
+
+	/* TODO: GAIN is useful for shorted PGA inputs. */
+	/* All SYS_MON channels use GAIN of 1 to keep it simple. */
+	ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
+				 ADS112C14_GAIN_CFG_SYS_MON |
+				 ADS112C14_GAIN_CFG_GAIN,
+				 FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) |
+				 FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, 1));
+	if (ret)
+		return ret;
+
+	/* All SYS_MON channels use signed data to keep it simple. */
+	ret = regmap_clear_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG,
+				ADS112C14_DIGITAL_CFG_CODING);
+	if (ret)
+		return ret;
+
+	/* TODO: REF_SEL does matter for shorted PGA inputs. */
+	/*
+	 * REVISIT: if we implement regulator support for the REFOUT pin, we
+	 * might need to make this voltage match what is required by that. In
+	 * that case, we could also adjust GAIN so that we still get the same
+	 * range.
+	 */
+	/*
+	 * Setting REF_SEL for SYS_MON doesn't matter (always uses internal
+	 * reference), but we do need to set REF_VAL.
+	 */
+	ret = regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG,
+				 ADS112C14_REFERENCE_CFG_REF_VAL,
+				 FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL,
+					    ADS112C14_REFERENCE_CFG_REF_VAL_2_5V));
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ads112c14_single_conversion(struct ads112c14_data *data,
+				       const struct iio_chan_spec *chan,
+				       u8 *buf)
+{
+	u32 reg_val;
+	int ret;
+
+	if (chan->channel >= 100) {
+		ret = ads112c14_prepare_sys_mon_channel(data, chan);
+		if (ret)
+			return ret;
+	} else {
+		/* Not implemented yet. */
+		return -EINVAL;
+	}
+
+	ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+			   ADS112C14_CONVERSION_CTRL_START);
+	if (ret)
+		return ret;
+
+	ret = regmap_read_poll_timeout(data->regmap,
+				       ADS112C14_REG_STATUS_MSB, reg_val,
+				       FIELD_GET(ADS112C14_STATUS_MSB_DRDY, reg_val),
+				       1 * USEC_PER_MSEC, 10 * USEC_PER_MSEC);
+	if (ret)
+		return ret;
+
+	return i2c_smbus_read_i2c_block_data(data->client, ADS112C14_CMD_RDATA,
+					     BITS_TO_BYTES(data->chip_info->resolution_bits),
+					     buf);
+}
+
+static int ads112c14_read_raw(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan, int *val,
+			      int *val2, long mask)
+{
+	struct ads112c14_data *data = iio_priv(indio_dev);
+	u32 vref_uV, fsr_bits;
+
+	/* Selecting V_REF source is not implemented yet. */
+	vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V];
+
+	/* Currently, everything is using signed data. */
+	fsr_bits = data->chip_info->resolution_bits - 1;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW: {
+		u8 buf[3];
+		int ret;
+
+		if (!iio_device_claim_direct(indio_dev))
+			return -EBUSY;
+
+		ret = ads112c14_single_conversion(data, chan, buf);
+		iio_device_release_direct(indio_dev);
+		if (ret < 0)
+			return ret;
+
+		switch (data->chip_info->resolution_bits) {
+		case 16:
+			*val = get_unaligned_be16(buf);
+			break;
+		case 24:
+			*val = get_unaligned_be24(buf);
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		*val = sign_extend32(*val, fsr_bits);
+
+		return IIO_VAL_INT;
+	}
+	case IIO_CHAN_INFO_SCALE:
+		if (chan->type == IIO_TEMP) {
+			/* TS_TC (typical) = 405 uV/°C */
+			*val = MILLI * vref_uV / 405;
+			*val2 = fsr_bits;
+			return IIO_VAL_FRACTIONAL_LOG2;
+		}
+
+		*val = vref_uV / (MICRO / MILLI);
+		/*
+		 * Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to be
+		 * multiplied by 8 to account for internal attenuation of / 8.
+		 */
+		*val2 = fsr_bits - (chan->address >= 3 ? 3 : 0);
+		return IIO_VAL_FRACTIONAL_LOG2;
+	case IIO_CHAN_INFO_OFFSET:
+		/* Only the temperature channel has an offset. */
+		if (chan->type != IIO_TEMP)
+			return -EINVAL;
+		/* Die temperature [°C] = 25°C + (Measured voltage – TS_Offset) / TS_TC */
+		/* TS_TC (typical) = 405 uV/°C */
+		/* TS_Offset (typical) = 119.5 mV */
+		*val = div_s64((s64)(25 * 405 - 119500) * BIT(fsr_bits), vref_uV);
+		return IIO_VAL_INT;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ads112c14_read_label(struct iio_dev *indio_dev,
+				struct iio_chan_spec const *chan, char *label)
+{
+	const char *label_source;
+
+	/* System monitor channels. */
+	switch (chan->channel) {
+	case ADS112C14_SYS_MON_CHANNEL_TEMP:
+		label_source = "Internal temperature sensor";
+		break;
+	case ADS112C14_SYS_MON_CHANNEL_EXT_REF:
+		label_source = "External reference";
+		break;
+	case ADS112C14_SYS_MON_CHANNEL_AVDD:
+		label_source = "AVDD";
+		break;
+	case ADS112C14_SYS_MON_CHANNEL_DVDD:
+		label_source = "DVDD";
+		break;
+	case ADS112C14_SYS_MON_CHANNEL_SHORT:
+		label_source = "Internal short";
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return sysfs_emit(label, "%s\n", label_source);
+}
+
+static const struct iio_info ads112c14_info = {
+	.read_raw = ads112c14_read_raw,
+	.read_label = ads112c14_read_label,
+};
+
+static int ads112c14_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	const struct ads112c14_chip_info *info;
+	struct iio_dev *indio_dev;
+	struct ads112c14_data *data;
+	u32 reg_val;
+	int ret;
+
+	info = i2c_get_match_data(client);
+
+	indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	data = iio_priv(indio_dev);
+	data->chip_info = info;
+	data->client = client;
+
+	ret = devm_regulator_get_enable(&client->dev, "dvdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
+
+	ret = devm_regulator_get_enable(&client->dev, "avdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+
+	data->regmap = devm_regmap_init_i2c(client, &ads112c14_regmap_config);
+	if (IS_ERR(data->regmap))
+		return dev_err_probe(dev, PTR_ERR(data->regmap),
+				     "failed to init regmap\n");
+
+	/* Write magic reset value (0x16) to ensure known state.*/
+	ret = regmap_write(data->regmap, ADS112C14_REG_CONVERSION_CTRL,
+			   FIELD_PREP(ADS112C14_CONVERSION_CTRL_RESET, 0x16));
+	/*
+	 * The reset may cause an -EREMOTEIO error because of failing to get the
+	 * I2C ACK at the end of the message. The device still gets reset.
+	 */
+	if (ret != -EREMOTEIO)
+		return ret;
+
+	fsleep(ADS112C14_DELAY_RESET_us);
+
+	ret = regmap_read(data->regmap, ADS112C14_REG_STATUS_MSB, &reg_val);
+	if (ret)
+		return ret;
+
+	if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
+		return dev_err_probe(dev, -EIO, "reset failed\n");
+
+	/*
+	 * Clear reset bit to prepare for next probe. And clear AVDD fault since
+	 * that happens on every reset.
+	 */
+	ret = regmap_write(data->regmap, ADS112C14_REG_STATUS_MSB,
+			   ADS112C14_STATUS_MSB_RESETN |
+			   ADS112C14_STATUS_MSB_AVDD_UVN);
+	if (ret)
+		return ret;
+
+	/* Place in single-shot conversion mode to make ready for raw read. */
+	ret = regmap_set_bits(data->regmap, ADS112C14_REG_DEVICE_CFG,
+			      ADS112C14_DEVICE_CFG_CONV_MODE);
+	if (ret)
+		return ret;
+
+	indio_dev->name = info->name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = ads112c14_sys_mon_channels;
+	indio_dev->num_channels = ARRAY_SIZE(ads112c14_sys_mon_channels);
+	indio_dev->info = &ads112c14_info;
+
+	return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct ads112c14_chip_info ads112c14_chip_info = {
+	.name = "ads112c14",
+	.resolution_bits = 16,
+};
+
+static const struct ads112c14_chip_info ads122c14_chip_info = {
+	.name = "ads122c14",
+	.resolution_bits = 24,
+};
+
+static const struct of_device_id ads112c14_of_match[] = {
+	{ .compatible = "ti,ads112c14", .data = &ads112c14_chip_info },
+	{ .compatible = "ti,ads122c14", .data = &ads122c14_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ads112c14_of_match);
+
+static const struct i2c_device_id ads112c14_id[] = {
+	{ .name = "ads112c14", .driver_data = (kernel_ulong_t)&ads112c14_chip_info },
+	{ .name = "ads122c14", .driver_data = (kernel_ulong_t)&ads122c14_chip_info },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ads112c14_id);
+
+static struct i2c_driver ads112c14_driver = {
+	.driver = {
+		.name = "ads112c14",
+		.of_match_table = ads112c14_of_match,
+	},
+	.probe = ads112c14_probe,
+	.id_table = ads112c14_id,
+};
+module_i2c_driver(ads112c14_driver);
+
+MODULE_AUTHOR("David Lechner (TI) <dlechner@baylibre.com>");
+MODULE_DESCRIPTION("TI ADS112C14 I2C ADC driver");
+MODULE_LICENSE("GPL");

-- 
2.43.0


^ permalink raw reply related

* [PATCH 3/4] iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
From: David Lechner (TI) @ 2026-06-15 22:00 UTC (permalink / raw)
  To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel,
	David Lechner
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com>

Implement support for the programmable gain amplifier on the internal
short SYS_MON channel. This channel is used for calibration, so it is
useful to be able to set the PGA to the same gain as the external
channels. The gain setting is implemented via the `_scale` attribute.

In the future, we may want to support different reference voltages for
this channel, so the scale_available table is populated during probe
rather than being a static table.

Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
 drivers/iio/adc/ti-ads112c14.c | 131 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 128 insertions(+), 3 deletions(-)

diff --git a/drivers/iio/adc/ti-ads112c14.c b/drivers/iio/adc/ti-ads112c14.c
index 97097ae2a487..202a449b4234 100644
--- a/drivers/iio/adc/ti-ads112c14.c
+++ b/drivers/iio/adc/ti-ads112c14.c
@@ -122,6 +122,26 @@
 
 #define ADS112C14_REG_REG_MAP_CRC			0x0F
 
+/* Available gains as tenths (x10) */
+static const u32 ads112c14_pga_gains_x10[] = {
+	5, /* 0.5 */
+	10, /* 1 */
+	20, /* 2 */
+	40, /* 4 */
+	50, /* 5 */
+	80, /* 8 */
+	100, /* 10 */
+	160, /* 16 */
+	200, /* 20 */
+	320, /* 32 */
+	500, /* 50 */
+	640, /* 64 */
+	1000, /* 100 */
+	1280, /* 128 */
+	2000, /* 200 */
+	2560, /* 256 */
+};
+
 static bool ads112c14_writeable_reg(struct device *dev, unsigned int reg)
 {
 	switch (reg) {
@@ -181,6 +201,8 @@ struct ads112c14_data {
 	const struct ads112c14_chip_info *chip_info;
 	struct i2c_client *client;
 	struct regmap *regmap;
+	u8 sys_mon_chan_short_gain_val;
+	int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
 };
 
 /* Maps REF_VAL to microvolts. */
@@ -239,21 +261,28 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
 		.address = 1,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW)
 				    | BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE),
 	},
 };
 
 static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
 					     const struct iio_chan_spec *chan)
 {
+	u32 gain_val;
 	int ret;
 
-	/* TODO: GAIN is useful for shorted PGA inputs. */
-	/* All SYS_MON channels use GAIN of 1 to keep it simple. */
+	/*
+	 * All SYS_MON channels use GAIN of 1 to keep it simple. Other than
+	 * the internal short channel, where it is useful in practice.
+	 */
+	gain_val = chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT ?
+		   data->sys_mon_chan_short_gain_val : 1;
+
 	ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
 				 ADS112C14_GAIN_CFG_SYS_MON |
 				 ADS112C14_GAIN_CFG_GAIN,
 				 FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, chan->address) |
-				 FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, 1));
+				 FIELD_PREP(ADS112C14_GAIN_CFG_GAIN, gain_val));
 	if (ret)
 		return ret;
 
@@ -366,6 +395,17 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
 			return IIO_VAL_FRACTIONAL_LOG2;
 		}
 
+		if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
+			int *scale_avail = data->sys_mon_chan_short_scale_available[
+				data->sys_mon_chan_short_gain_val
+			];
+
+			*val = scale_avail[0];
+			*val2 = scale_avail[1];
+
+			return IIO_VAL_INT_PLUS_NANO;
+		}
+
 		*val = vref_uV / (MICRO / MILLI);
 		/*
 		 * Last 3 SYS_MON channels (ext ref, AVDD, DVDD) need to be
@@ -387,6 +427,61 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
 	}
 }
 
+static int ads112c14_read_avail(struct iio_dev *indio_dev,
+				const struct iio_chan_spec *chan, const int **vals,
+				int *type, int *length, long mask)
+{
+	struct ads112c14_data *data = iio_priv(indio_dev);
+
+	switch (chan->channel) {
+	case ADS112C14_SYS_MON_CHANNEL_SHORT:
+		*vals = (const int *)data->sys_mon_chan_short_scale_available;
+		*length = 2 * ARRAY_SIZE(data->sys_mon_chan_short_scale_available);
+		*type = IIO_VAL_INT_PLUS_NANO;
+		return IIO_AVAIL_LIST;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ads112c14_write_raw(struct iio_dev *indio_dev,
+			       struct iio_chan_spec const *chan, int val,
+			       int val2, long mask)
+{
+	struct ads112c14_data *data = iio_priv(indio_dev);
+
+	switch (chan->channel) {
+	case ADS112C14_SYS_MON_CHANNEL_SHORT: {
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
+		for (u32 i = 0; i < ARRAY_SIZE(data->sys_mon_chan_short_scale_available); i++) {
+			if (val == data->sys_mon_chan_short_scale_available[i][0] &&
+			    val2 == data->sys_mon_chan_short_scale_available[i][1]) {
+				data->sys_mon_chan_short_gain_val = i;
+				return 0;
+			}
+		}
+		return -EINVAL;
+	}
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
+				       struct iio_chan_spec const *chan,
+				       long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		return IIO_VAL_INT_PLUS_NANO;
+	default:
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+}
+
 static int ads112c14_read_label(struct iio_dev *indio_dev,
 				struct iio_chan_spec const *chan, char *label)
 {
@@ -418,9 +513,34 @@ static int ads112c14_read_label(struct iio_dev *indio_dev,
 
 static const struct iio_info ads112c14_info = {
 	.read_raw = ads112c14_read_raw,
+	.read_avail = ads112c14_read_avail,
+	.write_raw = ads112c14_write_raw,
+	.write_raw_get_fmt = ads112c14_write_raw_get_fmt,
 	.read_label = ads112c14_read_label,
 };
 
+static void ads112c14_populate_tables(struct ads112c14_data *data)
+{
+	u32 vref_uV, fsr_bits;
+	int i;
+
+	/* For now, assuming we are using 2.5V reference. */
+	vref_uV = ads112c14_internal_ref_uV[ADS112C14_REFERENCE_CFG_REF_VAL_2_5V];
+	fsr_bits = data->chip_info->resolution_bits - 1;
+
+	for (i = 0; i < ARRAY_SIZE(ads112c14_pga_gains_x10); i++) {
+		int *scale_avail = &data->sys_mon_chan_short_scale_available[i][0];
+		u32 gain_x10 = ads112c14_pga_gains_x10[i];
+
+		/* NB: slightly odd arrangement to avoid overflow. */
+		scale_avail[0] = div_u64_rem(div_u64((u64)NANO * 10 /
+						     (MICRO / MILLI) * vref_uV /
+						     gain_x10,
+						     BIT(fsr_bits)),
+					     NANO, &scale_avail[1]);
+	}
+}
+
 static int ads112c14_probe(struct i2c_client *client)
 {
 	struct device *dev = &client->dev;
@@ -472,6 +592,9 @@ static int ads112c14_probe(struct i2c_client *client)
 	if (FIELD_GET(ADS112C14_STATUS_MSB_RESETN, reg_val))
 		return dev_err_probe(dev, -EIO, "reset failed\n");
 
+	/* Default gain after reset is 1. */
+	data->sys_mon_chan_short_gain_val = 1;
+
 	/*
 	 * Clear reset bit to prepare for next probe. And clear AVDD fault since
 	 * that happens on every reset.
@@ -488,6 +611,8 @@ static int ads112c14_probe(struct i2c_client *client)
 	if (ret)
 		return ret;
 
+	ads112c14_populate_tables(data);
+
 	indio_dev->name = info->name;
 	indio_dev->modes = INDIO_DIRECT_MODE;
 	indio_dev->channels = ads112c14_sys_mon_channels;

-- 
2.43.0


^ permalink raw reply related

* [PATCH 1/4] dt-bindings: iio: adc: add ti,ads122c14
From: David Lechner (TI) @ 2026-06-15 21:59 UTC (permalink / raw)
  To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel,
	David Lechner
In-Reply-To: <20260615-iio-adc-ti-ads122c14-v1-0-e6bdadf7cb2b@baylibre.com>

Add new bindings for ti,ads122c14 and similar devices.

This is an ADC that is primarily intended for use with temperature
sensors. There are a few unusual properties because of this. In
particular, the reference voltage source and current output requirements
can be different for each measurement, so these are included in the
channel bindings.

The REFP/REFN reference voltage is usually just connected to a resistor
that is being driven by the ADC's current outputs, so there is special
property for this case rather than requiring a regulator to be defined
to represent that.

ti,vref-source is reused from ti,tlv320adcx140.yaml (otherwise might
have preferred an enum of strings).

Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
---
 .../devicetree/bindings/iio/adc/ti,ads112c14.yaml  | 224 +++++++++++++++++++++
 MAINTAINERS                                        |   7 +
 include/dt-bindings/iio/adc/ti,ads112c14.h         |  11 +
 3 files changed, 242 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
new file mode 100644
index 000000000000..dc7f37cad772
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
@@ -0,0 +1,224 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads112c14.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments' ADS112C14 and similar ADC chips
+
+description: |
+  Supports the following Texas Instruments' ADC chips:
+  - ADS112C14 (16-bit)
+  - ADS122C14 (24-bit)
+
+  https://www.ti.com/lit/ds/symlink/ads122c14.pdf
+
+  These chips are primarily designed for use with temperature sensors such as
+  RTDs and thermocouples. The channel bindings reflect this in that each channel
+  represents the conditions required to make a measurement rather than strictly
+  just the physical input channels.
+
+maintainers:
+  - David Lechner <dlechner@baylibre.com>
+
+unevaluatedProperties: false
+
+properties:
+  compatible:
+    enum:
+      - ti,ads112c14
+      - ti,ads122c14
+
+  reg:
+    items:
+      - minimum: 0x40
+        maximum: 0x47
+
+  clocks:
+    maxItems: 1
+    description: Optional external clock connected to GPIO3 pin.
+
+  avdd-supply: true
+  dvdd-supply: true
+
+  refp-supply: true
+  refn-supply: true
+
+  refp-refn-resistor-ohms:
+    description:
+      The resistance of the external resistor between REFP and REFN when using
+      resistor bridge driven by current outputs for RTD measurements.
+
+  interrupts:
+    minItems: 1
+    items:
+      - description: FAULT interrupt (GPIO2 pin)
+      - description: DRDY interrupt (GPIO3 pin)
+
+  interrupt-names:
+    minItems: 1
+    maxItems: 2
+    items:
+      enum: [fault, drdy]
+
+  gpio-controller: true
+  '#gpio-cells':
+    const: 2
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+patternProperties:
+  ^channel@[0-7]$:
+    $ref: adc.yaml
+
+    unevaluatedProperties: false
+
+    properties:
+      reg:
+        maximum: 16 # arbitrary limit, channel@ can be any combination of AIN0-AIN7
+
+      single-channel:
+        maximum: 7
+
+      diff-channels:
+        items:
+          maximum: 7
+
+      bipolar:
+        description:
+          Set this flag if the differential input can be negative.
+
+      excitation-channels:
+        description: AINx pins used as current output.
+        $ref: /schemas/types.yaml#/definitions/uint32-array
+        minItems: 1
+        maxItems: 2
+        items:
+          maximum: 7
+
+      excitation-current-microamp:
+        description: The current output of the excitation channels in microamps.
+        minimum: 1
+        maximum: 1000
+
+      current-chopping:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description:
+          If provided, the two excitation channels are to be used with current
+          chopping enabled.
+
+      ti,vref-source:
+        description: |
+          Indicates the source for the reference voltage for this channel.
+          0 - Internal 2.5V reference
+          1 - Internal 1.25V reference
+          2 - External reference (REFP-REFN)
+          3 - AVDD as reference
+
+          For convenience, macros for these values are available in
+          dt-bindings/iio/adc/ti,ads112c14.h.
+        $ref: /schemas/types.yaml#/definitions/uint32
+        maximum: 3
+        default: 0
+
+    dependencies:
+      excitation-channels: [ excitation-current-microamp ]
+      excitation-current-microamp: [ excitation-channels ]
+      current-chopping: [ excitation-channels ]
+
+    oneOf:
+      - required: [ single-channel ]
+      - required: [ diff-channels ]
+
+required:
+  - compatible
+  - reg
+  - avdd-supply
+  - dvdd-supply
+
+dependencies:
+  refn-supply: [ refp-supply ]
+
+allOf:
+  - oneOf:
+      - required: [ refp-supply ]
+      - required: [ refp-refn-resistor-ohms ]
+      - properties:
+          refp-supply: false
+          refn-supply: false
+          refp-refn-resistor-ohms: false
+
+examples:
+  - |
+    #include <dt-bindings/iio/adc/ti,ads112c14.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@40 {
+            compatible = "ti,ads112c14";
+            reg = <0x40>;
+
+            avdd-supply = <&avdd>;
+            dvdd-supply = <&dvdd>;
+
+            /* 3-Wire RTD: Two IDACs, One Measurement (AIN1-AIN2) */
+
+            refp-refn-resistor-ohms = <500>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            channel@0 {
+              reg = <0>;
+              diff-channels = <1>, <2>;
+              excitation-channels = <0>, <3>;
+              excitation-current-microamp = <500>;
+              current-chopping;
+              ti,vref-source = <ADS112C14_VREF_SOURCE_EXTERNAL>;
+              label = "rtd";
+            };
+        };
+    };
+  - |
+    #include <dt-bindings/iio/adc/ti,ads112c14.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@40 {
+            compatible = "ti,ads112c14";
+            reg = <0x40>;
+
+            avdd-supply = <&avdd>;
+            dvdd-supply = <&dvdd>;
+
+            /* Resistive Bridge Measurement With a Thermistor for Temperature Compensation*/
+
+            refp-supply = <&avdd>;
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            channel@0 {
+              reg = <0>;
+              diff-channels = <6>, <7>;
+              bipolar;
+              ti,vref-source = <ADS112C14_VREF_SOURCE_EXTERNAL>;
+              label = "bridge";
+            };
+
+            channel@1 {
+              reg = <1>;
+              diff-channels = <1>, <2>;
+              ti,vref-source = <ADS112C14_VREF_SOURCE_INTERNAL_2_5V>;
+              label = "thermistor";
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index f1caa6e5198b..9ce7c61b0c14 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26911,6 +26911,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1119.yaml
 F:	drivers/iio/adc/ti-ads1119.c
 
+TI ADS112C14 ADC DRIVER
+M:	David Lechner <dlechner@baylibre.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/adc/ti,ads112c14.yaml
+F:	include/dt-bindings/iio/adc/ti,ads112c14.h
+
 TI ADS1018 ADC DRIVER
 M:	Kurt Borja <kuurtb@gmail.com>
 L:	linux-iio@vger.kernel.org
diff --git a/include/dt-bindings/iio/adc/ti,ads112c14.h b/include/dt-bindings/iio/adc/ti,ads112c14.h
new file mode 100644
index 000000000000..96906642fe41
--- /dev/null
+++ b/include/dt-bindings/iio/adc/ti,ads112c14.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef _DT_BINDINGS_TI_ADS112C14_H
+#define _DT_BINDINGS_TI_ADS112C14_H
+
+#define ADS112C14_VREF_SOURCE_INTERNAL_2_5V	0
+#define ADS112C14_VREF_SOURCE_INTERNAL_1_25V	1
+#define ADS112C14_VREF_SOURCE_EXTERNAL		2
+#define ADS112C14_VREF_SOURCE_AVDD		3
+
+#endif /* _DT_BINDINGS_TI_ADS112C14_H */

-- 
2.43.0


^ permalink raw reply related

* [PATCH 0/4] iio: adc: new ti-ads112c14 driver
From: David Lechner (TI) @ 2026-06-15 21:59 UTC (permalink / raw)
  To: Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley
  Cc: Kurt Borja, Nguyen Minh Tien, linux-iio, devicetree, linux-kernel,
	David Lechner

This adds support for TI ADS112C14 and ADS122C14 ADC chips.

The closest thing we've seen to this in the kernel already is ads124s08.
However, that has a completely different register map and the DT
bindings are incomplete and the driver is extremely basic. So I've just
started from scratch here.

We've also had a similar submission recently for ADS1220 [1]. That chip
is in a similar situation to ads124s08 in that it has a different
register map (but the submitted DT bindings are better than the ones for
ads124s08, even if still a bit incomplete). And literally as I was
writing the previous sentence, another series [2]  was sent for yet
another similar family of chips (ADS1262). That one is even more complex
in the feature set than the ones I am working on. I was going to polish
up the driver a bit more before submitting it, but now it seems more
urgent to coordinate with the other two series to align on how we would
like to handle all of these.

[1]: https://lore.kernel.org/linux-iio/20260610151342.44274-1-zizuzacker@gmail.com/
[2]: https://lore.kernel.org/linux-iio/20260612-ads126x-v1-0-894c788d03ed@gmail.com/

All of these chips have in common that they are designed for use with
RTDs and thermocouples and so they look very similar to each other in
terms of wiring and feature set, even if the register maps are
different. They are in the gray area where we could either keep them
separate because they are just different enough, or we could do like
we've done before with ad_sigma_delta and have a bit of an abstraction
layer for the register differences and otherwise try to share as much
code as possible. Normally, I would lean towards keeping them separate,
but in this case, I'm considering trying to share code because the
devicetree bindings for the inputs is complex and is going to be mostly
the same across all of these chips.

If we decide to go the route of sharing code, we could still merge this
series as-is and then do the refactoring to add the abstraction layer in
a follow-up series that also adds support for the first of the other
chips.

This series includes just basic support for reading single measurements
from the ADC and gain selection via the scale attribute. I plan to
follow this up with additional series to add support for buffered reads,
filtering/oversampling configuration, event support, gpio controller
support and perhaps a few other things that are slipping my mind right
now.

The most interesting part about this (that I alluded to above) is the
way channels are handled. These are multipling ADCs with differential
and single-ended inputs. But what sets them apart from other similar
chips is that since they are designed for use with RTDs, there can also
be a current output required to excite the RTD and this current output
might be different for different channels. So the way I conceptualized
the channels is that the devicetree specifies the conditions needed
to take a particular measurement rather than being purely a physical
channel.

This makes things more flexible, but does make the driver a bit more
complex. For example, knowing when the current output needs to be
enabled or disabled. For now, I have chosen a lazy-enable where they
are not turned on until the first measurement is taken that requires
them, but then they stay on until another measurement is taken that
doesn't require them. This can lead to some oddness with the diagnostic
channels that may be measuring something that indirectly requires the
current output (i.e. the external reference voltage when it is connected
to a resistor rather than a power supply). This means you need to take
a measurement that requires the current output to be enabled before the
diagnostic channels will give accurate readings.

I have also pushed a branch to [3] that contains the start of some
documentation for this driver that can give some more insight into how
the implementation works. It still needs some work and also documents
some things that haven't been implemented yet, so I haven't included it
in this series yet.

[3]: https://github.com/dlech/linux/blob/b4/iio-adc-ti-ads122c14/Documentation/iio/ads112c14.rst

Signed-off-by: David Lechner <dlechner@baylibre.com>
---
David Lechner (TI) (4):
      dt-bindings: iio: adc: add ti,ads122c14
      iio: adc: add ti-ads112c14 driver
      iio: adc: ti-ads112c14: implement gain on internal short SYS_MON channel
      iio: adc: ti-ads112c14: add measurement channel support

 .../devicetree/bindings/iio/adc/ti,ads112c14.yaml  |  224 +++++
 MAINTAINERS                                        |    8 +
 drivers/iio/adc/Kconfig                            |   12 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/ti-ads112c14.c                     | 1053 ++++++++++++++++++++
 include/dt-bindings/iio/adc/ti,ads112c14.h         |   11 +
 6 files changed, 1309 insertions(+)
---
base-commit: ec039126b7fac4e3af35ebccaa7c6f9b6875ba81
change-id: 20260514-iio-adc-ti-ads122c14-d0b92479334e

Best regards,
--  
David Lechner (TI) <dlechner@baylibre.com>


^ permalink raw reply

* Re: [PATCH net-next v2 1/2] dt-bindings: net: pse-pd: add bindings for Realtek/Broadcom PSE MCU
From: Rob Herring @ 2026-06-15 21:29 UTC (permalink / raw)
  To: Jonas Jelonek
  Cc: Oleksij Rempel, Kory Maincent, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Krzysztof Kozlowski,
	Conor Dooley, netdev, devicetree, linux-kernel, Daniel Golle,
	Bjørn Mork
In-Reply-To: <20260612132944.460646-2-jelonek.jonas@gmail.com>

On Fri, Jun 12, 2026 at 01:29:41PM +0000, Jonas Jelonek wrote:
> Add a binding for the microcontroller (MCU) that fronts the PSE silicon
> on a range of managed switches. The host talks only to the MCU, over
> I2C/SMBus or UART, using a fixed message-based protocol; the PSE chips
> behind it never appear on the bus.
> 
> The compatible identifies the PSE-MCU protocol dialect
> (realtek,pse-mcu-rtk or realtek,pse-mcu-bcm), not a specific part: the
> node describes the MCU - whose silicon is a general-purpose
> microcontroller that varies across boards - and the 'realtek' vendor
> prefix reflects the platform these MCUs are found on (Realtek-based PoE
> switches), following the google,cros-ec-* pattern rather than naming the
> MCU silicon. The '-rtk'/'-bcm' suffix selects the Realtek or Broadcom
> dialect within that one family. The specific PSE chip is detected at
> runtime and is not described here.
> 
> A single compatible per dialect covers both the I2C/SMBus and UART
> attachments: the wire protocol is identical across them and the transport
> is expressed by the node's parent bus, so it is not encoded in the
> compatible.
> 
> Both dialects share one protocol family and one device tree contract, so
> they are documented in a single binding under one vendor prefix. The
> 'realtek' prefix is used because this MCU front-end is found almost
> exclusively on Realtek-based switches; the Broadcom dialect is expressed
> as the realtek,pse-mcu-bcm compatible within the same family.
> 
> Signed-off-by: Jonas Jelonek <jelonek.jonas@gmail.com>
> ---
>  .../bindings/net/pse-pd/realtek,pse-mcu.yaml  | 154 ++++++++++++++++++
>  1 file changed, 154 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
> 
> diff --git a/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
> new file mode 100644
> index 000000000000..2fb729dcb41f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/pse-pd/realtek,pse-mcu.yaml
> @@ -0,0 +1,154 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/pse-pd/realtek,pse-mcu.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Realtek/Broadcom PSE MCU
> +
> +maintainers:
> +  - Jonas Jelonek <jelonek.jonas@gmail.com>
> +
> +description: |
> +  Microcontroller (MCU) that fronts the PSE hardware on switches using
> +  Realtek (RTL8238B, RTL8239, RTL8239C) or Broadcom (BCM59111, BCM59121)
> +  PSE chips. The MCU exposes a small message-based protocol over either
> +  I2C/SMBus or UART; the actual PSE silicon is not accessed directly. The
> +  Realtek and Broadcom variants share this device tree contract but use
> +  different protocol opcodes, selected by the compatible.
> +
> +  The compatible identifies the PSE-MCU protocol dialect, not a specific
> +  part. The device described here is the MCU, whose own silicon varies
> +  across boards and is incidental to the protocol. The MCU is not
> +  made by Realtek or Broadcom; the 'realtek' vendor prefix reflects the
> +  platform these MCUs are found on (Realtek-based PoE switches) and the
> +  '-rtk'/'-bcm' suffix selects the Realtek or Broadcom protocol dialect.
> +  The specific PSE chip behind the MCU is not described in the device
> +  tree either; it is detected at runtime by querying the MCU.
> +
> +  A single compatible per dialect covers both the I2C/SMBus and UART
> +  attachments: the wire protocol is identical across them and the
> +  transport is already expressed by the node's parent bus, so it is not
> +  encoded in the compatible. Transport-specific properties differ
> +  accordingly - the I2C attachment carries 'reg' (and, for Realtek,
> +  'realtek,i2c-protocol'), while the UART attachment carries the serial
> +  peripheral properties such as 'current-speed'.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - realtek,pse-mcu-rtk

The "rtk" feels redundant.

> +      - realtek,pse-mcu-bcm

"brcm" is the standard vendor prefix, so use that instead of "bcm". 
Though who defined the protocol in this case? Realtek or Broadcom? In 
the latter case, I'd argue that "brcm" should be the vendor prefix.

> +
> +  reg:
> +    maxItems: 1
> +
> +  power-supply:
> +    description: Regulator supplying the PoE power rail.
> +
> +  enable-gpios:
> +    maxItems: 1
> +
> +  realtek,i2c-protocol:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    enum: [ i2c, smbus ]
> +    description: |
> +      Wire framing the MCU firmware expects on the I2C bus. "smbus" means
> +      reads carry a leading command byte (0x00) and a repeated start; "i2c"
> +      means bare 12-byte writes and reads with no command prefix. Only
> +      applies to the Realtek I2C attachment.

I tend to think this should be distinguished by the compatible string. 
That would simplify the schema given it only applies to one of the 
compatible strings.

Rob

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox