Re: [PATCH v2] RFC: iio: lsm6dsx: Support temperature channel on some devices

From: Lorenzo Bianconi
Date: Thu Aug 31 2023 - 05:18:38 EST


> The ISM330 sensors (wai 0x6b) has a temperature channel that can
> be read out. Modify the lsm6dsx core to accomodate temperature
> readout on these sensors:
>
> - Make headspace for three members in the channels and odr_table,
> - Support offset
> - Add code to avoid configuring the ODR of the temperature
> sensor, it has no real ODR control register.
>
> This is investigated because the hardware has important real-life
> use cases using the Linux kernel.

Hi Linus,

please seem my comments inline below.

Regards,
Lorenzo

>
> Signed-off-by: Linus Walleij <linus.walleij@xxxxxxxxxx>
> ---
> The ISM330DHCX is used in the SpaceX Starlink terminals which
> are running a realtime patched close-to-mainline Linux kernel so
> let's support temperature readout on it because why not.
> SpaceX is using the temperature.
> ---
> Changes in v2:
> - Put to RFC because I can't test changes.
> - Added some mail addresses to SpaceX to the header. Maybe you
> guys can check if this works for you. Or forward to designated
> open source ambassador or whatever can help. (Addresses found
> in SpaceX code drop.)
> - Drop the code with strings for ism330dhc as we concluded that
> this is probably ism330dhcx which is already supported.
> (Would be nice if SpaceX can confirm.)
> - Don't write in nonsense register 0x0a for temperature sensor
> - More elaborate code to just avoid writing ODR for the temperature
> sensor and instead rely on gyro or accelerometer to drive
> the odr
> - Link to v1: https://lore.kernel.org/r/20230811-iio-spacex-lsm6ds0-v1-0-e953a440170d@xxxxxxxxxx
> ---
> drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 24 +++++++-
> drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c | 4 ++
> drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 79 +++++++++++++++++++++++++-
> 3 files changed, 102 insertions(+), 5 deletions(-)
>

[...]

> },
> .drdy_mask = {
> .addr = 0x13,
> @@ -869,6 +878,17 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = {
> .odr_avl[6] = { 833000, 0x07 },
> .odr_len = 7,
> },
> + [ST_LSM6DSX_ID_TEMP] = {
> + /*
> + * NOTE: this ODR will be capped and controllerd by the
> + * gyro and accelerometer don't have any reg to configure
> + * this ODR.
> + */
> + .odr_avl[0] = { 12500, 0x01 },
> + .odr_avl[1] = { 26000, 0x02 },
> + .odr_avl[2] = { 52000, 0x03 },
> + .odr_len = 3,

please consider we do not support low-power mode iirc (just high-performance -
bit 4 in CTRL6_C (15h)), so even enabling accel sensor, the temp sensor will
always runs at 52Hz. Here we should add just one entry, like:

.odr_avl[0] = { 52000, 0x03 },
.odr_len = 1,

> + },
> },
> .fs_table = {
> [ST_LSM6DSX_ID_ACC] = {
> @@ -937,6 +957,10 @@ static const struct st_lsm6dsx_settings st_lsm6dsx_sensor_settings[] = {
> .addr = 0x09,
> .mask = GENMASK(7, 4),
> },
> + [ST_LSM6DSX_ID_TEMP] = {
> + .addr = 0x0A,
> + .mask = GENMASK(5, 4),
> + },
> },
> .fifo_ops = {
> .update_fifo = st_lsm6dsx_update_fifo,
> @@ -1618,8 +1642,8 @@ int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u32 odr, u8 *val)
> odr_table = &sensor->hw->settings->odr_table[sensor->id];
> for (i = 0; i < odr_table->odr_len; i++) {
> /*
> - * ext devices can run at different odr respect to
> - * accel sensor
> + * ext devices and temp sensor can run at different odr
> + * respect to accel sensor
> */
> if (odr_table->odr_avl[i].milli_hz >= odr)
> break;
> @@ -1661,6 +1685,21 @@ st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u32 req_odr)
> switch (sensor->id) {
> case ST_LSM6DSX_ID_GYRO:
> break;
> + case ST_LSM6DSX_ID_TEMP:
> + /*
> + * According to the application note AN5398 Rev 3
> + * for ISM330DHCX, section 10, page 109
> + * the ODR for the temperature sensor is equal to the
> + * accelerometer ODR if the gyroscope is powered-down,
> + * up to 52 Hz, but constant 52 Hz if the gyroscope
> + * is powered on. It never goes above 52 Hz.
> + */
> + ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_GYRO]);
> + if ((req_odr > 0) && (hw->enable_mask |= BIT(ref_sensor->id)))

is there a typo here?

> + /* Gyro is on! ODR fixed to 52 Hz */
> + return 0;
> + /* We fall through and activate accelerometer if need be */
> + fallthrough;

I do not think this approach works since please consider what happens with the
sequence of events reported below:
- user enables gyro sensor
- user enables temp sensor
- user disables gyro sensor

IIUC the accel sensor is not enabled in this case so even the temp one will be
powered-down. Is it correct?
I think for the temp sensor we should introduce a dependency from the gyro one,
doing something like (just compiled, not tested):

diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
index 6a18b363cf73..ccd0d48bfb35 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
@@ -1633,19 +1633,36 @@ int st_lsm6dsx_check_odr(struct st_lsm6dsx_sensor *sensor, u32 odr, u8 *val)
}

static int
-st_lsm6dsx_check_odr_dependency(struct st_lsm6dsx_hw *hw, u32 odr,
- enum st_lsm6dsx_sensor_id id)
+st_lsm6dsx_check_odr_dependency(struct st_lsm6dsx_sensor *sensor,
+ enum st_lsm6dsx_sensor_id *odr_map,
+ int odr_map_size, u32 req_odr)
{
- struct st_lsm6dsx_sensor *ref = iio_priv(hw->iio_devs[id]);
+ struct st_lsm6dsx_hw *hw = sensor->hw;
+ int i;

- if (odr > 0) {
- if (hw->enable_mask & BIT(id))
- return max_t(u32, ref->odr, odr);
- else
- return odr;
- } else {
- return (hw->enable_mask & BIT(id)) ? ref->odr : 0;
+ for (i = 0; i < odr_map_size; i++) {
+ enum st_lsm6dsx_sensor_id id = odr_map[i];
+ struct st_lsm6dsx_sensor *ref = iio_priv(hw->iio_devs[id]);
+ u32 odr;
+
+ if (!hw->iio_devs[id] || id == sensor->id)
+ continue;
+
+ if (req_odr) {
+ if (hw->enable_mask & BIT(id))
+ odr = max_t(u32, ref->odr, req_odr);
+ else
+ odr = req_odr;
+ } else {
+ odr = hw->enable_mask & BIT(id) ? ref->odr : 0;
+ }
+
+ if (odr != req_odr)
+ /* device already configured */
+ return -EBUSY;
}
+
+ return 0;
}

static int
@@ -1659,14 +1676,30 @@ st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u32 req_odr)
int err;

switch (sensor->id) {
- case ST_LSM6DSX_ID_GYRO:
+ case ST_LSM6DSX_ID_TEMP:
+ case ST_LSM6DSX_ID_GYRO: {
+ enum st_lsm6dsx_sensor_id odr_dep_map[] = {
+ ST_LSM6DSX_ID_GYRO,
+ ST_LSM6DSX_ID_TEMP,
+ };
+
+ ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_GYRO]);
+ if (st_lsm6dsx_check_odr_dependency(sensor, odr_dep_map,
+ ARRAY_SIZE(odr_dep_map),
+ req_odr))
+ return 0;
break;
+ }
case ST_LSM6DSX_ID_EXT0:
case ST_LSM6DSX_ID_EXT1:
case ST_LSM6DSX_ID_EXT2:
case ST_LSM6DSX_ID_ACC: {
- u32 odr;
- int i;
+ enum st_lsm6dsx_sensor_id odr_dep_map[] = {
+ ST_LSM6DSX_ID_ACC,
+ ST_LSM6DSX_ID_EXT0,
+ ST_LSM6DSX_ID_EXT1,
+ ST_LSM6DSX_ID_EXT2,
+ };

/*
* i2c embedded controller relies on the accelerometer sensor as
@@ -1675,15 +1708,10 @@ st_lsm6dsx_set_odr(struct st_lsm6dsx_sensor *sensor, u32 req_odr)
* communicate with i2c slave devices
*/
ref_sensor = iio_priv(hw->iio_devs[ST_LSM6DSX_ID_ACC]);
- for (i = ST_LSM6DSX_ID_ACC; i < ST_LSM6DSX_ID_MAX; i++) {
- if (!hw->iio_devs[i] || i == sensor->id)
- continue;
-
- odr = st_lsm6dsx_check_odr_dependency(hw, req_odr, i);
- if (odr != req_odr)
- /* device already configured */
- return 0;
- }
+ if (st_lsm6dsx_check_odr_dependency(sensor, odr_dep_map,
+ ARRAY_SIZE(odr_dep_map),
+ req_odr))
+ return 0;
break;
}
default: /* should never occur */

What do you think? If you agree I can post it as preliminary patch (removing temp dependency).

Regards,
Lorenzo

> case ST_LSM6DSX_ID_EXT0:
> case ST_LSM6DSX_ID_EXT1:
> case ST_LSM6DSX_ID_EXT2:
> @@ -1800,6 +1839,10 @@ static int st_lsm6dsx_read_raw(struct iio_dev *iio_dev,
> *val2 = sensor->gain;
> ret = IIO_VAL_INT_PLUS_NANO;
> break;
> + case IIO_CHAN_INFO_OFFSET:
> + *val = sensor->offset;
> + ret = IIO_VAL_INT;
> + break;
> default:
> ret = -EINVAL;
> break;
> @@ -2106,6 +2149,22 @@ static const struct iio_info st_lsm6dsx_gyro_info = {
> .write_raw_get_fmt = st_lsm6dsx_write_raw_get_fmt,
> };
>
> +static struct attribute *st_lsm6dsx_temp_attributes[] = {
> + &iio_dev_attr_sampling_frequency_available.dev_attr.attr,
> + NULL,
> +};
> +
> +static const struct attribute_group st_lsm6dsx_temp_attribute_group = {
> + .attrs = st_lsm6dsx_temp_attributes,
> +};
> +
> +static const struct iio_info st_lsm6dsx_temp_info = {
> + .attrs = &st_lsm6dsx_temp_attribute_group,
> + .read_raw = st_lsm6dsx_read_raw,
> + .write_raw = st_lsm6dsx_write_raw,
> + .hwfifo_set_watermark = st_lsm6dsx_set_watermark,
> +};
> +
> static int st_lsm6dsx_get_drdy_pin(struct st_lsm6dsx_hw *hw, int *drdy_pin)
> {
> struct device *dev = hw->dev;
> @@ -2379,7 +2438,16 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw,
> sensor->id = id;
> sensor->hw = hw;
> sensor->odr = hw->settings->odr_table[id].odr_avl[0].milli_hz;
> - sensor->gain = hw->settings->fs_table[id].fs_avl[0].gain;
> + if (id == ST_LSM6DSX_ID_TEMP) {
> + /*
> + * The temperature sensor has a fixed scale and offset such
> + * that: temp_C = (raw / 256) + 25
> + */
> + sensor->gain = 3906;
> + sensor->offset = 25;
> + } else {
> + sensor->gain = hw->settings->fs_table[id].fs_avl[0].gain;
> + }
> sensor->watermark = 1;
>
> switch (id) {
> @@ -2393,6 +2461,11 @@ static struct iio_dev *st_lsm6dsx_alloc_iiodev(struct st_lsm6dsx_hw *hw,
> scnprintf(sensor->name, sizeof(sensor->name), "%s_gyro",
> name);
> break;
> + case ST_LSM6DSX_ID_TEMP:
> + iio_dev->info = &st_lsm6dsx_temp_info;
> + scnprintf(sensor->name, sizeof(sensor->name), "%s_temp",
> + name);
> + break;
> default:
> return NULL;
> }
>
> ---
> base-commit: 06c2afb862f9da8dc5efa4b6076a0e48c3fbaaa5
> change-id: 20230811-iio-spacex-lsm6ds0-33c9365e94bf
>
> Best regards,
> --
> Linus Walleij <linus.walleij@xxxxxxxxxx>
>

Attachment: signature.asc
Description: PGP signature