* [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data
2026-06-05 10:08 [PATCH 0/3] iio: lsm303dlh-magn: endianness + boot-time fullscale selection Herman van Hazendonk
@ 2026-06-05 10:08 ` Herman van Hazendonk
2026-06-05 17:47 ` Andy Shevchenko
2026-06-14 18:39 ` Jonathan Cameron
2026-06-05 10:08 ` [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg Herman van Hazendonk
2026-06-05 10:08 ` [PATCH 3/3] iio: magnetometer: st_magn: honour st,fullscale-mg DT property Herman van Hazendonk
2 siblings, 2 replies; 15+ messages in thread
From: Herman van Hazendonk @ 2026-06-05 10:08 UTC (permalink / raw)
To: jic23, linusw, denis.ciocca, robh, krzk+dt, conor+dt
Cc: dlechner, nuno.sa, andy, sanjayembeddedse, maudspierings,
linux-iio, devicetree, linux-kernel, Herman van Hazendonk
st_sensors_read_axis_data() unconditionally decoded multi-byte
results with get_unaligned_le16() / get_unaligned_le24() regardless
of the channel's declared scan_type.endianness.
For every ST sensor that has used this helper since it was introduced
this happened to be fine because the ST IMU/accel/gyro/pressure
families publish their data registers as little-endian and the
channel specs in those drivers declare IIO_LE accordingly.
The LSM303DLH magnetometer however publishes its X/Y/Z output as a
pair of big-endian bytes (the H register sits at the lower address,
0x03/0x05/0x07, and the L register immediately after), and its
channel specs in st_magn_core.c correctly declare IIO_BE -- but
read_axis_data() ignored that and decoded as little-endian, swapping
the high and low bytes of every magnetometer sample.
The bug is most visible on a stationary chip: in earth's field the
true X reading is small and the high byte sits at 0x00, so swapping
the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
= 240). Y and Z still appear "to vary" because their magnitudes are
larger and the noise in the low byte produces big swings in the
swapped high byte:
before (chip flat, sysfs in_magn_*_raw):
X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
after (direct i2c-dev big-endian decode, same chip same orientation):
X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's
ambient field at low gauss range)
Fix read_axis_data() to dispatch on ch->scan_type.endianness and
call get_unaligned_be16() / get_unaligned_be24() when the channel
declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
st_pressure, st_lsm6dsx and others) are unaffected because their
channel specs already declare IIO_LE and the LE path is unchanged.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
.../iio/common/st_sensors/st_sensors_core.c | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/common/st_sensors/st_sensors_core.c b/drivers/iio/common/st_sensors/st_sensors_core.c
index dbc5e16fbde4..84bce1539908 100644
--- a/drivers/iio/common/st_sensors/st_sensors_core.c
+++ b/drivers/iio/common/st_sensors/st_sensors_core.c
@@ -508,12 +508,21 @@ static int st_sensors_read_axis_data(struct iio_dev *indio_dev,
if (err < 0)
return err;
- if (byte_for_channel == 1)
+ if (byte_for_channel == 1) {
*data = (s8)*outdata;
- else if (byte_for_channel == 2)
- *data = (s16)get_unaligned_le16(outdata);
- else if (byte_for_channel == 3)
- *data = (s32)sign_extend32(get_unaligned_le24(outdata), 23);
+ } else if (byte_for_channel == 2) {
+ if (ch->scan_type.endianness == IIO_BE)
+ *data = (s16)get_unaligned_be16(outdata);
+ else
+ *data = (s16)get_unaligned_le16(outdata);
+ } else if (byte_for_channel == 3) {
+ if (ch->scan_type.endianness == IIO_BE)
+ *data = (s32)sign_extend32(get_unaligned_be24(outdata),
+ 23);
+ else
+ *data = (s32)sign_extend32(get_unaligned_le24(outdata),
+ 23);
+ }
return 0;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data
2026-06-05 10:08 ` [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data Herman van Hazendonk
@ 2026-06-05 17:47 ` Andy Shevchenko
2026-06-15 12:41 ` me
2026-06-14 18:39 ` Jonathan Cameron
1 sibling, 1 reply; 15+ messages in thread
From: Andy Shevchenko @ 2026-06-05 17:47 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: jic23, linusw, denis.ciocca, robh, krzk+dt, conor+dt, dlechner,
nuno.sa, andy, sanjayembeddedse, maudspierings, linux-iio,
devicetree, linux-kernel
On Fri, Jun 05, 2026 at 12:08:41PM +0200, Herman van Hazendonk wrote:
> st_sensors_read_axis_data() unconditionally decoded multi-byte
> results with get_unaligned_le16() / get_unaligned_le24() regardless
> of the channel's declared scan_type.endianness.
>
> For every ST sensor that has used this helper since it was introduced
> this happened to be fine because the ST IMU/accel/gyro/pressure
> families publish their data registers as little-endian and the
> channel specs in those drivers declare IIO_LE accordingly.
>
> The LSM303DLH magnetometer however publishes its X/Y/Z output as a
> pair of big-endian bytes (the H register sits at the lower address,
> 0x03/0x05/0x07, and the L register immediately after), and its
> channel specs in st_magn_core.c correctly declare IIO_BE -- but
> read_axis_data() ignored that and decoded as little-endian, swapping
> the high and low bytes of every magnetometer sample.
>
> The bug is most visible on a stationary chip: in earth's field the
> true X reading is small and the high byte sits at 0x00, so swapping
> the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
> = 240). Y and Z still appear "to vary" because their magnitudes are
> larger and the noise in the low byte produces big swings in the
> swapped high byte:
>
> before (chip flat, sysfs in_magn_*_raw):
> X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
>
> after (direct i2c-dev big-endian decode, same chip same orientation):
> X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's
> ambient field at low gauss range)
>
> Fix read_axis_data() to dispatch on ch->scan_type.endianness and
> call get_unaligned_be16() / get_unaligned_be24() when the channel
> declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
> st_pressure, st_lsm6dsx and others) are unaffected because their
> channel specs already declare IIO_LE and the LE path is unchanged.
...
> - if (byte_for_channel == 1)
> + if (byte_for_channel == 1) {
> *data = (s8)*outdata;
Maybe for the consistency's sake use sign_extend32() everywhere?
> + } else if (byte_for_channel == 2) {
> + if (ch->scan_type.endianness == IIO_BE)
> + *data = (s16)get_unaligned_be16(outdata);
> + else
> + *data = (s16)get_unaligned_le16(outdata);
> + } else if (byte_for_channel == 3) {
> + if (ch->scan_type.endianness == IIO_BE)
> + *data = (s32)sign_extend32(get_unaligned_be24(outdata),
> + 23);
> + else
> + *data = (s32)sign_extend32(get_unaligned_le24(outdata),
> + 23);
Why do you need casting here? sign_extend32() should return signed type.
With this being addressed, you can make them one-liners.
> + }
u32 tmp;
...
if (byte_for_channel == 1) {
// this way is done to show the below variant
tmp = *outdata;
*data = sign_extend32(tmp, 7);
} else if (byte_for_channel == 2) {
if (ch->scan_type.endianness == IIO_BE)
tmp = get_unaligned_be16(outdata);
else
tmp = get_unaligned_le16(outdata);
*data = sign_extend32(tmp, 15);
} else if (byte_for_channel == 3) {
if (ch->scan_type.endianness == IIO_BE)
tmp = get_unaligned_be24(outdata);
else
tmp = get_unaligned_le24(outdata);
*data = sign_extend32(tmp, 23);
}
Or even
if (byte_for_channel == 1) {
tmp = *outdata;
} else if (byte_for_channel == 2) {
if (ch->scan_type.endianness == IIO_BE)
tmp = get_unaligned_be16(outdata);
else
tmp = get_unaligned_le16(outdata);
} else if (byte_for_channel == 3) {
if (ch->scan_type.endianness == IIO_BE)
tmp = get_unaligned_be24(outdata);
else
tmp = get_unaligned_le24(outdata);
} else {
...error...
}
*data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1);
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data
2026-06-05 17:47 ` Andy Shevchenko
@ 2026-06-15 12:41 ` me
0 siblings, 0 replies; 15+ messages in thread
From: me @ 2026-06-15 12:41 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Herman van Hazendonk, jic23, linusw, denis.ciocca, robh, krzk+dt,
conor+dt, dlechner, nuno.sa, andy, sanjayembeddedse,
maudspierings, linux-iio, devicetree, linux-kernel
On 2026-06-05 19:47, Andy Shevchenko wrote:
> On Fri, Jun 05, 2026 at 12:08:41PM +0200, Herman van Hazendonk wrote:
>> st_sensors_read_axis_data() unconditionally decoded multi-byte
>> results with get_unaligned_le16() / get_unaligned_le24() regardless
>> of the channel's declared scan_type.endianness.
>>
>> For every ST sensor that has used this helper since it was introduced
>> this happened to be fine because the ST IMU/accel/gyro/pressure
>> families publish their data registers as little-endian and the
>> channel specs in those drivers declare IIO_LE accordingly.
>>
>> The LSM303DLH magnetometer however publishes its X/Y/Z output as a
>> pair of big-endian bytes (the H register sits at the lower address,
>> 0x03/0x05/0x07, and the L register immediately after), and its
>> channel specs in st_magn_core.c correctly declare IIO_BE -- but
>> read_axis_data() ignored that and decoded as little-endian, swapping
>> the high and low bytes of every magnetometer sample.
>>
>> The bug is most visible on a stationary chip: in earth's field the
>> true X reading is small and the high byte sits at 0x00, so swapping
>> the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
>> = 240). Y and Z still appear "to vary" because their magnitudes are
>> larger and the noise in the low byte produces big swings in the
>> swapped high byte:
>>
>> before (chip flat, sysfs in_magn_*_raw):
>> X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
>>
>> after (direct i2c-dev big-endian decode, same chip same
>> orientation):
>> X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's
>> ambient field at low gauss range)
>>
>> Fix read_axis_data() to dispatch on ch->scan_type.endianness and
>> call get_unaligned_be16() / get_unaligned_be24() when the channel
>> declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
>> st_pressure, st_lsm6dsx and others) are unaffected because their
>> channel specs already declare IIO_LE and the LE path is unchanged.
>
> ...
>
>> - if (byte_for_channel == 1)
>> + if (byte_for_channel == 1) {
>> *data = (s8)*outdata;
>
> Maybe for the consistency's sake use sign_extend32() everywhere?
>
>> + } else if (byte_for_channel == 2) {
>> + if (ch->scan_type.endianness == IIO_BE)
>> + *data = (s16)get_unaligned_be16(outdata);
>> + else
>> + *data = (s16)get_unaligned_le16(outdata);
>> + } else if (byte_for_channel == 3) {
>> + if (ch->scan_type.endianness == IIO_BE)
>> + *data = (s32)sign_extend32(get_unaligned_be24(outdata),
>> + 23);
>> + else
>> + *data = (s32)sign_extend32(get_unaligned_le24(outdata),
>> + 23);
>
> Why do you need casting here? sign_extend32() should return signed
> type.
> With this being addressed, you can make them one-liners.
>
>> + }
>
> u32 tmp;
> ...
> if (byte_for_channel == 1) {
> // this way is done to show the below variant
> tmp = *outdata;
> *data = sign_extend32(tmp, 7);
> } else if (byte_for_channel == 2) {
> if (ch->scan_type.endianness == IIO_BE)
> tmp = get_unaligned_be16(outdata);
> else
> tmp = get_unaligned_le16(outdata);
> *data = sign_extend32(tmp, 15);
> } else if (byte_for_channel == 3) {
> if (ch->scan_type.endianness == IIO_BE)
> tmp = get_unaligned_be24(outdata);
> else
> tmp = get_unaligned_le24(outdata);
> *data = sign_extend32(tmp, 23);
> }
>
> Or even
>
> if (byte_for_channel == 1) {
> tmp = *outdata;
> } else if (byte_for_channel == 2) {
> if (ch->scan_type.endianness == IIO_BE)
> tmp = get_unaligned_be16(outdata);
> else
> tmp = get_unaligned_le16(outdata);
> } else if (byte_for_channel == 3) {
> if (ch->scan_type.endianness == IIO_BE)
> tmp = get_unaligned_be24(outdata);
> else
> tmp = get_unaligned_le24(outdata);
> } else {
> ...error...
> }
> *data = sign_extend32(tmp, BYTES_TO_BITS(byte_for_channel) - 1);
Good points, both applied as you suggested in v2. Single u32 tmp
captures
the per-width decode, then one sign_extend32(tmp,
BYTES_TO_BITS(byte_for_channel)
- 1) at the bottom handles all three widths. Casts are gone. I also
turned
the previously-silent 0/≥4 fall-through into an explicit return
-EINVAL so
a malformed channel spec is caught instead of leaving *data uninitialis
Thanks,
Herman
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data
2026-06-05 10:08 ` [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data Herman van Hazendonk
2026-06-05 17:47 ` Andy Shevchenko
@ 2026-06-14 18:39 ` Jonathan Cameron
2026-06-15 11:12 ` me
1 sibling, 1 reply; 15+ messages in thread
From: Jonathan Cameron @ 2026-06-14 18:39 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: linusw, denis.ciocca, robh, krzk+dt, conor+dt, dlechner, nuno.sa,
andy, sanjayembeddedse, maudspierings, linux-iio, devicetree,
linux-kernel
On Fri, 5 Jun 2026 12:08:41 +0200
Herman van Hazendonk <github.com@herrie.org> wrote:
> st_sensors_read_axis_data() unconditionally decoded multi-byte
> results with get_unaligned_le16() / get_unaligned_le24() regardless
> of the channel's declared scan_type.endianness.
>
> For every ST sensor that has used this helper since it was introduced
> this happened to be fine because the ST IMU/accel/gyro/pressure
> families publish their data registers as little-endian and the
> channel specs in those drivers declare IIO_LE accordingly.
>
> The LSM303DLH magnetometer however publishes its X/Y/Z output as a
> pair of big-endian bytes (the H register sits at the lower address,
> 0x03/0x05/0x07, and the L register immediately after), and its
> channel specs in st_magn_core.c correctly declare IIO_BE -- but
> read_axis_data() ignored that and decoded as little-endian, swapping
> the high and low bytes of every magnetometer sample.
>
> The bug is most visible on a stationary chip: in earth's field the
> true X reading is small and the high byte sits at 0x00, so swapping
> the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
> = 240). Y and Z still appear "to vary" because their magnitudes are
> larger and the noise in the low byte produces big swings in the
> swapped high byte:
>
> before (chip flat, sysfs in_magn_*_raw):
> X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
>
> after (direct i2c-dev big-endian decode, same chip same orientation):
> X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's
> ambient field at low gauss range)
>
> Fix read_axis_data() to dispatch on ch->scan_type.endianness and
> call get_unaligned_be16() / get_unaligned_be24() when the channel
> declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
> st_pressure, st_lsm6dsx and others) are unaffected because their
> channel specs already declare IIO_LE and the LE path is unchanged.
>
Hi Herman,
Fixes tag please. We want to know how far to backport and provide
info to those trying to work out if the bug affects their kernel
trees.
Thanks
Jonathan
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data
2026-06-14 18:39 ` Jonathan Cameron
@ 2026-06-15 11:12 ` me
0 siblings, 0 replies; 15+ messages in thread
From: me @ 2026-06-15 11:12 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Herman van Hazendonk, linusw, denis.ciocca, robh, krzk+dt,
conor+dt, dlechner, nuno.sa, andy, sanjayembeddedse,
maudspierings, linux-iio, devicetree, linux-kernel
On 2026-06-14 20:39, Jonathan Cameron wrote:
> On Fri, 5 Jun 2026 12:08:41 +0200
> Herman van Hazendonk <github.com@herrie.org> wrote:
>
>> st_sensors_read_axis_data() unconditionally decoded multi-byte
>> results with get_unaligned_le16() / get_unaligned_le24() regardless
>> of the channel's declared scan_type.endianness.
>>
>> For every ST sensor that has used this helper since it was introduced
>> this happened to be fine because the ST IMU/accel/gyro/pressure
>> families publish their data registers as little-endian and the
>> channel specs in those drivers declare IIO_LE accordingly.
>>
>> The LSM303DLH magnetometer however publishes its X/Y/Z output as a
>> pair of big-endian bytes (the H register sits at the lower address,
>> 0x03/0x05/0x07, and the L register immediately after), and its
>> channel specs in st_magn_core.c correctly declare IIO_BE -- but
>> read_axis_data() ignored that and decoded as little-endian, swapping
>> the high and low bytes of every magnetometer sample.
>>
>> The bug is most visible on a stationary chip: in earth's field the
>> true X reading is small and the high byte sits at 0x00, so swapping
>> the bytes pins sysfs X at exactly the low byte's pattern (e.g. 0x00F0
>> = 240). Y and Z still appear "to vary" because their magnitudes are
>> larger and the noise in the low byte produces big swings in the
>> swapped high byte:
>>
>> before (chip flat, sysfs in_magn_*_raw):
>> X=240 (stuck), Y= 12032..23296, Z=-16128..-9728
>>
>> after (direct i2c-dev big-endian decode, same chip same
>> orientation):
>> X≈-4096, Y≈210, Z≈80 (sensible values reflecting earth's
>> ambient field at low gauss range)
>>
>> Fix read_axis_data() to dispatch on ch->scan_type.endianness and
>> call get_unaligned_be16() / get_unaligned_be24() when the channel
>> declares IIO_BE. Existing IIO_LE consumers (st_accel, st_gyro,
>> st_pressure, st_lsm6dsx and others) are unaffected because their
>> channel specs already declare IIO_LE and the LE path is unchanged.
>>
> Hi Herman,
>
> Fixes tag please. We want to know how far to backport and provide
> info to those trying to work out if the bug affects their kernel
> trees.
>
> Thanks
>
> Jonathan
>
>> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
Hi Jonathan,
I've been working with the HP TouchPad which uses the Qualcomm APQ8060
to bring this to mainline kernel, which was running a very ancient
2.6.35 kernel where I could compare things since it was working there
without issues, hence I could compare them side by side, check values
etc.
It seems this "bug" was introduced with 3.9:
Fixes: 23491b513bcd ("iio:common: Add STMicroelectronics common
library")
That's the commit that introduced st_sensors_read_axis_data() with the
unconditional get_unaligned_le16() decode.
I'll include that for the v2.
Thanks,
Herman
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg
2026-06-05 10:08 [PATCH 0/3] iio: lsm303dlh-magn: endianness + boot-time fullscale selection Herman van Hazendonk
2026-06-05 10:08 ` [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data Herman van Hazendonk
@ 2026-06-05 10:08 ` Herman van Hazendonk
2026-06-05 16:07 ` Conor Dooley
` (2 more replies)
2026-06-05 10:08 ` [PATCH 3/3] iio: magnetometer: st_magn: honour st,fullscale-mg DT property Herman van Hazendonk
2 siblings, 3 replies; 15+ messages in thread
From: Herman van Hazendonk @ 2026-06-05 10:08 UTC (permalink / raw)
To: jic23, linusw, denis.ciocca, robh, krzk+dt, conor+dt
Cc: dlechner, nuno.sa, andy, sanjayembeddedse, maudspierings,
linux-iio, devicetree, linux-kernel, Herman van Hazendonk
Add an optional st,fullscale-mg property that selects the initial
full-scale range of an ST MEMS sensor at probe time, expressed in
milligauss for magnetometers (and analogous engineering units for
other ST sensor families that may grow this property in the future).
The property is purely additive: if absent, drivers fall back to
their existing chip default, and if present but unsupported by the
specific sensor the driver warns and falls back. No existing in-tree
DTS is affected.
The motivating case is the LSM303DLH magnetometer on the HP TouchPad
(apq8060 / tenderloin) where the kernel's chip-default +/-1.3 G range
saturates the X axis to the chip's 0xF000 overflow sentinel out of
probe, because the chip is mounted close to surrounding power planes
and picks up enough DC bias to exceed the smallest range. The driver
core hardcodes fs_avl[0] as the starting range, so userspace cannot
recover without racing the driver to write the in_magn_x_scale sysfs
attribute after probe. st,fullscale-mg lets the device tree declare
a wider initial range up-front and avoids the race entirely.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
.../devicetree/bindings/iio/st,st-sensors.yaml | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
index a1a958215cdb..335f38e9f78f 100644
--- a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
+++ b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
@@ -126,6 +126,24 @@ properties:
mount-matrix:
description: an optional 3x3 mounting rotation matrix.
+ st,fullscale-mg:
+ description: |
+ Selects the initial sensor full-scale at probe time, expressed in
+ milligauss for magnetometers (or analogous engineering units for
+ other sensor families that may grow this property in the future).
+ The value must match one of the sensor-specific full-scale ranges
+ supported by the chip; if the chip does not support the requested
+ range the driver falls back to its built-in default.
+
+ This is intended for boards where the magnetometer chip picks up
+ enough DC bias from nearby PCB structures (power planes, ferrous
+ shields, etc.) that the kernel's chip-default highest-sensitivity
+ range saturates one or more axes to the chip's overflow sentinel,
+ and userspace observes that axis as permanently stuck. Declaring
+ a wider initial range avoids the saturation at the cost of a
+ slightly coarser quantisation.
+ $ref: /schemas/types.yaml#/definitions/uint32
+
allOf:
- if:
properties:
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg
2026-06-05 10:08 ` [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg Herman van Hazendonk
@ 2026-06-05 16:07 ` Conor Dooley
2026-06-07 22:54 ` Linus Walleij
2026-06-14 18:44 ` Jonathan Cameron
2 siblings, 0 replies; 15+ messages in thread
From: Conor Dooley @ 2026-06-05 16:07 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: jic23, linusw, denis.ciocca, robh, krzk+dt, conor+dt, dlechner,
nuno.sa, andy, sanjayembeddedse, maudspierings, linux-iio,
devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 3138 bytes --]
On Fri, Jun 05, 2026 at 12:08:42PM +0200, Herman van Hazendonk wrote:
> Add an optional st,fullscale-mg property that selects the initial
> full-scale range of an ST MEMS sensor at probe time, expressed in
> milligauss for magnetometers (and analogous engineering units for
> other ST sensor families that may grow this property in the future).
>
> The property is purely additive: if absent, drivers fall back to
> their existing chip default, and if present but unsupported by the
> specific sensor the driver warns and falls back. No existing in-tree
> DTS is affected.
>
> The motivating case is the LSM303DLH magnetometer on the HP TouchPad
> (apq8060 / tenderloin) where the kernel's chip-default +/-1.3 G range
> saturates the X axis to the chip's 0xF000 overflow sentinel out of
> probe, because the chip is mounted close to surrounding power planes
> and picks up enough DC bias to exceed the smallest range. The driver
> core hardcodes fs_avl[0] as the starting range, so userspace cannot
> recover without racing the driver to write the in_magn_x_scale sysfs
> attribute after probe. st,fullscale-mg lets the device tree declare
> a wider initial range up-front and avoids the race entirely.
>
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
> .../devicetree/bindings/iio/st,st-sensors.yaml | 18 ++++++++++++++++++
> 1 file changed, 18 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> index a1a958215cdb..335f38e9f78f 100644
> --- a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> +++ b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> @@ -126,6 +126,24 @@ properties:
> mount-matrix:
> description: an optional 3x3 mounting rotation matrix.
>
> + st,fullscale-mg:
I'd be inclined to say that this should spell out milligauss, but
this seems reasonable enough to me.
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
> + description: |
> + Selects the initial sensor full-scale at probe time, expressed in
> + milligauss for magnetometers (or analogous engineering units for
> + other sensor families that may grow this property in the future).
> + The value must match one of the sensor-specific full-scale ranges
> + supported by the chip; if the chip does not support the requested
> + range the driver falls back to its built-in default.
> +
> + This is intended for boards where the magnetometer chip picks up
> + enough DC bias from nearby PCB structures (power planes, ferrous
> + shields, etc.) that the kernel's chip-default highest-sensitivity
> + range saturates one or more axes to the chip's overflow sentinel,
> + and userspace observes that axis as permanently stuck. Declaring
> + a wider initial range avoids the saturation at the cost of a
> + slightly coarser quantisation.
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> allOf:
> - if:
> properties:
> --
> 2.43.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg
2026-06-05 10:08 ` [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg Herman van Hazendonk
2026-06-05 16:07 ` Conor Dooley
@ 2026-06-07 22:54 ` Linus Walleij
2026-06-14 18:44 ` Jonathan Cameron
2 siblings, 0 replies; 15+ messages in thread
From: Linus Walleij @ 2026-06-07 22:54 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: jic23, denis.ciocca, robh, krzk+dt, conor+dt, dlechner, nuno.sa,
andy, sanjayembeddedse, maudspierings, linux-iio, devicetree,
linux-kernel
On Fri, Jun 5, 2026 at 12:08 PM Herman van Hazendonk
<github.com@herrie.org> wrote:
> Add an optional st,fullscale-mg property that selects the initial
> full-scale range of an ST MEMS sensor at probe time, expressed in
> milligauss for magnetometers (and analogous engineering units for
> other ST sensor families that may grow this property in the future).
>
> The property is purely additive: if absent, drivers fall back to
> their existing chip default, and if present but unsupported by the
> specific sensor the driver warns and falls back. No existing in-tree
> DTS is affected.
>
> The motivating case is the LSM303DLH magnetometer on the HP TouchPad
> (apq8060 / tenderloin) where the kernel's chip-default +/-1.3 G range
> saturates the X axis to the chip's 0xF000 overflow sentinel out of
> probe, because the chip is mounted close to surrounding power planes
> and picks up enough DC bias to exceed the smallest range. The driver
> core hardcodes fs_avl[0] as the starting range, so userspace cannot
> recover without racing the driver to write the in_magn_x_scale sysfs
> attribute after probe. st,fullscale-mg lets the device tree declare
> a wider initial range up-front and avoids the race entirely.
>
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
Makes sense to me.
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg
2026-06-05 10:08 ` [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg Herman van Hazendonk
2026-06-05 16:07 ` Conor Dooley
2026-06-07 22:54 ` Linus Walleij
@ 2026-06-14 18:44 ` Jonathan Cameron
2026-06-15 11:16 ` me
2 siblings, 1 reply; 15+ messages in thread
From: Jonathan Cameron @ 2026-06-14 18:44 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: linusw, denis.ciocca, robh, krzk+dt, conor+dt, dlechner, nuno.sa,
andy, sanjayembeddedse, maudspierings, linux-iio, devicetree,
linux-kernel
On Fri, 5 Jun 2026 12:08:42 +0200
Herman van Hazendonk <github.com@herrie.org> wrote:
> Add an optional st,fullscale-mg property that selects the initial
> full-scale range of an ST MEMS sensor at probe time, expressed in
> milligauss for magnetometers (and analogous engineering units for
> other ST sensor families that may grow this property in the future).
>
> The property is purely additive: if absent, drivers fall back to
> their existing chip default, and if present but unsupported by the
> specific sensor the driver warns and falls back. No existing in-tree
> DTS is affected.
>
> The motivating case is the LSM303DLH magnetometer on the HP TouchPad
> (apq8060 / tenderloin) where the kernel's chip-default +/-1.3 G range
> saturates the X axis to the chip's 0xF000 overflow sentinel out of
> probe, because the chip is mounted close to surrounding power planes
> and picks up enough DC bias to exceed the smallest range. The driver
> core hardcodes fs_avl[0] as the starting range, so userspace cannot
> recover without racing the driver to write the in_magn_x_scale sysfs
> attribute after probe. st,fullscale-mg lets the device tree declare
> a wider initial range up-front and avoids the race entirely.
I'm trying to understand what you mean here by racing.
If we get this overflow condition the chip is wedged until reset, or
userspace simply has to change the range to recover?
I'm wondering if a UDEV rule is sufficient in theory to fix this.
I'm not necessarily against having the range in DT as it is effectively
hardware dependent but just want to make sure I fully understand the issue.
Jonathan
>
> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> ---
> .../devicetree/bindings/iio/st,st-sensors.yaml | 18 ++++++++++++++++++
> 1 file changed, 18 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> index a1a958215cdb..335f38e9f78f 100644
> --- a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> +++ b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> @@ -126,6 +126,24 @@ properties:
> mount-matrix:
> description: an optional 3x3 mounting rotation matrix.
>
> + st,fullscale-mg:
> + description: |
> + Selects the initial sensor full-scale at probe time, expressed in
> + milligauss for magnetometers (or analogous engineering units for
> + other sensor families that may grow this property in the future).
> + The value must match one of the sensor-specific full-scale ranges
> + supported by the chip; if the chip does not support the requested
> + range the driver falls back to its built-in default.
> +
> + This is intended for boards where the magnetometer chip picks up
> + enough DC bias from nearby PCB structures (power planes, ferrous
> + shields, etc.) that the kernel's chip-default highest-sensitivity
> + range saturates one or more axes to the chip's overflow sentinel,
> + and userspace observes that axis as permanently stuck. Declaring
> + a wider initial range avoids the saturation at the cost of a
> + slightly coarser quantisation.
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> allOf:
> - if:
> properties:
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg
2026-06-14 18:44 ` Jonathan Cameron
@ 2026-06-15 11:16 ` me
2026-06-21 12:41 ` Jonathan Cameron
0 siblings, 1 reply; 15+ messages in thread
From: me @ 2026-06-15 11:16 UTC (permalink / raw)
To: Jonathan Cameron
Cc: Herman van Hazendonk, linusw, denis.ciocca, robh, krzk+dt,
conor+dt, dlechner, nuno.sa, andy, sanjayembeddedse,
maudspierings, linux-iio, devicetree, linux-kernel
On 2026-06-14 20:44, Jonathan Cameron wrote:
> On Fri, 5 Jun 2026 12:08:42 +0200
> Herman van Hazendonk <github.com@herrie.org> wrote:
>
>> Add an optional st,fullscale-mg property that selects the initial
>> full-scale range of an ST MEMS sensor at probe time, expressed in
>> milligauss for magnetometers (and analogous engineering units for
>> other ST sensor families that may grow this property in the future).
>>
>> The property is purely additive: if absent, drivers fall back to
>> their existing chip default, and if present but unsupported by the
>> specific sensor the driver warns and falls back. No existing in-tree
>> DTS is affected.
>>
>> The motivating case is the LSM303DLH magnetometer on the HP TouchPad
>> (apq8060 / tenderloin) where the kernel's chip-default +/-1.3 G range
>> saturates the X axis to the chip's 0xF000 overflow sentinel out of
>> probe, because the chip is mounted close to surrounding power planes
>> and picks up enough DC bias to exceed the smallest range. The driver
>> core hardcodes fs_avl[0] as the starting range, so userspace cannot
>> recover without racing the driver to write the in_magn_x_scale sysfs
>> attribute after probe. st,fullscale-mg lets the device tree declare
>> a wider initial range up-front and avoids the race entirely.
>
> I'm trying to understand what you mean here by racing.
>
> If we get this overflow condition the chip is wedged until reset, or
> userspace simply has to change the range to recover?
>
> I'm wondering if a UDEV rule is sufficient in theory to fix this.
>
> I'm not necessarily against having the range in DT as it is effectively
> hardware dependent but just want to make sure I fully understand the
> issue.
>
> Jonathan
>
>
>
>>
>> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
>> ---
>> .../devicetree/bindings/iio/st,st-sensors.yaml | 18
>> ++++++++++++++++++
>> 1 file changed, 18 insertions(+)
>>
>> diff --git a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
>> b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
>> index a1a958215cdb..335f38e9f78f 100644
>> --- a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
>> +++ b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
>> @@ -126,6 +126,24 @@ properties:
>> mount-matrix:
>> description: an optional 3x3 mounting rotation matrix.
>>
>> + st,fullscale-mg:
>> + description: |
>> + Selects the initial sensor full-scale at probe time, expressed
>> in
>> + milligauss for magnetometers (or analogous engineering units
>> for
>> + other sensor families that may grow this property in the
>> future).
>> + The value must match one of the sensor-specific full-scale
>> ranges
>> + supported by the chip; if the chip does not support the
>> requested
>> + range the driver falls back to its built-in default.
>> +
>> + This is intended for boards where the magnetometer chip picks
>> up
>> + enough DC bias from nearby PCB structures (power planes,
>> ferrous
>> + shields, etc.) that the kernel's chip-default
>> highest-sensitivity
>> + range saturates one or more axes to the chip's overflow
>> sentinel,
>> + and userspace observes that axis as permanently stuck.
>> Declaring
>> + a wider initial range avoids the saturation at the cost of a
>> + slightly coarser quantisation.
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> +
>> allOf:
>> - if:
>> properties:
Hi Jonathan,
"racing" was loose wording on my part. The chip is not wedged. Once
userspace writes a wider range to in_magn_x_scale,
the next conversion comes back with sensible data and everything works.
A UDEV rule on add of the IIO device would, in principle, fix
the steady-state problem.
What I was clumsily pointing at is the probe-time window: the IIO
consumers in our stack (sensorfw's iio-sensors-adaptor, geomagnetic
/ orientation services) start polling in_magn_x_raw essentially the
moment the device node appears, and they treat the saturated
0xF000 sentinel as a legitimate sample rather than as overflow. Until
the UDEV rule fires and the kernel commits the new range, every
read returns the stuck sentinel, so orientation/compass features are
wrong from boot for some non-trivial number of samples (and on
slow-boot paths the consumer may have already cached a bogus calibration
baseline by the time UDEV catches up).
So the trade-off as I see it is:
- UDEV rule: works for steady state, fully out-of-tree, but the
per-board configuration ends up split between two places (DTS for
"this board has an LSM303DLH at i2c@... in this orientation"; UDEV for
"and by the way it needs a wider range or its raw readings are
nonsense"). The wider-range requirement is purely a property of how the
magnetometer is mounted on the board, which is what DT is for.
- st,fullscale-mg in DT: keeps all hardware-dependent calibration in one
place, available before any IIO consumer can open the device,
and harmless on boards that don't need it (absent property existing
chip-default behaviour, no DTS in tree changes).
I'm happy to drop the wording about "racing" in v2 and replace it with a
more accurate description of the early-consumer issue if that
reads better. And of course if you'd rather we ship a UDEV rule
downstream instead of adding a binding, I'll yield, but my read is that
this is hardware-dependent enough to belong in DT.
Thanks,
Herman
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg
2026-06-15 11:16 ` me
@ 2026-06-21 12:41 ` Jonathan Cameron
0 siblings, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-06-21 12:41 UTC (permalink / raw)
To: me
Cc: github.com, linusw, denis.ciocca, robh, krzk+dt, conor+dt,
dlechner, nuno.sa, andy, sanjayembeddedse, maudspierings,
linux-iio, devicetree, linux-kernel
On Mon, 15 Jun 2026 13:16:52 +0200
me@herrie.org wrote:
> On 2026-06-14 20:44, Jonathan Cameron wrote:
> > On Fri, 5 Jun 2026 12:08:42 +0200
> > Herman van Hazendonk <github.com@herrie.org> wrote:
> >
> >> Add an optional st,fullscale-mg property that selects the initial
> >> full-scale range of an ST MEMS sensor at probe time, expressed in
> >> milligauss for magnetometers (and analogous engineering units for
> >> other ST sensor families that may grow this property in the future).
> >>
> >> The property is purely additive: if absent, drivers fall back to
> >> their existing chip default, and if present but unsupported by the
> >> specific sensor the driver warns and falls back. No existing in-tree
> >> DTS is affected.
> >>
> >> The motivating case is the LSM303DLH magnetometer on the HP TouchPad
> >> (apq8060 / tenderloin) where the kernel's chip-default +/-1.3 G range
> >> saturates the X axis to the chip's 0xF000 overflow sentinel out of
> >> probe, because the chip is mounted close to surrounding power planes
> >> and picks up enough DC bias to exceed the smallest range. The driver
> >> core hardcodes fs_avl[0] as the starting range, so userspace cannot
> >> recover without racing the driver to write the in_magn_x_scale sysfs
> >> attribute after probe. st,fullscale-mg lets the device tree declare
> >> a wider initial range up-front and avoids the race entirely.
> >
> > I'm trying to understand what you mean here by racing.
> >
> > If we get this overflow condition the chip is wedged until reset, or
> > userspace simply has to change the range to recover?
> >
> > I'm wondering if a UDEV rule is sufficient in theory to fix this.
> >
> > I'm not necessarily against having the range in DT as it is effectively
> > hardware dependent but just want to make sure I fully understand the
> > issue.
> >
> > Jonathan
> >
> >
> >
> >>
> >> Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
> >> ---
> >> .../devicetree/bindings/iio/st,st-sensors.yaml | 18
> >> ++++++++++++++++++
> >> 1 file changed, 18 insertions(+)
> >>
> >> diff --git a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> >> b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> >> index a1a958215cdb..335f38e9f78f 100644
> >> --- a/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> >> +++ b/Documentation/devicetree/bindings/iio/st,st-sensors.yaml
> >> @@ -126,6 +126,24 @@ properties:
> >> mount-matrix:
> >> description: an optional 3x3 mounting rotation matrix.
> >>
> >> + st,fullscale-mg:
> >> + description: |
> >> + Selects the initial sensor full-scale at probe time, expressed
> >> in
> >> + milligauss for magnetometers (or analogous engineering units
> >> for
> >> + other sensor families that may grow this property in the
> >> future).
> >> + The value must match one of the sensor-specific full-scale
> >> ranges
> >> + supported by the chip; if the chip does not support the
> >> requested
> >> + range the driver falls back to its built-in default.
> >> +
> >> + This is intended for boards where the magnetometer chip picks
> >> up
> >> + enough DC bias from nearby PCB structures (power planes,
> >> ferrous
> >> + shields, etc.) that the kernel's chip-default
> >> highest-sensitivity
> >> + range saturates one or more axes to the chip's overflow
> >> sentinel,
> >> + and userspace observes that axis as permanently stuck.
> >> Declaring
> >> + a wider initial range avoids the saturation at the cost of a
> >> + slightly coarser quantisation.
> >> + $ref: /schemas/types.yaml#/definitions/uint32
> >> +
> >> allOf:
> >> - if:
> >> properties:
> Hi Jonathan,
>
> "racing" was loose wording on my part. The chip is not wedged. Once
> userspace writes a wider range to in_magn_x_scale,
> the next conversion comes back with sensible data and everything works.
> A UDEV rule on add of the IIO device would, in principle, fix
> the steady-state problem.
>
> What I was clumsily pointing at is the probe-time window: the IIO
> consumers in our stack (sensorfw's iio-sensors-adaptor, geomagnetic
> / orientation services) start polling in_magn_x_raw essentially the
> moment the device node appears, and they treat the saturated
> 0xF000 sentinel as a legitimate sample rather than as overflow.
Should this perhaps be returning -ERANGE or similar to indicate a failed
read via the return value rather than reading out something
that requires device specific userspace to handle?
Not that this changes the requirements as obviously it still doesn't work
when we want a reading!
> Until
> the UDEV rule fires and the kernel commits the new range, every
> read returns the stuck sentinel, so orientation/compass features are
> wrong from boot for some non-trivial number of samples (and on
> slow-boot paths the consumer may have already cached a bogus calibration
> baseline by the time UDEV catches up).
>
> So the trade-off as I see it is:
>
> - UDEV rule: works for steady state, fully out-of-tree, but the
> per-board configuration ends up split between two places (DTS for
> "this board has an LSM303DLH at i2c@... in this orientation"; UDEV for
> "and by the way it needs a wider range or its raw readings are
> nonsense"). The wider-range requirement is purely a property of how the
> magnetometer is mounted on the board, which is what DT is for.
Fair enough to it being board dependent.
>
> - st,fullscale-mg in DT: keeps all hardware-dependent calibration in one
> place, available before any IIO consumer can open the device,
> and harmless on boards that don't need it (absent property existing
> chip-default behaviour, no DTS in tree changes).
>
> I'm happy to drop the wording about "racing" in v2 and replace it with a
> more accurate description of the early-consumer issue if that
> reads better. And of course if you'd rather we ship a UDEV rule
> downstream instead of adding a binding, I'll yield, but my read is that
> this is hardware-dependent enough to belong in DT.
I'm fine with the DT solution with some of this extra info captured
in the commit message.
Thanks,
Jonathan
>
> Thanks,
> Herman
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 3/3] iio: magnetometer: st_magn: honour st,fullscale-mg DT property
2026-06-05 10:08 [PATCH 0/3] iio: lsm303dlh-magn: endianness + boot-time fullscale selection Herman van Hazendonk
2026-06-05 10:08 ` [PATCH 1/3] iio: common: st_sensors: honour channel endianness in read_axis_data Herman van Hazendonk
2026-06-05 10:08 ` [PATCH 2/3] dt-bindings: iio: st,st-sensors: add st,fullscale-mg Herman van Hazendonk
@ 2026-06-05 10:08 ` Herman van Hazendonk
2026-06-05 17:52 ` Andy Shevchenko
2 siblings, 1 reply; 15+ messages in thread
From: Herman van Hazendonk @ 2026-06-05 10:08 UTC (permalink / raw)
To: jic23, linusw, denis.ciocca, robh, krzk+dt, conor+dt
Cc: dlechner, nuno.sa, andy, sanjayembeddedse, maudspierings,
linux-iio, devicetree, linux-kernel, Herman van Hazendonk
The ST magnetometer core's common probe hardcodes fs_avl[0] -- the
highest-sensitivity full-scale supported by the chip -- as the
starting range. For the LSM303DLH that is +/-1.3 G; for the
LSM303DLHC and LSM303DLM it is +/-2 G; for the LIS3MDL it is +/-4 G.
That is the right default for "minimal noise floor at a desk", but
it leaves no margin for boards that pick up appreciable DC bias from
nearby PCB structures. On the HP TouchPad (apq8060 / tenderloin) the
LSM303DLH magnetometer is mounted close enough to the surrounding
power planes that X reads back as the chip's 0xF000 overflow
sentinel (== -4096 raw, the value the chip publishes when the ADC
saturates) on every sample at the chip-default range, while Y and Z
fall well within the +/-1.3 G window.
Parse the st,fullscale-mg device-tree property (documented separately
in dt-bindings/iio/st,st-sensors.yaml) in the magnetometer common
probe to select the initial fs_avl entry by its mg value. The driver
tolerates an unknown / unsupported value by falling back to the chip
default and warning, so the property is purely additive -- existing
in-tree DTSes are unaffected.
Per-sensor mg ranges are listed in st_magn_sensors_settings[]. For
LSM303DLH the valid values are 1300, 1900, 2500, 4000, 4700, 5600
and 8100; for LSM303DLHC they are 1300, 1900, 2500, 4000, 4700, 5600,
8100 (same code path); for LIS3MDL they are 4000, 8000, 12000, 16000;
and so on. Sensors with a fixed full-scale (fs.addr == 0) simply
ignore the property.
Empirical scale sweep on the HP TouchPad confirmed that on this
board any fs_avl >= 1 produces non-saturated X readings:
scale (0.001 G/LSB) | X raw Y raw Z raw
--------------------+-------------------------------
1.100 | -4096 44 46 (X saturated)
0.855 | -547 37 37 (clean)
0.670 | -433 94 103 (clean)
0.450 | -266 44 71 (clean)
0.400 | -235 34 65 (clean)
0.330 | -196 27 56 (clean)
0.230 | -145 15 40 (clean)
2500 mg is the natural choice for tenderloin: comfortably outside
the saturation regime while keeping useful precision for compass
applications.
Signed-off-by: Herman van Hazendonk <github.com@herrie.org>
---
drivers/iio/magnetometer/st_magn_core.c | 35 +++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/drivers/iio/magnetometer/st_magn_core.c b/drivers/iio/magnetometer/st_magn_core.c
index ef348d316c00..936253440856 100644
--- a/drivers/iio/magnetometer/st_magn_core.c
+++ b/drivers/iio/magnetometer/st_magn_core.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/property.h>
#include <linux/sysfs.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
@@ -628,6 +629,40 @@ int st_magn_common_probe(struct iio_dev *indio_dev)
mdata->current_fullscale = &mdata->sensor_settings->fs.fs_avl[0];
mdata->odr = mdata->sensor_settings->odr.odr_avl[0].hz;
+ /*
+ * Allow the device tree to override the default full-scale. Hardware
+ * such as the LSM303DLH magnetometer on the HP TouchPad picks up
+ * enough DC bias from nearby PCB structures that the chip-default
+ * highest-sensitivity range saturates the X axis to a sentinel
+ * 0xF000 immediately at probe; selecting a less sensitive range via
+ * st,fullscale-mg fixes that without requiring userspace to write
+ * in_magn_*_scale at startup.
+ */
+ {
+ u32 fs_mg;
+
+ if (!device_property_read_u32(parent, "st,fullscale-mg",
+ &fs_mg)) {
+ struct st_sensor_fullscale *fs =
+ &mdata->sensor_settings->fs;
+ int i;
+
+ for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
+ if (!fs->fs_avl[i].num)
+ break;
+ if (fs->fs_avl[i].num == fs_mg) {
+ mdata->current_fullscale =
+ &fs->fs_avl[i];
+ break;
+ }
+ }
+ if (mdata->current_fullscale->num != fs_mg)
+ dev_warn(parent,
+ "st,fullscale-mg=%u not supported, using %u\n",
+ fs_mg, mdata->current_fullscale->num);
+ }
+ }
+
if (!pdata)
pdata = (struct st_sensors_platform_data *)&default_magn_pdata;
--
2.43.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 3/3] iio: magnetometer: st_magn: honour st,fullscale-mg DT property
2026-06-05 10:08 ` [PATCH 3/3] iio: magnetometer: st_magn: honour st,fullscale-mg DT property Herman van Hazendonk
@ 2026-06-05 17:52 ` Andy Shevchenko
2026-06-15 12:40 ` me
0 siblings, 1 reply; 15+ messages in thread
From: Andy Shevchenko @ 2026-06-05 17:52 UTC (permalink / raw)
To: Herman van Hazendonk
Cc: jic23, linusw, denis.ciocca, robh, krzk+dt, conor+dt, dlechner,
nuno.sa, andy, sanjayembeddedse, maudspierings, linux-iio,
devicetree, linux-kernel
On Fri, Jun 05, 2026 at 12:08:43PM +0200, Herman van Hazendonk wrote:
> The ST magnetometer core's common probe hardcodes fs_avl[0] -- the
> highest-sensitivity full-scale supported by the chip -- as the
> starting range. For the LSM303DLH that is +/-1.3 G; for the
> LSM303DLHC and LSM303DLM it is +/-2 G; for the LIS3MDL it is +/-4 G.
>
> That is the right default for "minimal noise floor at a desk", but
> it leaves no margin for boards that pick up appreciable DC bias from
> nearby PCB structures. On the HP TouchPad (apq8060 / tenderloin) the
> LSM303DLH magnetometer is mounted close enough to the surrounding
> power planes that X reads back as the chip's 0xF000 overflow
> sentinel (== -4096 raw, the value the chip publishes when the ADC
> saturates) on every sample at the chip-default range, while Y and Z
> fall well within the +/-1.3 G window.
>
> Parse the st,fullscale-mg device-tree property (documented separately
> in dt-bindings/iio/st,st-sensors.yaml) in the magnetometer common
> probe to select the initial fs_avl entry by its mg value. The driver
> tolerates an unknown / unsupported value by falling back to the chip
> default and warning, so the property is purely additive -- existing
> in-tree DTSes are unaffected.
>
> Per-sensor mg ranges are listed in st_magn_sensors_settings[]. For
> LSM303DLH the valid values are 1300, 1900, 2500, 4000, 4700, 5600
> and 8100; for LSM303DLHC they are 1300, 1900, 2500, 4000, 4700, 5600,
> 8100 (same code path); for LIS3MDL they are 4000, 8000, 12000, 16000;
> and so on. Sensors with a fixed full-scale (fs.addr == 0) simply
> ignore the property.
>
> Empirical scale sweep on the HP TouchPad confirmed that on this
> board any fs_avl >= 1 produces non-saturated X readings:
>
> scale (0.001 G/LSB) | X raw Y raw Z raw
> --------------------+-------------------------------
> 1.100 | -4096 44 46 (X saturated)
> 0.855 | -547 37 37 (clean)
> 0.670 | -433 94 103 (clean)
> 0.450 | -266 44 71 (clean)
> 0.400 | -235 34 65 (clean)
> 0.330 | -196 27 56 (clean)
> 0.230 | -145 15 40 (clean)
>
> 2500 mg is the natural choice for tenderloin: comfortably outside
> the saturation regime while keeping useful precision for compass
> applications.
...
> + {
Oh, no. Use
const char *propname;
...
propname = "st,fullscale-mg";
if (device_property_present(..., propname)) {
struct st_sensor_fullscale *fs = &mdata->sensor_settings->fs;
u32 fs_mg;
ret = device_property_read_u32(parent, propname, &fs_mg);
if (ret)
return ret;
...
}
instead.
> + u32 fs_mg;
> + if (!device_property_read_u32(parent, "st,fullscale-mg",
> + &fs_mg)) {
> + struct st_sensor_fullscale *fs =
> + &mdata->sensor_settings->fs;
> + int i;
> +
> + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
> + if (!fs->fs_avl[i].num)
> + break;
This is strange. What's the point to go via the whole table? Does it have gaps?
> + if (fs->fs_avl[i].num == fs_mg) {
> + mdata->current_fullscale =
> + &fs->fs_avl[i];
> + break;
> + }
> + }
> + if (mdata->current_fullscale->num != fs_mg)
> + dev_warn(parent,
> + "st,fullscale-mg=%u not supported, using %u\n",
Also use %s and propname.
> + fs_mg, mdata->current_fullscale->num);
> + }
> + }
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 3/3] iio: magnetometer: st_magn: honour st,fullscale-mg DT property
2026-06-05 17:52 ` Andy Shevchenko
@ 2026-06-15 12:40 ` me
0 siblings, 0 replies; 15+ messages in thread
From: me @ 2026-06-15 12:40 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Herman van Hazendonk, jic23, linusw, denis.ciocca, robh, krzk+dt,
conor+dt, dlechner, nuno.sa, andy, sanjayembeddedse,
maudspierings, linux-iio, devicetree, linux-kernel
On 2026-06-05 19:52, Andy Shevchenko wrote:
> On Fri, Jun 05, 2026 at 12:08:43PM +0200, Herman van Hazendonk wrote:
>> The ST magnetometer core's common probe hardcodes fs_avl[0] -- the
>> highest-sensitivity full-scale supported by the chip -- as the
>> starting range. For the LSM303DLH that is +/-1.3 G; for the
>> LSM303DLHC and LSM303DLM it is +/-2 G; for the LIS3MDL it is +/-4 G.
>>
>> That is the right default for "minimal noise floor at a desk", but
>> it leaves no margin for boards that pick up appreciable DC bias from
>> nearby PCB structures. On the HP TouchPad (apq8060 / tenderloin) the
>> LSM303DLH magnetometer is mounted close enough to the surrounding
>> power planes that X reads back as the chip's 0xF000 overflow
>> sentinel (== -4096 raw, the value the chip publishes when the ADC
>> saturates) on every sample at the chip-default range, while Y and Z
>> fall well within the +/-1.3 G window.
>>
>> Parse the st,fullscale-mg device-tree property (documented separately
>> in dt-bindings/iio/st,st-sensors.yaml) in the magnetometer common
>> probe to select the initial fs_avl entry by its mg value. The driver
>> tolerates an unknown / unsupported value by falling back to the chip
>> default and warning, so the property is purely additive -- existing
>> in-tree DTSes are unaffected.
>>
>> Per-sensor mg ranges are listed in st_magn_sensors_settings[]. For
>> LSM303DLH the valid values are 1300, 1900, 2500, 4000, 4700, 5600
>> and 8100; for LSM303DLHC they are 1300, 1900, 2500, 4000, 4700, 5600,
>> 8100 (same code path); for LIS3MDL they are 4000, 8000, 12000, 16000;
>> and so on. Sensors with a fixed full-scale (fs.addr == 0) simply
>> ignore the property.
>>
>> Empirical scale sweep on the HP TouchPad confirmed that on this
>> board any fs_avl >= 1 produces non-saturated X readings:
>>
>> scale (0.001 G/LSB) | X raw Y raw Z raw
>> --------------------+-------------------------------
>> 1.100 | -4096 44 46 (X saturated)
>> 0.855 | -547 37 37 (clean)
>> 0.670 | -433 94 103 (clean)
>> 0.450 | -266 44 71 (clean)
>> 0.400 | -235 34 65 (clean)
>> 0.330 | -196 27 56 (clean)
>> 0.230 | -145 15 40 (clean)
>>
>> 2500 mg is the natural choice for tenderloin: comfortably outside
>> the saturation regime while keeping useful precision for compass
>> applications.
>
> ...
>
>> + {
>
> Oh, no. Use
>
> const char *propname;
> ...
> propname = "st,fullscale-mg";
> if (device_property_present(..., propname)) {
> struct st_sensor_fullscale *fs = &mdata->sensor_settings->fs;
> u32 fs_mg;
>
> ret = device_property_read_u32(parent, propname, &fs_mg);
> if (ret)
> return ret;
>
> ...
> }
>
> instead.
Hi Andy,
Done in v2: propname lifted to a local, presence check first,
device_property_read_u32()
failure now return errs rather than silently skipping, and the dev_warn
format string uses
%s, propname so the name is in exactly one place.
>
>> + u32 fs_mg;
>
>> + if (!device_property_read_u32(parent, "st,fullscale-mg",
>> + &fs_mg)) {
>> + struct st_sensor_fullscale *fs =
>> + &mdata->sensor_settings->fs;
>> + int i;
>> +
>> + for (i = 0; i < ST_SENSORS_FULLSCALE_AVL_MAX; i++) {
>> + if (!fs->fs_avl[i].num)
>> + break;
>
> This is strange. What's the point to go via the whole table? Does it
> have gaps?
>
>> + if (fs->fs_avl[i].num == fs_mg) {
>> + mdata->current_fullscale =
>> + &fs->fs_avl[i];
>> + break;
>> + }
>> + }
>> + if (mdata->current_fullscale->num != fs_mg)
>> + dev_warn(parent,
>> + "st,fullscale-mg=%u not supported, using %u\n",
>
> Also use %s and propname.
>
>> + fs_mg, mdata->current_fullscale->num);
>> + }
>> + }
It's not gaps: fs_avl[] is a fixed-size array of
ST_SENSORS_FULLSCALE_AVL_MAX (currently 8)
entries, but different magnetometers fill in different prefix lengths up
to that cap.
LSM303DLH uses 7 entries; LSM303DLHC uses 7; LIS3MDL only uses 4.
The if (!fs->fs_avl[i].num) break; is the existing sentinel convention
used elsewhere in this
file (see st_sensors_set_fullscale_by_gain() / st_sensors_match_fs()) so
the loop terminates at
the first zero .num. I kept the same convention so future sensors with
shorter ranges just work;
happy to switch to a ARRAY_SIZE() + is_terminator() helper if you'd
prefer, but that would be a
wider cleanup than this patch warrants.
Thanks,
Herman
^ permalink raw reply [flat|nested] 15+ messages in thread