All of lore.kernel.org
 help / color / mirror / Atom feed
From: Piyush Patle <piyushpatle228@gmail.com>
To: ak@it-klinger.de, jic23@kernel.org
Cc: dlechner@baylibre.com, nuno.sa@analog.com, andy@kernel.org,
	robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org,
	linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v4 7/7] iio: adc: hx711: add support for HX710B
Date: Mon, 27 Apr 2026 15:39:38 +0530	[thread overview]
Message-ID: <20260427100950.33936-8-piyushpatle228@gmail.com> (raw)
In-Reply-To: <20260427100950.33936-1-piyushpatle228@gmail.com>

Add support for the AVIA HX710B ADC, which shares the HX711 GPIO
interface but has a fixed gain of 128 and uses trailing PD_SCK pulses
to select the active channel rather than the gain.

The HX710B has three operating modes controlled by the trailing pulse
count after the 24 data bits (Table 3 in the HX710B datasheet):
  25 pulses (1 trailing): differential input at 10 SPS
  26 pulses (2 trailing): DVDD-AVDD supply monitor at 40 SPS
  27 pulses (3 trailing): differential input at 40 SPS

Model the HX710B with its own hx710b_chan_spec[] and hx710b_iio_info.
Store the trailing pulse count in chan->address so hx710b_set_channel()
can switch channels without a separate lookup table. The supply monitor
uses .channel = 2 to avoid aliasing the channel2 terminal (index 1) of
the differential pair.

The HX710B has a dedicated VREF pin for the ADC reference voltage. The
driver tries vref-supply first; if absent it falls back to avdd-supply
(for boards where VREF is tied to AVDD). The HX711 uses AVDD as its
reference and is unaffected.

Add fixed_gain and fixed_gain_val fields to hx711_chip_info to carry the
gain into the scale calculation. Store a per-instance scale in
hx711_data for HX710B and use it in hx711_read_raw() when fixed_gain is
set. Update hx711_reset() to reset channel_set on HX710B after a
power-down cycle.

Enlarge the trigger buffer from 2 to 3 channels plus a pad word to keep
the timestamp naturally aligned; HX711 continues to use only the first
two slots.

Signed-off-by: Piyush Patle <piyushpatle228@gmail.com>
---
Changes in v4:
- Add a third HX710B channel (27 pulses, differential 40 SPS) based on
  Table 3 of the HX710B datasheet.
- Use .channel = 2 for the supply monitor channel to avoid aliasing the
  .channel2 = 1 terminal of the first differential pair.
- Add vref-supply probe path for the HX710B VREF reference pin; fall
  back to avdd-supply when vref-supply is absent.
- Add NULL guard on device_get_match_data() in the chip_info
  introduction patch; kept here as documentation that the guard exists.
- Keep hx711_chip_info fields in the final order introduced by the
  chip_info patch; this patch appends fixed_gain_val and fixed_gain
  without reordering existing fields.
- Move channel_set and scale fields to hx711_data (per-instance).
- Update channel_set only after both hx711_read() and
  hx711_wait_for_ready() succeed.
- Keep a single HX710B scale derived from the fixed gain of 128.

Changes in v3:
- Add HX710B support on top of the separate hx711_chip_info refactor.
- Update channel_set only after hx711_read() and hx711_wait_for_ready()
  both succeed.
- Keep a single HX710B fixed-gain scale based on the datasheet's fixed
  PGA gain of 128; do not apply the HX711 channel-B gain of 32 to the
  HX710B supply monitor path.
- Use unsigned int for channel state and fixed-gain scale storage.
- Keep HX710B trailing pulse counts in chan->address.
- Describe HX710B channel 0 as a differential IIO channel.
- Reorder hx711_chip_info fields based on pahole output so the
  structure has no internal holes.

Changes in v2:
- Fix pulse count bug: HX710B values were {25, 26} total SCK cycles;
  corrected to {1, 2} trailing pulses because hx711_read() already
  clocks the 24 data bits.
- Add .differential = 1 and .channel2 = 1 to HX710B channel 0.
- Move trailing pulse counts from a separate array to chan->address.
- Replace chan_pulse_count tests with a dedicated fixed_gain flag.
- Add fixed_gain_val to hx711_chip_info.
- Add the iio_info pointer to hx711_chip_info and assign
  indio_dev->info from chip_info.
- Remove the NULL check after device_get_match_data().
- Remove reset_channel from hx711_chip_info.
- Change hx711_reset_read() and hx710b_set_channel() to take
  const struct iio_chan_spec *.
- Revert unrelated hx711_data struct member alignment noise.
- Sort of_device_id entries alphabetically.
- Expand the commit message to explain HX711 versus HX710B trailing
  pulse behaviour.
- Restore the file header to mention weight sensor modules.
---
 drivers/iio/adc/Kconfig |   1 +
 drivers/iio/adc/hx711.c | 189 ++++++++++++++++++++++++++++++++++------
 2 files changed, 164 insertions(+), 26 deletions(-)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index f18692aea795..09a1b29fbd9c 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -790,6 +790,7 @@ config HX711
 	select IIO_TRIGGERED_BUFFER
 	help
 	  If you say Y here you get support for the following AVIA ADCs:
+	    - HX710B
 	    - HX711
 	  which are used for bridge sensors such as weigh cells.
 
diff --git a/drivers/iio/adc/hx711.c b/drivers/iio/adc/hx711.c
index dc6703ead8a0..41b9beb6d787 100644
--- a/drivers/iio/adc/hx711.c
+++ b/drivers/iio/adc/hx711.c
@@ -78,16 +78,20 @@ static int hx711_get_scale_to_gain(const int *gain_scale, int scale)
 
 /**
  * struct hx711_chip_info - per-variant static configuration
- * @name:         IIO device name
- * @channels:     channel specification array
- * @iio_info:     IIO info ops for this variant
- * @num_channels: number of entries in @channels
+ * @name:          IIO device name
+ * @channels:      channel specification array
+ * @iio_info:      IIO info ops for this variant
+ * @num_channels:  number of entries in @channels
+ * @fixed_gain_val: fixed PGA gain (used when @fixed_gain is true)
+ * @fixed_gain:    true if the variant has a fixed ADC gain (e.g. HX710B)
  */
 struct hx711_chip_info {
 	const char			*name;
 	const struct iio_chan_spec	*channels;
 	const struct iio_info		*iio_info;
 	unsigned int			num_channels;
+	unsigned int			fixed_gain_val;
+	bool				fixed_gain;
 };
 
 struct hx711_data {
@@ -97,14 +101,17 @@ struct hx711_data {
 	int			gain_set;	/* gain set on device */
 	int			gain_chan_a;	/* gain for channel A */
 	int			gain_scale[HX711_GAIN_MAX];
+	unsigned int		channel_set;	/* HX710B active channel */
 	const struct hx711_chip_info	*chip_info;
+	unsigned int		scale;		/* HX710B fixed-gain scale */
 	struct mutex		lock;
 	/*
 	 * triggered buffer
-	 * 2x32-bit channel + 64-bit naturally aligned timestamp
+	 * up to 3x32-bit channels + pad + 64-bit naturally aligned timestamp
 	 */
 	struct {
-		u32 channel[2];
+		u32 channel[3];
+		u32 pad;
 		aligned_s64 timestamp;
 	} buffer;
 	/*
@@ -204,6 +211,7 @@ static int hx711_wait_for_ready(struct hx711_data *hx711_data)
 
 static int hx711_reset(struct hx711_data *hx711_data)
 {
+	const struct hx711_chip_info *info = hx711_data->chip_info;
 	int val = hx711_wait_for_ready(hx711_data);
 
 	if (val) {
@@ -222,8 +230,11 @@ static int hx711_reset(struct hx711_data *hx711_data)
 
 		val = hx711_wait_for_ready(hx711_data);
 
-		/* after a reset the gain is 128 */
-		hx711_data->gain_set = HX711_RESET_GAIN;
+		if (info->fixed_gain)
+			hx711_data->channel_set = 0;
+		else
+			/* after a reset the gain is 128 */
+			hx711_data->gain_set = HX711_RESET_GAIN;
 	}
 
 	return val;
@@ -264,9 +275,36 @@ static int hx711_set_gain_for_channel(struct hx711_data *hx711_data, int chan)
 	return 0;
 }
 
+/*
+ * Switch the HX710B to the requested channel for the next conversion.
+ * chan->address holds the trailing pulse count (Table 3 in datasheet).
+ * channel_set is updated only after both reads succeed.
+ */
+static int hx710b_set_channel(struct hx711_data *hx711_data,
+			      const struct iio_chan_spec *chan)
+{
+	int ret;
+
+	if (hx711_data->channel_set == (unsigned int)chan->channel)
+		return 0;
+
+	ret = hx711_read(hx711_data, chan->address);
+	if (ret < 0)
+		return ret;
+
+	ret = hx711_wait_for_ready(hx711_data);
+	if (ret)
+		return ret;
+
+	hx711_data->channel_set = chan->channel;
+
+	return 0;
+}
+
 static int hx711_reset_read(struct hx711_data *hx711_data,
 			    const struct iio_chan_spec *chan)
 {
+	const struct hx711_chip_info *info = hx711_data->chip_info;
 	unsigned int trailing_pulses;
 	int ret;
 
@@ -279,11 +317,18 @@ static int hx711_reset_read(struct hx711_data *hx711_data,
 		return -EIO;
 	}
 
-	ret = hx711_set_gain_for_channel(hx711_data, chan->channel);
-	if (ret < 0)
-		return ret;
+	if (info->fixed_gain) {
+		ret = hx710b_set_channel(hx711_data, chan);
+		if (ret < 0)
+			return ret;
+		trailing_pulses = chan->address;
+	} else {
+		ret = hx711_set_gain_for_channel(hx711_data, chan->channel);
+		if (ret < 0)
+			return ret;
+		trailing_pulses = hx711_get_gain_to_pulse(hx711_data->gain_set);
+	}
 
-	trailing_pulses = hx711_get_gain_to_pulse(hx711_data->gain_set);
 	return hx711_read(hx711_data, trailing_pulses);
 }
 
@@ -292,6 +337,7 @@ static int hx711_read_raw(struct iio_dev *indio_dev,
 				int *val, int *val2, long mask)
 {
 	struct hx711_data *hx711_data = iio_priv(indio_dev);
+	const struct hx711_chip_info *info = hx711_data->chip_info;
 
 	switch (mask) {
 	case IIO_CHAN_INFO_RAW:
@@ -308,8 +354,11 @@ static int hx711_read_raw(struct iio_dev *indio_dev,
 		*val = 0;
 		mutex_lock(&hx711_data->lock);
 
-		*val2 = hx711_get_gain_to_scale(hx711_data->gain_scale,
-						hx711_data->gain_set);
+		if (info->fixed_gain)
+			*val2 = hx711_data->scale;
+		else
+			*val2 = hx711_get_gain_to_scale(hx711_data->gain_scale,
+							hx711_data->gain_set);
 
 		mutex_unlock(&hx711_data->lock);
 
@@ -445,6 +494,10 @@ static const struct iio_info hx711_iio_info = {
 	.attrs			= &hx711_attribute_group,
 };
 
+static const struct iio_info hx710b_iio_info = {
+	.read_raw		= hx711_read_raw,
+};
+
 static const struct iio_chan_spec hx711_chan_spec[] = {
 	{
 		.type = IIO_VOLTAGE,
@@ -477,6 +530,68 @@ static const struct iio_chan_spec hx711_chan_spec[] = {
 	IIO_CHAN_SOFT_TIMESTAMP(2),
 };
 
+/*
+ * HX710B channels (Table 3 in datasheet).
+ * 25 pulses (1 trailing): differential input, 10 SPS  -> channel 0
+ * 26 pulses (2 trailing): DVDD-AVDD supply monitor, 40 SPS -> channel 2
+ * 27 pulses (3 trailing): differential input, 40 SPS  -> channel 3
+ * .address stores the trailing pulse count for hx710b_set_channel().
+ * Channel 2 is used for the supply monitor to avoid aliasing the
+ * channel2 terminal (index 1) of the differential pair.
+ */
+static const struct iio_chan_spec hx710b_chan_spec[] = {
+	{
+		.type = IIO_VOLTAGE,
+		.differential = 1,
+		.channel = 0,
+		.channel2 = 1,
+		.indexed = 1,
+		.address = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index = 0,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 24,
+			.storagebits = 32,
+			.endianness = IIO_CPU,
+		},
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.channel = 2,
+		.indexed = 1,
+		.address = 2,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index = 1,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 24,
+			.storagebits = 32,
+			.endianness = IIO_CPU,
+		},
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.differential = 1,
+		.channel = 3,
+		.channel2 = 4,
+		.indexed = 1,
+		.address = 3,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.scan_index = 2,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 24,
+			.storagebits = 32,
+			.endianness = IIO_CPU,
+		},
+	},
+	IIO_CHAN_SOFT_TIMESTAMP(3),
+};
+
 static const struct hx711_chip_info hx711_chip = {
 	.name		= "hx711",
 	.channels	= hx711_chan_spec,
@@ -484,6 +599,15 @@ static const struct hx711_chip_info hx711_chip = {
 	.num_channels	= ARRAY_SIZE(hx711_chan_spec),
 };
 
+static const struct hx711_chip_info hx710b_chip = {
+	.name		= "hx710b",
+	.channels	= hx710b_chan_spec,
+	.iio_info	= &hx710b_iio_info,
+	.num_channels	= ARRAY_SIZE(hx710b_chan_spec),
+	.fixed_gain	= true,
+	.fixed_gain_val	= 128,
+};
+
 static int hx711_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -525,32 +649,44 @@ static int hx711_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, PTR_ERR(hx711_data->gpiod_dout),
 				     "failed to get dout-gpiod\n");
 
-	ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
+	/*
+	 * The HX710B uses the VREF pin as the ADC reference; try vref-supply
+	 * first and fall back to avdd-supply when VREF is tied to AVDD on the
+	 * board.  The HX711 uses AVDD as its reference.
+	 */
+	if (chip_info->fixed_gain)
+		ret = devm_regulator_get_enable_read_voltage(dev, "vref");
+	if (!chip_info->fixed_gain || ret == -ENODEV)
+		ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
 	if (ret < 0)
 		return ret;
 
 	/*
 	 * with
-	 * full scale differential input range: AVDD / GAIN
+	 * full scale differential input range: VREF / GAIN
 	 * full scale output data: 2^24
 	 * we can say:
-	 *     AVDD / GAIN = 2^24
+	 *     VREF / GAIN = 2^24
 	 * therefore:
-	 *     1 LSB = AVDD / GAIN / 2^24
-	 * AVDD is in uV, but we need 10^-9 mV
+	 *     1 LSB = VREF / GAIN / 2^24
+	 * VREF is in uV, but we need 10^-9 mV
 	 * approximately to fit into a 32 bit number:
-	 * 1 LSB = (AVDD * 100) / GAIN / 1678 [10^-9 mV]
+	 * 1 LSB = (VREF * 100) / GAIN / 1678 [10^-9 mV]
 	 */
 
 	/* we need 10^-9 mV */
 	ret *= 100;
 
-	for (i = 0; i < HX711_GAIN_MAX; i++)
-		hx711_data->gain_scale[i] =
-			ret / hx711_gain_to_scale[i].gain / 1678;
+	if (chip_info->fixed_gain) {
+		hx711_data->scale = ret / chip_info->fixed_gain_val / 1678;
+	} else {
+		for (i = 0; i < HX711_GAIN_MAX; i++)
+			hx711_data->gain_scale[i] =
+				ret / hx711_gain_to_scale[i].gain / 1678;
 
-	hx711_data->gain_set = 128;
-	hx711_data->gain_chan_a = 128;
+		hx711_data->gain_set = 128;
+		hx711_data->gain_chan_a = 128;
+	}
 
 	hx711_data->clock_frequency = 400000;
 	ret = device_property_read_u32(&pdev->dev, "clock-frequency",
@@ -589,7 +725,8 @@ static int hx711_probe(struct platform_device *pdev)
 }
 
 static const struct of_device_id of_hx711_match[] = {
-	{ .compatible = "avia,hx711", .data = &hx711_chip },
+	{ .compatible = "avia,hx710b", .data = &hx710b_chip },
+	{ .compatible = "avia,hx711",  .data = &hx711_chip  },
 	{ }
 };
 
-- 
2.43.0


  parent reply	other threads:[~2026-04-27 10:11 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-27 10:09 [PATCH v4 0/7] iio: adc: hx711: add HX710B support Piyush Patle
2026-04-27 10:09 ` [PATCH v4 1/7] dt-bindings: iio: adc: avia-hx711: add avia,hx710b compatible Piyush Patle
2026-04-27 15:29   ` David Lechner
2026-04-27 22:36     ` Piyush Patle
2026-04-28 17:49   ` Jonathan Cameron
2026-04-27 10:09 ` [PATCH v4 2/7] iio: adc: hx711: move scale computation to per-device storage Piyush Patle
2026-04-27 14:02   ` Andy Shevchenko
2026-04-27 22:38     ` Piyush Patle
2026-04-28 17:52       ` Jonathan Cameron
2026-04-27 10:09 ` [PATCH v4 3/7] iio: adc: hx711: update Kconfig, module description and file header Piyush Patle
2026-04-27 10:26   ` Joshua Crofts
2026-04-27 13:46     ` Andy Shevchenko
2026-04-27 13:49       ` Joshua Crofts
2026-04-28 17:54         ` Jonathan Cameron
2026-04-28 18:03           ` Joshua Crofts
2026-04-29  9:57           ` Andy Shevchenko
2026-04-29 10:27             ` Jonathan Cameron
2026-04-27 13:58   ` Andy Shevchenko
2026-04-27 14:19     ` Andy Shevchenko
2026-04-27 10:09 ` [PATCH v4 4/7] iio: adc: hx711: introduce hx711_chip_info per-variant structure Piyush Patle
2026-04-27 14:08   ` Andy Shevchenko
2026-04-27 14:14     ` Andy Shevchenko
2026-04-27 22:47       ` Piyush Patle
2026-04-28 17:57         ` Jonathan Cameron
2026-04-27 22:44     ` Piyush Patle
2026-04-27 10:09 ` [PATCH v4 5/7] iio: adc: hx711: pass trailing pulse count into hx711_read() Piyush Patle
2026-04-27 14:12   ` Andy Shevchenko
2026-04-27 22:49     ` Piyush Patle
2026-04-27 10:09 ` [PATCH v4 6/7] iio: adc: hx711: pass iio_chan_spec to hx711_reset_read() Piyush Patle
2026-04-27 14:16   ` Andy Shevchenko
2026-04-27 10:09 ` Piyush Patle [this message]
2026-04-27 14:34   ` [PATCH v4 7/7] iio: adc: hx711: add support for HX710B Andy Shevchenko
2026-04-27 23:06     ` Piyush Patle

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260427100950.33936-8-piyushpatle228@gmail.com \
    --to=piyushpatle228@gmail.com \
    --cc=ak@it-klinger.de \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=dlechner@baylibre.com \
    --cc=jic23@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-iio@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=nuno.sa@analog.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.