Devicetree
 help / color / mirror / Atom feed
* [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

* Re: [PATCH v8 00/10] clk: realtek: Add RTD1625 clock support
From: Brian Masney @ 2026-06-15 21:28 UTC (permalink / raw)
  To: Yu-Chun Lin
  Cc: mturquette, sboyd, robh, krzk+dt, conor+dt, p.zabel, cylee12,
	afaerber, jyanchou, devicetree, linux-clk, linux-kernel,
	linux-arm-kernel, linux-realtek-soc, james.tai, cy.huang,
	stanley_chang
In-Reply-To: <20260610080824.255063-1-eleanor.lin@realtek.com>

Hi Yu-Chun,

On Wed, Jun 10, 2026 at 04:08:14PM +0800, Yu-Chun Lin wrote:
> Hello,
> 
> This patch series adds clock support for Realtek's RTD1625 platform.
> The series includes:
> 1. Device Tree: Add clock controller nodes.
> 2. Infrastructure: reset controller, basic clocks, PLLs, gate clocks, mux
> clocks, and MMC-tuned PLLs.
> 3. Platform drivers: two clock controller drivers for RTD1625-CRT and
> RTD1625-ISO.
> 
> Best regards,
> Yu-Chun Lin

Sashiko has some legitimate feedback about this patch set:

https://sashiko.dev/#/patchset/20260610080824.255063-1-eleanor.lin%40realtek.com

Can you go through that and post a new version? I'll review the next
version manually in more detail.

Brian


^ permalink raw reply

* Re: [PATCH] dt-bindings: i2c: i2c-mux-pinctrl: change maintainer
From: Andi Shyti @ 2026-06-15 21:15 UTC (permalink / raw)
  To: Thierry Reding
  Cc: Wolfram Sang, linux-i2c, Thierry Reding, Peter Rosin, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, devicetree
In-Reply-To: <ajBGibftf679T6P4@arch.a226c7d-lcedt>

Hi Thierry,

> >  maintainers:
> > -  - Wolfram Sang <wsa@kernel.org>
> > +  - Thierry Reding <treding@nvidia.com>
> >  
> >  description: |
> >    This binding describes an I2C bus multiplexer that uses pin multiplexing to route the I2C
> 
> By default I used to list the subsystem maintainer as the bindings
> maintainer if the binding wasn't Tegra-specific, or in this case the
> original author wasn't active anymore.
> 
> I'm fine being listed as the maintainer for this if you don't want to,
> but I prefer to use the thierry.reding@kernel.org email address for
> communication.

Just to be clear, are you saying that in this file you want your
kernel.org e-mail or are you referring to generic communication?

Andi

> With that:
> 
> Acked-by: Thierry Reding <treding@nvidia.com>



^ permalink raw reply

* Re: [PATCH v9 4/9] dt-bindings: display: imx: Add i.MX94 DCIF
From: Rob Herring (Arm) @ 2026-06-15 21:04 UTC (permalink / raw)
  To: Laurentiu Palcu
  Cc: Conor Dooley, Simona Vetter, Fabio Estevam, David Airlie,
	Luca Ceresoli, linux-arm-kernel, dri-devel, imx, linux-kernel,
	Krzysztof Kozlowski, Maxime Ripard, Maarten Lankhorst,
	Thomas Zimmermann, Pengutronix Kernel Team, devicetree, Frank Li,
	Philipp Zabel, linux-clk, Sascha Hauer, Ying Liu
In-Reply-To: <20260612-dcif-upstreaming-v9-4-8d0ff89aa3c5@oss.nxp.com>


On Fri, 12 Jun 2026 14:58:35 +0300, Laurentiu Palcu wrote:
> DCIF is the i.MX94 Display Controller Interface which is used to
> drive a TFT LCD panel or connects to a display interface depending
> on the chip configuration.
> 
> Signed-off-by: Laurentiu Palcu <laurentiu.palcu@oss.nxp.com>
> ---
>  .../bindings/display/imx/nxp,imx94-dcif.yaml       | 90 ++++++++++++++++++++++
>  1 file changed, 90 insertions(+)
> 

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


^ permalink raw reply

* Re: [PATCH v3] dt-bindings: pinctrl: qcom,pmic-gpio: Add Qualcomm PMK7750
From: Rob Herring (Arm) @ 2026-06-15 21:02 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: linux-arm-msm, Abel Vesa, devicetree, linux-gpio,
	Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bjorn Andersson,
	linux-kernel
In-Reply-To: <20260612090426.23403-2-krzysztof.kozlowski@oss.qualcomm.com>


On Fri, 12 Jun 2026 11:04:27 +0200, Krzysztof Kozlowski wrote:
> Document Qualcomm PMK7750 GPIO used with Eliza SoC.  PMIC is almost the
> same as PMK8550, thus compatible with it.
> 
> Cc: Abel Vesa <abel.vesa@oss.qualcomm.com>
> Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
> 
> ---
> 
> Changes in v3:
> 1. Drop stale pmk7750 from main enum lisrt (the big one) - leftover of
>    previous version
> 
> Changes in v2:
> 1. Add fallback compatible.
> ---
>  .../bindings/pinctrl/qcom,pmic-gpio.yaml      | 151 +++++++++---------
>  1 file changed, 78 insertions(+), 73 deletions(-)
> 

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


^ permalink raw reply

* Re: [PATCH v3] dt-bindings: interrupt-controller: ti,irq-crossbar: Convert to DT schema
From: Rob Herring (Arm) @ 2026-06-15 21:01 UTC (permalink / raw)
  To: Bhargav Joshi
  Cc: simona.toaca, Krzysztof Kozlowski, m-chawdhry, daniel.baluta,
	Thomas Gleixner, Sricharan R, linux-kernel, devicetree,
	Conor Dooley, goledhruva
In-Reply-To: <20260612-crossbar-v3-1-266747bc2e86@gmail.com>


On Fri, 12 Jun 2026 02:42:29 +0530, Bhargav Joshi wrote:
> Convert TI irq-crossbar binding from text format to DT schema.
> 
> As part of conversion following changes are made:
>  - Add '#interrupt-cells' as a required property which was missing in
>    text binding
>  - As irq-crossbar is interrupt-controller. Move binding from
>    bindings/arm/omap to bindings/interrupt-controller
> 
> Signed-off-by: Bhargav Joshi <j.bhargav.u@gmail.com>
> ---
> Changes in v3:
> - Fixed typo in property description
> - Link to v2: https://lore.kernel.org/r/20260611-crossbar-v2-1-231d4f88298e@gmail.com
> 
> Changes in v2:
> - Dropped property name change and driver updates.
> - Link to v1: https://lore.kernel.org/r/20260606-crossbar-v1-0-f67f7cb9ee50@gmail.com
> ---
>  .../devicetree/bindings/arm/omap/crossbar.txt      | 55 -------------
>  .../interrupt-controller/ti,irq-crossbar.yaml      | 96 ++++++++++++++++++++++
>  2 files changed, 96 insertions(+), 55 deletions(-)
> 

Applied, thanks!


^ permalink raw reply

* Re: [PATCH net-next v5 05/15] net: ethernet: oa_tc6: Move constant definitions to header file
From: Jakub Kicinski @ 2026-06-15 20:55 UTC (permalink / raw)
  To: Selvamani Rajagopal via B4 Relay
  Cc: Selvamani.Rajagopal, Andrew Lunn, Piergiorgio Beruto,
	Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
	Paolo Abeni, Andrew Lunn, Parthiban Veerasooran, Richard Cochran,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley, Simon Horman,
	Jonathan Corbet, Shuah Khan, netdev, linux-kernel, devicetree,
	linux-doc, Jerry Ray
In-Reply-To: <20260614-s2500-mac-phy-support-v5-5-89874b72f725@onsemi.com>

On Sun, 14 Jun 2026 10:00:21 -0700 Selvamani Rajagopal via B4 Relay
wrote:
> To help other source files within the module share the
> constant definitions, they are moved to a header file.
> 
> The memory map selector(MMS) values that are defined in
> in Table 6 of OPEN Alliance 10BASE-T1x Serial Interface
> specification and currently used are added.

If you're adding kdoc on functions the return value must be documented
(unless it's void of course):

Warning: drivers/net/ethernet/oa_tc6/oa_tc6_ptp.c:34 No description found for return value of 'oa_tc6_ptp_register'
Warning: drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c:143 function parameter 'stats' not described in 'oa_tc6_get_ts_stats'
Warning: drivers/net/ethernet/oa_tc6/oa_tc6_tstamp.c:185 No description found for return value of 'oa_tc6_get_ts_info'


Please note that net-next is closed during the merge window, please
wait with the repost per: https://netdev.bots.linux.dev/net-next.html
-- 
pw-bot: cr

^ permalink raw reply

* Re: [PATCH v2] riscv: dts: spacemit: k1-orangepi-rv2: Add cpu scaling
From: sashiko-bot @ 2026-06-15 20:41 UTC (permalink / raw)
  To: Vincent Legoll; +Cc: conor+dt, robh, devicetree
In-Reply-To: <20260615-k1-cpufreq-orangepi-rv2-v2-1-237f006e580b@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `buck1` CPU supply regulator has a dangerously high maximum voltage constraint of 3.45V, risking permanent CPU damage under DVFS.
- [Low] The patch includes `k1-opp.dtsi`, which does not exist in the source tree, leading to a fatal device tree build error.
--

commit 6c23623ea2ead11420af84821e27659f9fd1622e
Author: Vincent Legoll <vincent.legoll@gmail.com>

riscv: dts: spacemit: k1-orangepi-rv2: Add cpu scaling

This commit enables CPU DVFS on the OrangePi RV2 by including an OPP
device tree file and binding the CPU supply to a regulator.

> diff --git a/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts b/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts
> index 7c49bce427f30..2c2542e02d6a8 100644
> --- a/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts
> +++ b/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts
> @@ -7,6 +7,7 @@
>  /dts-v1/;
>  
>  #include "k1.dtsi"
> +#include "k1-opp.dtsi"

[Severity: Low]
Does the k1-opp.dtsi file exist in the source tree? Including a missing file
will cause the device tree compiler to fail with a no such file or directory
error, immediately breaking the build.

>  #include "k1-pinctrl.dtsi"
>  
>  / {

[ ... ]

> @@ -80,6 +81,38 @@ &combo_phy {
>  	status = "okay";
>  };
>  
> +&cpu_0 {
> +	cpu-supply = <&buck1_0v9>;
> +};

[ ... ]

> @@ -162,7 +195,7 @@ pmic@41 {
>  		dldoin2-supply = <&buck5>;
>  
>  		regulators {
> -			buck1 {
> +			buck1_0v9: buck1 {
>  				regulator-min-microvolt = <500000>;
>  				regulator-max-microvolt = <3450000>;

[Severity: High]
Is this maximum voltage constraint safe for the CPU under DVFS? The
regulator allows up to 3.45V, and by actively binding it to the CPU as
cpu-supply in this patch, cpufreq or the regulator framework could request
voltages much higher than the typical ~0.9V core voltage. Could this risk
permanent overvoltage damage to the CPU if an invalid OPP table or userspace
override occurs?

>  				regulator-ramp-delay = <5000>;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615-k1-cpufreq-orangepi-rv2-v2-1-237f006e580b@gmail.com?part=1

^ permalink raw reply

* Re: [PATCH 2/3] dt-bindings: mfd: s2mu005-pmic: drop compatible property for multi-led node
From: sashiko-bot @ 2026-06-15 20:40 UTC (permalink / raw)
  To: Kaustabh Chakraborty; +Cc: robh, devicetree, conor+dt
In-Reply-To: <20260616-s2mu005-pmic-supplement-v1-2-41e84518b711@disroot.org>

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The commit removes the `compatible` property from the `multi-led` node schema but leaves it in the `examples` section, causing `dt_binding_check` to fail.
--

commit 0e7f716b7cd2059b3923af518e9775e3715e58df
Author: Kaustabh Chakraborty <kauschluss@disroot.org>

dt-bindings: mfd: s2mu005-pmic: drop compatible property for multi-led node

The multi-led node is very trivial in description and also has no
sub-nodes. A compatible string property for such nodes is not preferred
by upstream. Remove said node from the schema. While at it, also add a
description following its other sibling nodes.

Link: https://lore.kernel.org/all/d2f4cb7d-5c3e-4b9a-86ca-04262cbb9775@kernel.org

> diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
> index 8354422e39b1e..f62fe7a05147e 100644
> --- a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
> +++ b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
> @@ -38,17 +38,10 @@ properties:
>        Child node describing MUIC device.
>  
>    multi-led:
> -    type: object
> +    $ref: /schemas/leds/leds-class-multicolor.yaml#
>  
> -    allOf:
> -      - $ref: /schemas/leds/leds-class-multicolor.yaml#
> -
> -    properties:
> -      compatible:
> -        const: samsung,s2mu005-rgb
> -
> -    required:
> -      - compatible
> +    description:
> +      Child node describing the RGB status LED.
>  
>      unevaluatedProperties: false

[Severity: Low]
With the compatible property dropped from the schema but unevaluatedProperties
remaining false, will this cause a regression during dt_binding_check?

The examples section in this file still defines the compatible string:

examples:
...
            multi-led {
                compatible = "samsung,s2mu005-rgb";
                color = <LED_COLOR_ID_RGB>;
                function = LED_FUNCTION_INDICATOR;
                linux,default-trigger = "pattern";
            };

Should the compatible string be removed from the multi-led node in the examples
block as well to prevent an unevaluated property error?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260616-s2mu005-pmic-supplement-v1-0-41e84518b711@disroot.org?part=2

^ permalink raw reply

* [PATCH v2] riscv: dts: spacemit: k1-orangepi-rv2: Add cpu scaling
From: Vincent Legoll @ 2026-06-15 20:34 UTC (permalink / raw)
  To: Shuwei Wu, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
	Yixun Lan
  Cc: devicetree, linux-riscv, spacemit, linux-kernel, Vincent Legoll

Enable CPU DVFS on OrangePi RV2

Signed-off-by: Vincent Legoll <vincent.legoll@gmail.com>
---
Tested on a 2GB RAM OrangePi RV2 board, patch applied over
spacemit/for-next + Shuwei Wu's V3 series.

On Void linux (musl libc):

uname -a
Linux opirv2 7.1.0-rc1-00043-gb860bca13be4 #14 SMP PREEMPT Sat Jun 13 
11:02:13 CEST 2026 riscv64 GNU/Linux

awk --version | head -1
GNU Awk 5.3.2, API 4.0

echo userspace > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor

echo 1600000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
time awk 'BEGIN{for(i=0;i<1000000;i++){}}'
real    0m0.300s
user    0m0.299s
sys     0m0.001s

echo 1228800 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
time awk 'BEGIN{for(i=0;i<1000000;i++){}}'
real    0m0.432s
user    0m0.429s
sys     0m0.004s

echo 1000000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
time awk 'BEGIN{for(i=0;i<1000000;i++){}}'
real    0m0.476s
user    0m0.476s
sys     0m0.001s

echo 819000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
time awk 'BEGIN{for(i=0;i<1000000;i++){}}'
real    0m0.582s
user    0m0.581s
sys     0m0.001s

echo 614400 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
time awk 'BEGIN{for(i=0;i<1000000;i++){}}'
real    0m0.778s
user    0m0.773s
sys     0m0.005s

Changes in v2:
- Fix indentation with TABs
Feedback from Yixun Lan (dlan)
- sort includes in alphabetical order
- use b4
- Link to v1: https://patch.msgid.link/f031e13e-d9f5-414a-b90e-a3d5a60b8d05@online.fr
---
 arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts | 35 +++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts b/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts
index 7c49bce427f3..2c2542e02d6a 100644
--- a/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts
+++ b/arch/riscv/boot/dts/spacemit/k1-orangepi-rv2.dts
@@ -7,6 +7,7 @@
 /dts-v1/;
 
 #include "k1.dtsi"
+#include "k1-opp.dtsi"
 #include "k1-pinctrl.dtsi"
 
 / {
@@ -80,6 +81,38 @@ &combo_phy {
 	status = "okay";
 };
 
+&cpu_0 {
+	cpu-supply = <&buck1_0v9>;
+};
+
+&cpu_1 {
+	cpu-supply = <&buck1_0v9>;
+};
+
+&cpu_2 {
+	cpu-supply = <&buck1_0v9>;
+};
+
+&cpu_3 {
+	cpu-supply = <&buck1_0v9>;
+};
+
+&cpu_4 {
+	cpu-supply = <&buck1_0v9>;
+};
+
+&cpu_5 {
+	cpu-supply = <&buck1_0v9>;
+};
+
+&cpu_6 {
+	cpu-supply = <&buck1_0v9>;
+};
+
+&cpu_7 {
+	cpu-supply = <&buck1_0v9>;
+};
+
 &emmc {
 	bus-width = <8>;
 	mmc-hs400-1_8v;
@@ -162,7 +195,7 @@ pmic@41 {
 		dldoin2-supply = <&buck5>;
 
 		regulators {
-			buck1 {
+			buck1_0v9: buck1 {
 				regulator-min-microvolt = <500000>;
 				regulator-max-microvolt = <3450000>;
 				regulator-ramp-delay = <5000>;

---
base-commit: 32cb211965ea0ba0ab8568959f006c7fa10e9f23
change-id: 20260615-k1-cpufreq-orangepi-rv2-5ece985163cd

Best regards,
--  
Vincent Legoll <vincent.legoll@gmail.com>


^ permalink raw reply related

* Re: [PATCH v11 0/3] Add eDP support for RK3576
From: Damon Ding @ 2026-06-15 12:33 UTC (permalink / raw)
  To: robh, krzk+dt, conor+dt, heiko
  Cc: sebastian.reichel, nicolas.frattaroli, alchark, detlev.casanova,
	cristian.ciocaltea, michael.riesch, andy.yan, devicetree,
	linux-arm-kernel, linux-rockchip, linux-kernel
In-Reply-To: <20260605022305.3058853-1-damon.ding@rock-chips.com>

Hi all,

Gentle ping on this patch series.

Best regards,
Damon

On 6/5/2026 10:23 AM, Damon Ding wrote:
> Picked from:
> https://lore.kernel.org/all/20260601065100.1103873-1-damon.ding@rock-chips.com/
> 
> Patch 1-2 are to add missing clock "hclk" for RK3588 eDP nodes.
> Patch 3 is to add the RK3576 eDP node.
> 
> Damon Ding (3):
>    arm64: dts: rockchip: Add missing hclk for RK3588 eDP0
>    arm64: dts: rockchip: Add missing hclk for RK3588 eDP1
>    arm64: dts: rockchip: Add eDP node for RK3576
> 
>   arch/arm64/boot/dts/rockchip/rk3576.dtsi      | 28 +++++++++++++++++++
>   arch/arm64/boot/dts/rockchip/rk3588-base.dtsi |  4 +--
>   .../arm64/boot/dts/rockchip/rk3588-extra.dtsi |  4 +--
>   3 files changed, 32 insertions(+), 4 deletions(-)
> 
> ---
> 
> Changes in v2:
> - Split out separate patches to add the "hclk" clock reference.
> - Split out separate patches to enable the "hclk" clock.
> - Add Reviewed-by tag.
> 
> Changes in v3:
> - Add a patch to expand descriptions for clocks of the eDP node.
> - Add Reviewed-by tag.
> 
> Changes in v4:
> - Modify commit msg.
> 
> Changes in v5:
> - Enforce the correct third clock name on a per-compatible basis.
> - Modify the commit msg simultaneously.
> - Add Acked-by tag.
> 
> Changes in v6:
> - Expand more detail commit msg about using hclk instead of grf clock.
> 
> Changes in v7:
> - List all valid clock names at the top level, and constrain the clock
>    count for each platform with minItems/maxItems in allOf.
> 
> Changes in v8:
> - Fix indentation to 10 for enum in clock-names property.
> 
> Changes in v9:
> - Restore the explicit clock-names for RK3399 and RK3588 eDP dt-bindings.
> 
> Changes in v10:
> - Use automatic cleanup to fix OF node reference leak reported by
>    Sashiko.
> 
> Changes in v11:
> - Pick and rebase DT related patches.
> 


^ permalink raw reply

* [PATCH 3/3] leds: rgb: s2m: use multi-led node of mfd as source node
From: Kaustabh Chakraborty @ 2026-06-15 20:26 UTC (permalink / raw)
  To: André Draszik, Lee Jones, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Pavel Machek
  Cc: linux-kernel, linux-samsung-soc, devicetree, linux-leds,
	Kaustabh Chakraborty
In-Reply-To: <20260616-s2mu005-pmic-supplement-v1-0-41e84518b711@disroot.org>

With the provided compatible string, the driver is able to use the
respective node to initialize following the properties in said node.

However, the compatible node is removed from the devicetree schema.
Follow the same in the driver. The soruce node must be, as per schema, a
sub-node named "multi-led" in the parent MFD node. Initialize the LED
driver into the sub-system by fetching it from the parent node.

Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
 drivers/leds/rgb/leds-s2m-rgb.c | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/drivers/leds/rgb/leds-s2m-rgb.c b/drivers/leds/rgb/leds-s2m-rgb.c
index d239f54eee901..aa7e739213568 100644
--- a/drivers/leds/rgb/leds-s2m-rgb.c
+++ b/drivers/leds/rgb/leds-s2m-rgb.c
@@ -366,6 +366,7 @@ static int s2m_rgb_probe(struct platform_device *pdev)
 	struct sec_pmic_dev *pmic_drvdata = dev_get_drvdata(dev->parent);
 	struct s2m_rgb *rgb;
 	struct led_init_data init_data = {};
+	struct device_node *multi_led_node __free(device_node) = NULL;
 	int ret;
 
 	rgb = devm_kzalloc(dev, sizeof(*rgb), GFP_KERNEL);
@@ -392,7 +393,11 @@ static int s2m_rgb_probe(struct platform_device *pdev)
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to create mutex lock\n");
 
-	init_data.fwnode = of_fwnode_handle(dev->of_node);
+	multi_led_node = of_get_child_by_name(dev->parent->of_node, "multi-led");
+	if (!multi_led_node)
+		return dev_err_probe(dev, -ENODEV, "RGB LED node required but not found\n");
+
+	init_data.fwnode = of_fwnode_handle(multi_led_node);
 	ret = devm_led_classdev_multicolor_register_ext(dev, &rgb->mc, &init_data);
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to create LED device\n");
@@ -406,12 +411,6 @@ static const struct platform_device_id s2m_rgb_id_table[] = {
 };
 MODULE_DEVICE_TABLE(platform, s2m_rgb_id_table);
 
-static const struct of_device_id s2m_rgb_of_match_table[] = {
-	{ .compatible = "samsung,s2mu005-rgb", .data = (void *)S2MU005 },
-	{ /* sentinel */ },
-};
-MODULE_DEVICE_TABLE(of, s2m_rgb_of_match_table);
-
 static struct platform_driver s2m_rgb_driver = {
 	.driver = {
 		.name = "s2m-rgb",

-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/3] dt-bindings: mfd: s2mu005-pmic: drop compatible property for multi-led node
From: Kaustabh Chakraborty @ 2026-06-15 20:26 UTC (permalink / raw)
  To: André Draszik, Lee Jones, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Pavel Machek
  Cc: linux-kernel, linux-samsung-soc, devicetree, linux-leds,
	Kaustabh Chakraborty, Krzysztof Kozlowski
In-Reply-To: <20260616-s2mu005-pmic-supplement-v1-0-41e84518b711@disroot.org>

The multi-led node is very trivial in description and also has no
sub-nodes. A compatible string property for such nodes is not preferred
by upstream. Remove said node from the schema. While at it, also add a
description following its other sibling nodes.

Link: https://lore.kernel.org/all/d2f4cb7d-5c3e-4b9a-86ca-04262cbb9775@kernel.org
Suggested-by: Krzysztof Kozlowski <krzk@kernel.org>
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
 .../devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml       | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
index 8354422e39b1e..f62fe7a05147e 100644
--- a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
+++ b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
@@ -38,17 +38,10 @@ properties:
       Child node describing MUIC device.
 
   multi-led:
-    type: object
+    $ref: /schemas/leds/leds-class-multicolor.yaml#
 
-    allOf:
-      - $ref: /schemas/leds/leds-class-multicolor.yaml#
-
-    properties:
-      compatible:
-        const: samsung,s2mu005-rgb
-
-    required:
-      - compatible
+    description:
+      Child node describing the RGB status LED.
 
     unevaluatedProperties: false
 

-- 
2.53.0


^ permalink raw reply related

* [PATCH 1/3] dt-bindings: mfd: s2mu005-pmic: reorder reg and interrupts properties
From: Kaustabh Chakraborty @ 2026-06-15 20:26 UTC (permalink / raw)
  To: André Draszik, Lee Jones, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Pavel Machek
  Cc: linux-kernel, linux-samsung-soc, devicetree, linux-leds,
	Kaustabh Chakraborty, Krzysztof Kozlowski
In-Reply-To: <20260616-s2mu005-pmic-supplement-v1-0-41e84518b711@disroot.org>

As per convention, and as also reiterated by maintainers [1], the
properties in schema is to be ordered similar to how its done in
devicetree sources; starting from compatible and reg. Re-order the
properties in this schema accordingly.

Link: https://lore.kernel.org/all/0240eb13-6c56-4879-8db7-b990a220a78f@kernel.org [1]
Suggested-by: Krzysztof Kozlowski <krzk@kernel.org>
Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
 .../devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml        | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
index aff68c035b38e..8354422e39b1e 100644
--- a/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
+++ b/Documentation/devicetree/bindings/mfd/samsung,s2mu005-pmic.yaml
@@ -21,14 +21,17 @@ properties:
   compatible:
     const: samsung,s2mu005-pmic
 
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
   flash:
     $ref: /schemas/leds/samsung,s2mu005-flash.yaml#
     description:
       Child node describing flash LEDs.
 
-  interrupts:
-    maxItems: 1
-
   muic:
     $ref: /schemas/extcon/samsung,s2mu005-muic.yaml#
     description:
@@ -49,9 +52,6 @@ properties:
 
     unevaluatedProperties: false
 
-  reg:
-    maxItems: 1
-
 required:
   - compatible
   - reg

-- 
2.53.0


^ permalink raw reply related

* [PATCH 0/3] Additional fixes for "Support for Samsung S2MU005 PMIC and its sub-devices"
From: Kaustabh Chakraborty @ 2026-06-15 20:26 UTC (permalink / raw)
  To: André Draszik, Lee Jones, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Pavel Machek
  Cc: linux-kernel, linux-samsung-soc, devicetree, linux-leds,
	Kaustabh Chakraborty, Krzysztof Kozlowski

This series is a result of discussions from [1] and comments spawned
under it. When this was being discussed, a thread from v6, the v7 was
already posted in the list. The fixes were scheduled for v8, but the
concerned patches were already merged.

And unfortunately, I as per my plans was out-of-office for vacation, so
I couldn't follow up immediately. Sorry for that.

I understand that as the kernel is nearing the v7.1 release, and its
following merge window for v7.2-rc1, the maintainers would be busier
than usual. Apologies again for disrupting any normal procedures. But if
it makes to v7.2-rc1, along with the original series, that would be
nice.

This series has two distinct fixes: (a) to reorder the properties, and
(b) to remove the compatible node. The latter needs driver intervention,
thus this is also posted to the LED maintainers (Lee, ... et al?)

So, the following patches can independently go through different trees:
(a) patch #1, goes through devicetree tree
(b) patch #2 and #3, go through leds tree

[1] https://lore.kernel.org/all/20260515-justly-recite-6028f4bfb24a@spud

Signed-off-by: Kaustabh Chakraborty <kauschluss@disroot.org>
---
Kaustabh Chakraborty (3):
      dt-bindings: mfd: s2mu005-pmic: reorder reg and interrupts properties
      dt-bindings: mfd: s2mu005-pmic: drop compatible property for multi-led node
      leds: rgb: s2m: use multi-led node of mfd as source node

 .../bindings/mfd/samsung,s2mu005-pmic.yaml         | 25 ++++++++--------------
 drivers/leds/rgb/leds-s2m-rgb.c                    | 13 ++++++-----
 2 files changed, 15 insertions(+), 23 deletions(-)
---
base-commit: 8d6dbbbe3ba62de0a63e962ee004afb848c8e3ac
change-id: 20260616-s2mu005-pmic-supplement-82dcef999c8a

Best regards,
--  
Kaustabh Chakraborty <kauschluss@disroot.org>


^ permalink raw reply

* Re: [PATCH v2] hw_random: timeriomem-rng: add configurable read width and data mask
From: sashiko-bot @ 2026-06-15 20:26 UTC (permalink / raw)
  To: Jad Keskes; +Cc: robh, devicetree, conor+dt
In-Reply-To: <20260615201339.1264676-1-inasj268@gmail.com>

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

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but an improper teardown sequence allows a concurrent reader to restart the timer after it was cancelled, leading to a Use-After-Free and potential Deadlock.
--

commit f3fa66cce2c0ad3af71293c86473cf060b400645
Author: Jad Keskes <inasj268@gmail.com>

hw_random: timeriomem-rng: add configurable read width and data mask

This commit adds support for reading 8, 16, or 32 bits from the hardware
random number generator. It applies an optional mask to the data and
configures these properties via device tree or platform data.

> diff --git a/drivers/char/hw_random/timeriomem-rng.c b/drivers/char/hw_random/timeriomem-rng.c
> index e61f063932090..4557326618c90 100644
> --- a/drivers/char/hw_random/timeriomem-rng.c
> +++ b/drivers/char/hw_random/timeriomem-rng.c

[ ... ]

> @@ -71,11 +76,28 @@ static int timeriomem_rng_read(struct hwrng *hwrng, void *data,
>  			usleep_range(period_us,
>  					period_us + max(1, period_us / 100));
>  
> -		*(u32 *)data = readl(priv->io_base);
> -		retval += sizeof(u32);
> -		data += sizeof(u32);
> -		max -= sizeof(u32);
> -	} while (wait && max > sizeof(u32));
> +		switch (priv->width) {
> +		case 8: {
> +			u8 val = readb(priv->io_base) & priv->mask;
> +			*(u8 *)data = val;
> +			break;
> +		}

[ ... ]

> +		}
> +
> +		retval += chunk;
> +		data += chunk;
> +		max -= chunk;
> +	} while (wait && max > chunk);

[Severity: High]
This isn't a bug introduced by this patch, but does the current driver
teardown sequence allow a concurrent reader to cause a use-after-free
or deadlock?

Because the driver uses devm_hwrng_register(), the hwrng is unregistered by
devres after timeriomem_rng_remove() completes.

If timeriomem_rng_remove() calls hrtimer_cancel(&priv->timer) while a reader
is inside timeriomem_rng_read() (for example, sleeping between reads), the
devres cleanup will subsequently call hwrng_unregister() which blocks
waiting for the active reader to finish.

However, the reader will eventually finish its loop and unconditionally run
the following at the end of timeriomem_rng_read():

	hrtimer_forward_now(&priv->timer, priv->period);
	hrtimer_restart(&priv->timer);

Can this sequence restart the timer after it was cancelled by the remove
function, allowing the timer to eventually fire and dereference the freed
priv pointer?

Additionally, if a reader is waiting in wait_for_completion(&priv->completion)
when the timer is cancelled, could this prevent the timer from firing,
leaving the reader hanging in TASK_UNINTERRUPTIBLE and causing the
subsequent hwrng_unregister() to deadlock?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615201339.1264676-1-inasj268@gmail.com?part=1

^ permalink raw reply

* [PATCH v2] hw_random: timeriomem-rng: add configurable read width and data mask
From: Jad Keskes @ 2026-06-15 20:13 UTC (permalink / raw)
  To: Olivia Mackall, Herbert Xu
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Alexander Clouter,
	linux-crypto, devicetree, linux-kernel, Jad Keskes

The TODO for supporting read sizes other than 32 bits and masking has
been sitting in this driver since 2009.  Implement it.

Add width (8, 16, or 32 bits) and mask properties to the platform data
and device tree bindings.  The read loop dispatches on width using
readb/readw/readl so a configured 8-bit access doesn't trigger a bus
error on hardware that rejects 32-bit reads to that address.  The mask
is ANDed with the value before storing.

These are platform properties, not runtime policy -- width depends on
SoC integration, mask reflects which output bits carry entropy.

The alignment check in probe is updated to verify the resource is
aligned to the configured width instead of hardcoding 4-byte alignment.

Signed-off-by: Jad Keskes <inasj268@gmail.com>
---

v2:
- Remove old timeriomem_rng.yaml to avoid dt_binding_check conflict
- Use IS_ALIGNED() instead of modulo for 32-bit PAE safety


 .../bindings/rng/timeriomem-rng.yaml          | 76 ++++++++++++++++++
 .../bindings/rng/timeriomem_rng.yaml          | 48 ------------
 drivers/char/hw_random/timeriomem-rng.c       | 78 +++++++++++++++----
 include/linux/timeriomem-rng.h                | 12 +++
 4 files changed, 153 insertions(+), 61 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/rng/timeriomem-rng.yaml
 delete mode 100644 Documentation/devicetree/bindings/rng/timeriomem_rng.yaml

diff --git a/Documentation/devicetree/bindings/rng/timeriomem-rng.yaml b/Documentation/devicetree/bindings/rng/timeriomem-rng.yaml
new file mode 100644
index 000000000000..0d8460e9f916
--- /dev/null
+++ b/Documentation/devicetree/bindings/rng/timeriomem-rng.yaml
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/rng/timeriomem-rng.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Timer IOMEM Hardware Random Number Generator
+
+description: |
+  This binding covers platforms that have a single IO memory address which
+  provides periodic random data.  The driver reads from the address at a
+  fixed interval, returning a configurable-width value masked to the desired
+  bits.
+
+maintainers:
+  - Alexander Clouter <alex@digriz.org.uk>
+
+properties:
+  compatible:
+    enum:
+      - timeriomem_rng
+
+  reg:
+    maxItems: 1
+
+  period:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Interval in microseconds between reads.  New random data is expected to
+      be available at this rate.
+
+  quality:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 0
+    description:
+      Estimated entropy per 1024 bits of data, in the same scale as the
+      kernel's hwrng core (0-1024).
+
+  width:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 32
+    enum: [8, 16, 32]
+    description:
+      Access width in bits.  Determines whether the read is performed as
+      an 8-bit, 16-bit, or 32-bit bus access.
+
+  mask:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 0xFFFFFFFF
+    description:
+      Mask applied to the value read from the register.  Bits set to 0 in
+      the mask are cleared in the output data.  Default (no mask) passes
+      all bits through.
+
+required:
+  - compatible
+  - reg
+  - period
+
+additionalProperties: false
+
+examples:
+  - |
+    rng@f0001000 {
+        compatible = "timeriomem_rng";
+        reg = <0xf0001000 0x4>;
+        period = <100000>;
+    };
+
+    rng@f0002000 {
+        compatible = "timeriomem_rng";
+        reg = <0xf0002000 0x1>;
+        period = <50000>;
+        width = <8>;
+        mask = <0xFF>;
+    };
diff --git a/Documentation/devicetree/bindings/rng/timeriomem_rng.yaml b/Documentation/devicetree/bindings/rng/timeriomem_rng.yaml
deleted file mode 100644
index 4754174e9849..000000000000
--- a/Documentation/devicetree/bindings/rng/timeriomem_rng.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-only
-%YAML 1.2
----
-$id: http://devicetree.org/schemas/rng/timeriomem_rng.yaml#
-$schema: http://devicetree.org/meta-schemas/core.yaml#
-
-title: TimerIO Random Number Generator
-
-maintainers:
-  - Krzysztof Kozlowski <krzk@kernel.org>
-
-properties:
-  compatible:
-    const: timeriomem_rng
-
-  period:
-    $ref: /schemas/types.yaml#/definitions/uint32
-    description: wait time in microseconds to use between samples
-
-  quality:
-    $ref: /schemas/types.yaml#/definitions/uint32
-    default: 0
-    description:
-      Estimated number of bits of true entropy per 1024 bits read from the rng.
-      Defaults to zero which causes the kernel's default quality to be used
-      instead.  Note that the default quality is usually zero which disables
-      using this rng to automatically fill the kernel's entropy pool.
-
-  reg:
-    maxItems: 1
-    description:
-      Base address to sample from. Currently 'reg' must be at least four bytes
-      wide and 32-bit aligned.
-
-required:
-  - compatible
-  - period
-  - reg
-
-additionalProperties: false
-
-examples:
-  - |
-    rng@44 {
-        compatible = "timeriomem_rng";
-        reg = <0x44 0x04>;
-        period = <1000000>;
-    };
diff --git a/drivers/char/hw_random/timeriomem-rng.c b/drivers/char/hw_random/timeriomem-rng.c
index e61f06393209..4557326618c9 100644
--- a/drivers/char/hw_random/timeriomem-rng.c
+++ b/drivers/char/hw_random/timeriomem-rng.c
@@ -14,7 +14,9 @@
  *   has to do is provide the address and 'wait time' that new data becomes
  *   available.
  *
- * TODO: add support for reading sizes other than 32bits and masking
+ * The read width (8, 16, or 32 bits) and an optional data mask can be
+ * configured through platform data or device tree properties.  Default is
+ * 32-bit reads with no mask.
  */
 
 #include <linux/completion.h>
@@ -34,6 +36,8 @@ struct timeriomem_rng_private {
 	void __iomem		*io_base;
 	ktime_t			period;
 	unsigned int		present:1;
+	unsigned int		width;
+	u32			mask;
 
 	struct hrtimer		timer;
 	struct completion	completion;
@@ -48,6 +52,7 @@ static int timeriomem_rng_read(struct hwrng *hwrng, void *data,
 		container_of(hwrng, struct timeriomem_rng_private, rng_ops);
 	int retval = 0;
 	int period_us = ktime_to_us(priv->period);
+	int chunk = priv->width / 8;
 
 	/*
 	 * There may not have been enough time for new data to be generated
@@ -71,11 +76,28 @@ static int timeriomem_rng_read(struct hwrng *hwrng, void *data,
 			usleep_range(period_us,
 					period_us + max(1, period_us / 100));
 
-		*(u32 *)data = readl(priv->io_base);
-		retval += sizeof(u32);
-		data += sizeof(u32);
-		max -= sizeof(u32);
-	} while (wait && max > sizeof(u32));
+		switch (priv->width) {
+		case 8: {
+			u8 val = readb(priv->io_base) & priv->mask;
+			*(u8 *)data = val;
+			break;
+		}
+		case 16: {
+			u16 val = readw(priv->io_base) & priv->mask;
+			*(u16 *)data = val;
+			break;
+		}
+		case 32: {
+			u32 val = readl(priv->io_base) & priv->mask;
+			*(u32 *)data = val;
+			break;
+		}
+		}
+
+		retval += chunk;
+		data += chunk;
+		max -= chunk;
+	} while (wait && max > chunk);
 
 	/*
 	 * Block any new callers until the RNG has had time to generate new
@@ -125,11 +147,8 @@ static int timeriomem_rng_probe(struct platform_device *pdev)
 	if (IS_ERR(priv->io_base))
 		return PTR_ERR(priv->io_base);
 
-	if (res->start % 4 != 0 || resource_size(res) < 4) {
-		dev_err(&pdev->dev,
-			"address must be at least four bytes wide and 32-bit aligned\n");
-		return -EINVAL;
-	}
+	priv->width = 32;
+	priv->mask = 0xFFFFFFFF;
 
 	if (pdev->dev.of_node) {
 		int i;
@@ -145,9 +164,42 @@ static int timeriomem_rng_probe(struct platform_device *pdev)
 		if (!of_property_read_u32(pdev->dev.of_node,
 						"quality", &i))
 			priv->rng_ops.quality = i;
+
+		of_property_read_u32(pdev->dev.of_node,
+				     "width", &priv->width);
+		of_property_read_u32(pdev->dev.of_node,
+				     "mask", &priv->mask);
 	} else {
 		period = pdata->period;
 		priv->rng_ops.quality = pdata->quality;
+
+		if (pdata->width_set)
+			priv->width = pdata->width;
+		if (pdata->mask_set)
+			priv->mask = pdata->mask;
+	}
+
+	if (priv->width == 0)
+		priv->width = 32;
+
+	switch (priv->width) {
+	case 8:
+	case 16:
+	case 32:
+		break;
+	default:
+		dev_err(&pdev->dev, "invalid width %u, must be 8, 16, or 32\n",
+			priv->width);
+		return -EINVAL;
+	}
+
+	if (!IS_ALIGNED(res->start, priv->width / 8) ||
+	    resource_size(res) < priv->width / 8) {
+		dev_err(&pdev->dev,
+			"address must be at least %u-bit aligned (%u byte%s)\n",
+			priv->width, priv->width / 8,
+			priv->width / 8 > 1 ? "s" : "");
+		return -EINVAL;
 	}
 
 	priv->period = us_to_ktime(period);
@@ -167,8 +219,8 @@ static int timeriomem_rng_probe(struct platform_device *pdev)
 		return err;
 	}
 
-	dev_info(&pdev->dev, "32bits from 0x%p @ %dus\n",
-			priv->io_base, period);
+	dev_info(&pdev->dev, "%ubit from %p @ %dus\n",
+		 priv->width, priv->io_base, period);
 
 	return 0;
 }
diff --git a/include/linux/timeriomem-rng.h b/include/linux/timeriomem-rng.h
index 672df7fbf6c1..b4202ad2f507 100644
--- a/include/linux/timeriomem-rng.h
+++ b/include/linux/timeriomem-rng.h
@@ -16,6 +16,18 @@ struct timeriomem_rng_data {
 
 	/* bits of entropy per 1024 bits read */
 	unsigned int		quality;
+
+	/* read width (8, 16, or 32), 0 means 32 */
+	unsigned int		width;
+
+	/* set to true if width is explicitly provided */
+	bool			width_set;
+
+	/* mask applied to raw read value */
+	u32			mask;
+
+	/* set to true if mask is explicitly provided */
+	bool			mask_set;
 };
 
 #endif /* _LINUX_TIMERIOMEM_RNG_H */
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH 3/3] backlight: lp8864: Convert from LED to backlight class driver
From: Andrew Davis @ 2026-06-15 19:51 UTC (permalink / raw)
  To: A. Sverdlin, linux-leds
  Cc: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Helge Deller, dri-devel,
	devicetree, linux-kernel, linux-fbdev
In-Reply-To: <20260615120353.3409035-4-alexander.sverdlin@siemens.com>

On 6/15/26 7:03 AM, A. Sverdlin wrote:
> From: Alexander Sverdlin <alexander.sverdlin@siemens.com>
> 
> Move the TI LP8864/LP8866 driver from drivers/leds/ to
> drivers/video/backlight/

Why move it? You can register a backlight device from any directory.

> and convert it to register a backlight class
> device as its primary interface.
> 

What do you mean by "primary"? You should be able to register with
both frameworks and have the driver interop between as needed.

> The motivation is a use case on a hot-pluggable segment of an I2C bus.
> The generic led-backlight driver (drivers/video/backlight/led_bl.c) is a
> platform driver and as such inherently non-hotpluggable.

That isn't strictly true, there is platform_device_{del,unregister}(), so
whatever your mechanism for removing the I2C device would be, the same
could be done to the led_bl device before then removing the I2C device.

We don't want to have to move every LED driver that could possibly
be used as a backlight to the backlight framework, the led_bl.c
handles adapting LED->backlight as needed. So what you really need
here is to de-couple led_bl.c from DT so it can better handle dynamic
add/remove. Then this LED driver simply could register a "led-backlight"
platform driver to handle the backlight interface, and remove the
backlight device when it itself (the LED device) is removed.

Andrew

  It cannot react
> to dynamic appearance/disappearance of the underlying I2C device. By
> making the LP8864 driver directly register a backlight class device, it
> becomes a native I2C driver that properly supports hot-plug/unplug
> events on the I2C bus.
> 
> Key changes:
> - Register a backlight class device using
>    devm_backlight_device_register() as the primary interface
> - Implement backlight_ops (update_status, get_brightness)
> - The hardware 16-bit brightness register (0x0000-0xFFFF) is directly
>    exposed as the backlight brightness range
> - Support DT properties "default-brightness" and "max-brightness"
>    from the backlight common binding
> - Include BL_CORE_SUSPENDRESUME for proper power management integration
> - Preserve backward-compatible LED class device registration: if the
>    "led" child node is present in the DT, an LED class device is also
>    registered (same as the original driver behavior)
> - Preserve the CONFIG_LEDS_LP8864 Kconfig symbol name so that existing
>    kernel configurations are not affected
> - Update MAINTAINERS to reflect the new file location
> 
> This will be noticeable for applications which already used the LP8864
> as a backend for the generic led-backlight platform driver, as a
> backlight device will now appear directly in addition to the LED class
> device. However, no in-tree device-trees reference this driver, so
> there is no mainline impact.
> 
> Signed-off-by: Alexander Sverdlin <alexander.sverdlin@siemens.com>
> ---
>   MAINTAINERS                                   |   2 +-
>   drivers/leds/Kconfig                          |  12 --
>   drivers/leds/Makefile                         |   1 -
>   drivers/video/backlight/Kconfig               |  15 +++
>   drivers/video/backlight/Makefile              |   1 +
>   .../backlight/lp8864_bl.c}                    | 111 ++++++++++++++----
>   6 files changed, 106 insertions(+), 36 deletions(-)
>   rename drivers/{leds/leds-lp8864.c => video/backlight/lp8864_bl.c} (70%)
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index dbd4552236e64..250e8b1ed4bb5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -26481,7 +26481,7 @@ M:	Alexander Sverdlin <alexander.sverdlin@siemens.com>
>   L:	linux-leds@vger.kernel.org
>   S:	Maintained
>   F:	Documentation/devicetree/bindings/leds/backlight/ti,lp8864.yaml
> -F:	drivers/leds/leds-lp8864.c
> +F:	drivers/video/backlight/lp8864_bl.c
>   
>   TEXAS INSTRUMENTS' SYSTEM CONTROL INTERFACE (TISCI) PROTOCOL DRIVER
>   M:	Nishanth Menon <nm@ti.com>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index f4a0a3c8c8705..990cb9ef18c1e 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -529,18 +529,6 @@ config LEDS_LP8860
>   	  on the LP8860 4 channel LED driver using the I2C communication
>   	  bus.
>   
> -config LEDS_LP8864
> -	tristate "LED support for the TI LP8864/LP8866 4/6 channel LED drivers"
> -	depends on LEDS_CLASS && I2C && OF
> -	select REGMAP_I2C
> -	help
> -	  If you say yes here you get support for the TI LP8864-Q1,
> -	  LP8864S-Q1, LP8866-Q1, LP8866S-Q1 4/6 channel LED backlight
> -	  drivers with I2C interface.
> -
> -	  To compile this driver as a module, choose M here: the
> -	  module will be called leds-lp8864.
> -
>   config LEDS_CLEVO_MAIL
>   	tristate "Mail LED on Clevo notebook"
>   	depends on LEDS_CLASS && BROKEN
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 8fdb45d5b4393..5e624a48aa2a5 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -59,7 +59,6 @@ obj-$(CONFIG_LEDS_LP55XX_COMMON)	+= leds-lp55xx-common.o
>   obj-$(CONFIG_LEDS_LP8501)		+= leds-lp8501.o
>   obj-$(CONFIG_LEDS_LP8788)		+= leds-lp8788.o
>   obj-$(CONFIG_LEDS_LP8860)		+= leds-lp8860.o
> -obj-$(CONFIG_LEDS_LP8864)		+= leds-lp8864.o
>   obj-$(CONFIG_LEDS_LT3593)		+= leds-lt3593.o
>   obj-$(CONFIG_LEDS_MAX5970)		+= leds-max5970.o
>   obj-$(CONFIG_LEDS_MAX77650)		+= leds-max77650.o
> diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
> index a7a3fbaf7c29e..82ecd7e46236d 100644
> --- a/drivers/video/backlight/Kconfig
> +++ b/drivers/video/backlight/Kconfig
> @@ -514,6 +514,21 @@ config BACKLIGHT_LED
>   	  If you have a LCD backlight adjustable by LED class driver, say Y
>   	  to enable this driver.
>   
> +config LEDS_LP8864
> +	tristate "Backlight driver for TI LP8864/LP8866 4/6 channel LED drivers"
> +	depends on I2C && OF
> +	select REGMAP_I2C
> +	select NEW_LEDS
> +	select LEDS_CLASS
> +	help
> +	  If you say yes here you get support for the TI LP8864-Q1,
> +	  LP8864S-Q1, LP8866-Q1, LP8866S-Q1 4/6 channel LED backlight
> +	  drivers with I2C interface. The driver registers a backlight
> +	  class device and optionally an LED class device.
> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called lp8864_bl.
> +
>   endif # BACKLIGHT_CLASS_DEVICE
>   
>   endmenu
> diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
> index 794820a98ed49..6a7287d01d81b 100644
> --- a/drivers/video/backlight/Makefile
> +++ b/drivers/video/backlight/Makefile
> @@ -62,3 +62,4 @@ obj-$(CONFIG_BACKLIGHT_WM831X)		+= wm831x_bl.o
>   obj-$(CONFIG_BACKLIGHT_ARCXCNN) 	+= arcxcnn_bl.o
>   obj-$(CONFIG_BACKLIGHT_RAVE_SP)		+= rave-sp-backlight.o
>   obj-$(CONFIG_BACKLIGHT_LED)		+= led_bl.o
> +obj-$(CONFIG_LEDS_LP8864)		+= lp8864_bl.o
> diff --git a/drivers/leds/leds-lp8864.c b/drivers/video/backlight/lp8864_bl.c
> similarity index 70%
> rename from drivers/leds/leds-lp8864.c
> rename to drivers/video/backlight/lp8864_bl.c
> index d05211b970c94..67b28f7daedd2 100644
> --- a/drivers/leds/leds-lp8864.c
> +++ b/drivers/video/backlight/lp8864_bl.c
> @@ -1,12 +1,13 @@
>   // SPDX-License-Identifier: GPL-2.0-only
>   /*
> - * TI LP8864/LP8866 4/6 Channel LED Driver
> + * TI LP8864/LP8866 4/6 Channel LED Backlight Driver
>    *
> - * Copyright (C) 2024 Siemens AG
> + * Copyright (C) 2024-2026 Siemens AG
>    *
>    * Based on LP8860 driver by Dan Murphy <dmurphy@ti.com>
>    */
>   
> +#include <linux/backlight.h>
>   #include <linux/gpio/consumer.h>
>   #include <linux/i2c.h>
>   #include <linux/init.h>
> @@ -27,6 +28,8 @@
>   #define LP8864_LED_STATUS		0x12
>   #define   LP8864_LED_STATUS_WR_MASK	GENMASK(14, 9)	/* Writeable bits in the LED_STATUS reg */
>   
> +#define LP8864_MAX_BRIGHTNESS		0xffff
> +
>   /* Textual meaning for status bits, starting from bit 1 */
>   static const char *const lp8864_supply_status_msg[] = {
>   	"Vin under-voltage fault",
> @@ -71,13 +74,15 @@ static const char *const lp8864_led_status_msg[] = {
>   /**
>    * struct lp8864
>    * @client: Pointer to the I2C client
> - * @led_dev: led class device pointer
> + * @led_dev: optional led class device pointer
> + * @bl: backlight device pointer
>    * @regmap: Devices register map
>    * @led_status_mask: Helps to report LED fault only once
>    */
>   struct lp8864 {
>   	struct i2c_client *client;
> -	struct led_classdev led_dev;
> +	struct led_classdev *led_dev;
> +	struct backlight_device *bl;
>   	struct regmap *regmap;
>   	u16 led_status_mask;
>   };
> @@ -157,28 +162,59 @@ static int lp8864_fault_check(struct lp8864 *priv)
>   	return ret;
>   }
>   
> -static int lp8864_brightness_set(struct led_classdev *led_cdev,
> -				 enum led_brightness brt_val)
> +static int lp8864_brightness_set(struct lp8864 *priv, unsigned int brightness)
>   {
> -	struct lp8864 *priv = container_of(led_cdev, struct lp8864, led_dev);
> -	/* Scale 0..LED_FULL into 16-bit HW brightness */
> -	unsigned int val = brt_val * 0xffff / LED_FULL;
>   	int ret;
>   
>   	ret = lp8864_fault_check(priv);
>   	if (ret)
>   		return ret;
>   
> -	ret = regmap_write(priv->regmap, LP8864_BRT_CONTROL, val);
> +	ret = regmap_write(priv->regmap, LP8864_BRT_CONTROL, brightness);
>   	if (ret)
>   		dev_err(&priv->client->dev, "Failed to write brightness value\n");
>   
>   	return ret;
>   }
>   
> -static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev)
> +static int lp8864_backlight_update_status(struct backlight_device *bl)
> +{
> +	return lp8864_brightness_set(bl_get_data(bl), backlight_get_brightness(bl));
> +}
> +
> +static int lp8864_backlight_get_brightness(struct backlight_device *bl)
>   {
> -	struct lp8864 *priv = container_of(led_cdev, struct lp8864, led_dev);
> +	struct lp8864 *priv = bl_get_data(bl);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(priv->regmap, LP8864_BRT_CONTROL, &val);
> +	if (ret) {
> +		dev_err(&priv->client->dev, "Failed to read brightness value\n");
> +		return ret;
> +	}
> +
> +	return val;
> +}
> +
> +static const struct backlight_ops lp8864_backlight_ops = {
> +	.options = BL_CORE_SUSPENDRESUME,
> +	.update_status = lp8864_backlight_update_status,
> +	.get_brightness = lp8864_backlight_get_brightness,
> +};
> +
> +static int lp8864_led_brightness_set(struct led_classdev *led_cdev,
> +				     enum led_brightness brt_val)
> +{
> +	struct lp8864 *priv = dev_get_drvdata(led_cdev->dev->parent);
> +
> +	/* Scale 0..LED_FULL into 16-bit HW brightness */
> +	return lp8864_brightness_set(priv, brt_val * 0xffff / LED_FULL);
> +}
> +
> +static enum led_brightness lp8864_led_brightness_get(struct led_classdev *led_cdev)
> +{
> +	struct lp8864 *priv = dev_get_drvdata(led_cdev->dev->parent);
>   	unsigned int val;
>   	int ret;
>   
> @@ -212,18 +248,15 @@ static int lp8864_probe(struct i2c_client *client)
>   	struct device_node *np = dev_of_node(&client->dev);
>   	struct device_node *child_node;
>   	struct led_init_data init_data = {};
> +	struct backlight_device *bl;
> +	struct backlight_properties props;
>   	struct gpio_desc *enable_gpio;
> +	u32 val;
>   
>   	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
>   	if (!priv)
>   		return -ENOMEM;
>   
> -	child_node = of_get_next_available_child(np, NULL);
> -	if (!child_node) {
> -		dev_err(&client->dev, "No LED function defined\n");
> -		return -EINVAL;
> -	}
> -
>   	ret = devm_regulator_get_enable_optional(&client->dev, "vled");
>   	if (ret && ret != -ENODEV)
>   		return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n");
> @@ -238,8 +271,7 @@ static int lp8864_probe(struct i2c_client *client)
>   		return ret;
>   
>   	priv->client = client;
> -	priv->led_dev.brightness_set_blocking = lp8864_brightness_set;
> -	priv->led_dev.brightness_get = lp8864_brightness_get;
> +	i2c_set_clientdata(client, priv);
>   
>   	priv->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config);
>   	if (IS_ERR(priv->regmap))
> @@ -258,11 +290,46 @@ static int lp8864_probe(struct i2c_client *client)
>   	if (ret)
>   		return ret;
>   
> +	/* Register backlight class device */
> +	memset(&props, 0, sizeof(props));
> +	props.type = BACKLIGHT_RAW;
> +	props.max_brightness = LP8864_MAX_BRIGHTNESS;
> +	props.brightness = LP8864_MAX_BRIGHTNESS;
> +	props.scale = BACKLIGHT_SCALE_LINEAR;
> +
> +	if (!device_property_read_u32(&client->dev, "max-brightness", &val))
> +		props.max_brightness = val;
> +
> +	if (!device_property_read_u32(&client->dev, "default-brightness", &val))
> +		props.brightness = val;
> +
> +	bl = devm_backlight_device_register(&client->dev, "lp8864-backlight",
> +					    &client->dev, priv,
> +					    &lp8864_backlight_ops, &props);
> +	if (IS_ERR(bl))
> +		return dev_err_probe(&client->dev, PTR_ERR(bl),
> +				     "Failed to register backlight device\n");
> +
> +	priv->bl = bl;
> +	backlight_update_status(bl);
> +
> +	/* Register LED class device if "led" child node is present */
> +	child_node = of_get_available_child_by_name(np, "led");
> +	if (!child_node)
> +		return 0;
> +
> +	priv->led_dev = devm_kzalloc(&client->dev, sizeof(*priv->led_dev), GFP_KERNEL);
> +	if (!priv->led_dev)
> +		return -ENOMEM;
> +
> +	priv->led_dev->brightness_set_blocking = lp8864_led_brightness_set;
> +	priv->led_dev->brightness_get = lp8864_led_brightness_get;
> +
>   	init_data.fwnode = of_fwnode_handle(child_node);
>   	init_data.devicename = "lp8864";
>   	init_data.default_label = ":display_cluster";
>   
> -	ret = devm_led_classdev_register_ext(&client->dev, &priv->led_dev, &init_data);
> +	ret = devm_led_classdev_register_ext(&client->dev, priv->led_dev, &init_data);
>   	if (ret)
>   		dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret));
>   
> @@ -291,6 +358,6 @@ static struct i2c_driver lp8864_driver = {
>   };
>   module_i2c_driver(lp8864_driver);
>   
> -MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver");
> +MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED Backlight driver");
>   MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@siemens.com>");
>   MODULE_LICENSE("GPL");


^ permalink raw reply

* Re: [PATCH v1 1/2] dt-bindings: spi: snps,dw-apb-ssi: Add support for snps,dwc-ssi-2.00a
From: Changhuang Liang @ 2026-06-15 11:15 UTC (permalink / raw)
  To: Conor Dooley
  Cc: Conor Dooley, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Mark Brown, linux-spi@vger.kernel.org,
	linux-kernel@vger.kernel.org, devicetree@vger.kernel.org
In-Reply-To: <20260615-regretful-unviable-3a6a65f48d9b@wendy>

Hi, Conor

> On Mon, Jun 15, 2026 at 10:49:07AM +0000, Changhuang Liang wrote:
> > Hi, Conor
> >
> > > On Sat, Jun 13, 2026 at 03:38:46AM +0000, Changhuang Liang wrote:
> > > > Hi, Conor
> > > >
> > > > Thanks for the review.
> > > >
> > > > > On Fri, Jun 12, 2026 at 05:58:55AM -0700, Changhuang Liang wrote:
> > > > > > Add a new compatible string "snps,dwc-ssi-2.00a" for the
> > > > > > Synopsys DesignWare SSI controller version 2.00a.
> > > > >
> > > > > Two things. Firstly, driver patch suggests a fallback to 1.01a
> > > > > is possible. Why haven't you added one?
> > > >
> > > > Will support fallback.
> > > >
> > > > > Secondly, I am going to expect that when your starfive user for
> > > > > this appears in my inbox that it has a device-specific
> > > > > compatible, so you may as well add that now.
> > > >
> > > > Our standard SPI control is completely based on the Synopsys
> > > > DesignWare
> > > SSI controller version 2.00a.
> > > > However, for the controller that accesses the flash, a set of
> > > > modifications has been made based on the Synopsys DesignWare SSI
> > > controller version 2.00a, and the driver part also requires specific
> adaptation.
> > > >
> > > > Therefore, on the JHB100 SoC, the initial device tree source I
> > > > have planned
> > > looks like this:
> > > >
> > > > spi0: spi@17f20000 {
> > > >       compatible = "snps,dwc-ssi-2.00a";
> > > >       reg = <0x0 0x17f20000 0x0 0x10000>; };
> > > >
> > > > sfc0: spi@18000000 {
> > > >       compatible = "starfive,jhb100-spi";
> > > >       reg = <0x0 0x18000000 0x0 0x10000>; };
> > > >
> > > > Thus, I plan to add a new "starfive,jhb100-spi" compatible string
> > > > for the
> > > controller that accesses the flash.
> > > > For now, I intend to keep these as two separate series.
> > >
> > > No, if you have two integrations of the designware IP with different
> > > programming models on the jhb100 I expect to see two different
> > > soc-specific compatibles used. I don't want to see bare synopsys
> > > compatibles on the platforms I am looking after.
> >
> > Okay, I will change it to the following:
> >
> > spi0: spi@17f20000 {
> >       compatible = " starfive,jhb100-spi";
> 
> This one is compatible with "snps,dwc-ssi-2.00a" and "snps,dwc-ssi-1.00a", so
> it should probably have those as fallbacks. The latter for sure, the former if
> you know there's some non-discoverable differences between 2.00a and
> 1.00a hardware.
> 

'm not sure about the hardware differences between 2.00a and 1.00a at the moment either.
I'll use the following method first:

compatible = "starfive,jhb100-spi", "snps,dwc-ssi-2.00a", "snps,dwc-ssi-1.01a"

> Cheers,
> Conor.
> 
> >       reg = <0x0 0x17f20000 0x0 0x10000>; };
> >
> > sfc0: spi@18000000 {
> >       compatible = "starfive,jhb100-sfc";
> >       reg = <0x0 0x18000000 0x0 0x10000>; };
> >
> > Best Regards,
> > Changhuang
> >

Best Regards,
Changhuang

^ permalink raw reply

* Re: [PATCH] hw_random: timeriomem-rng: add configurable read width and data mask
From: sashiko-bot @ 2026-06-15 19:25 UTC (permalink / raw)
  To: Jad Keskes; +Cc: robh, conor+dt, devicetree
In-Reply-To: <20260615170922.1132642-1-inasj268@gmail.com>

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

New issues:
- [Medium] The patch introduces a new device tree binding schema file but leaves the old schema intact, causing validation conflicts.
- [Low] Using the modulo operator `%` on a `resource_size_t` variable causes an undefined reference link error on 32-bit platforms.

Pre-existing issues:
- [High] The driver cancels the timer in `remove()` but relies on `devm` cleanup to unregister the hwrng device, creating a teardown race condition that leads to use-after-free or deadlock.
--

commit 57d226c081fc616a1deb344ec82199ee7e9c1059
Author: Jad Keskes <inasj268@gmail.com>

hw_random: timeriomem-rng: add configurable read width and data mask

The TODO for supporting read sizes other than 32 bits and masking has
been sitting in this driver since 2009.  Implement it.

> diff --git a/Documentation/devicetree/bindings/rng/timeriomem-rng.yaml b/Documentation/devicetree/bindings/rng/timeriomem-rng.yaml
> new file mode 100644
> index 0000000000000..0d8460e9f9163
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/rng/timeriomem-rng.yaml
> @@ -0,0 +1,76 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/rng/timeriomem-rng.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Timer IOMEM Hardware Random Number Generator

[Severity: Medium]
Will this new device tree binding schema file conflict with the existing
schema?

The patch introduces this new schema but does not appear to replace or remove
the existing timeriomem_rng.yaml file. Since both schemas match the same
compatible = "timeriomem_rng" string and the old schema specifies
additionalProperties: false, any device tree node including the newly added
width or mask properties might pass this new schema but fail the old schema's
validation, which would break dt_binding_check.

> diff --git a/drivers/char/hw_random/timeriomem-rng.c b/drivers/char/hw_random/timeriomem-rng.c
> index e61f063932090..9308988521473 100644
> --- a/drivers/char/hw_random/timeriomem-rng.c
> +++ b/drivers/char/hw_random/timeriomem-rng.c
[ ... ]
> @@ -71,11 +76,28 @@ static int timeriomem_rng_read(struct hwrng *hwrng, void *data,
>  			usleep_range(period_us,
>  					period_us + max(1, period_us / 100));
>  
> -		*(u32 *)data = readl(priv->io_base);
> -		retval += sizeof(u32);
> -		data += sizeof(u32);
> -		max -= sizeof(u32);
> -	} while (wait && max > sizeof(u32));
> +		switch (priv->width) {
> +		case 8: {
> +			u8 val = readb(priv->io_base) & priv->mask;
> +			*(u8 *)data = val;
> +			break;
> +		}
> +		case 16: {
> +			u16 val = readw(priv->io_base) & priv->mask;
> +			*(u16 *)data = val;
> +			break;
> +		}
> +		case 32: {
> +			u32 val = readl(priv->io_base) & priv->mask;
> +			*(u32 *)data = val;
> +			break;
> +		}
> +		}
> +
> +		retval += chunk;
> +		data += chunk;
> +		max -= chunk;
> +	} while (wait && max > chunk);

[Severity: High]
This is a pre-existing issue, but does the driver have a teardown race
condition between remove() and devres cleanup?

In timeriomem_rng_remove(), hrtimer_cancel() is explicitly called to stop the
timer. However, because the RNG device was registered with
devm_hwrng_register() during probe, it remains accessible to userspace and the
hwrng core until the devres cleanups run strictly after remove() returns.

If a concurrent read is blocked on wait_for_completion() when the timer is
cancelled, it could hang indefinitely, deadlocking the reader thread and the
subsequent hwrng_unregister() call in devres cleanup.

Alternatively, if a reader completes its wait and unconditionally calls
hrtimer_restart() in timeriomem_rng_read() after the cancellation, the timer
might be re-armed:

drivers/char/hw_random/timeriomem-rng.c:timeriomem_rng_read() {
    ...
	priv->present = 0;
	reinit_completion(&priv->completion);
	hrtimer_forward_now(&priv->timer, priv->period);
	hrtimer_restart(&priv->timer);
    ...
}

When the devres cleanup later unregisters the RNG and frees the priv
structure, this re-armed timer will eventually fire, potentially dereferencing
a freed pointer.

[ ... ]
> @@ -145,9 +164,42 @@ static int timeriomem_rng_probe(struct platform_device *pdev)
>  		if (!of_property_read_u32(pdev->dev.of_node,
>  						"quality", &i))
>  			priv->rng_ops.quality = i;
> +
> +		of_property_read_u32(pdev->dev.of_node,
> +				     "width", &priv->width);
> +		of_property_read_u32(pdev->dev.of_node,
> +				     "mask", &priv->mask);
>  	} else {
>  		period = pdata->period;
>  		priv->rng_ops.quality = pdata->quality;
> +
> +		if (pdata->width_set)
> +			priv->width = pdata->width;
> +		if (pdata->mask_set)
> +			priv->mask = pdata->mask;
> +	}
> +
> +	if (priv->width == 0)
> +		priv->width = 32;
> +
> +	switch (priv->width) {
> +	case 8:
> +	case 16:
> +	case 32:
> +		break;
> +	default:
> +		dev_err(&pdev->dev, "invalid width %u, must be 8, 16, or 32\n",
> +			priv->width);
> +		return -EINVAL;
> +	}
> +
> +	if (res->start % (priv->width / 8) != 0 ||
> +	    resource_size(res) < priv->width / 8) {

[Severity: Low]
Does using the modulo operator here cause an undefined reference to __umoddi3
on 32-bit platforms?

The variable res->start is of type resource_size_t, which can be a 64-bit
integer on 32-bit platforms with PAE enabled (CONFIG_PHYS_ADDR_T_64BIT=y).

Since the divisor (priv->width / 8) is a runtime value rather than a
compile-time constant, the compiler might emit a call to __umoddi3 for the
64-bit division, which leads to a linker error on 32-bit architectures.
Could IS_ALIGNED() or div_u64() be used instead?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260615170922.1132642-1-inasj268@gmail.com?part=1

^ 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