From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:04 +0000 (+0000) Subject: iio: imu: inv_icm45600: add buffer support in iio devices X-Git-Tag: v6.19-rc1~65^2~58^2~86 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=06674a72cf7afa540b4d8c5a4ee41d547988fd78;p=thirdparty%2Fkernel%2Flinux.git iio: imu: inv_icm45600: add buffer support in iio devices Add FIFO control functions. Support hwfifo watermark by multiplexing gyro and accel settings. Support hwfifo flush. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile index 4f442b61896e9..72f95bc30d993 100644 --- a/drivers/iio/imu/inv_icm45600/Makefile +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o inv-icm45600-y += inv_icm45600_core.o +inv-icm45600-y += inv_icm45600_buffer.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h index 5f637e2f2ec8f..aac8cd852c12c 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600.h +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h @@ -5,6 +5,7 @@ #define INV_ICM45600_H_ #include +#include #include #include #include @@ -14,6 +15,8 @@ #include #include +#include "inv_icm45600_buffer.h" + #define INV_ICM45600_REG_BANK_MASK GENMASK(15, 8) #define INV_ICM45600_REG_ADDR_MASK GENMASK(7, 0) @@ -94,6 +97,8 @@ struct inv_icm45600_sensor_conf { u8 filter; }; +#define INV_ICM45600_SENSOR_CONF_KEEP_VALUES { U8_MAX, U8_MAX, U8_MAX, U8_MAX } + struct inv_icm45600_conf { struct inv_icm45600_sensor_conf gyro; struct inv_icm45600_sensor_conf accel; @@ -122,6 +127,7 @@ struct inv_icm45600_chip_info { * @indio_accel: accelerometer IIO device. * @chip_info: chip driver data. * @timestamp: interrupt timestamps. + * @fifo: FIFO management structure. * @buffer: data transfer buffer aligned for DMA. */ struct inv_icm45600_state { @@ -138,6 +144,7 @@ struct inv_icm45600_state { s64 gyro; s64 accel; } timestamp; + struct inv_icm45600_fifo fifo; union { u8 buff[2]; __le16 u16; @@ -190,6 +197,7 @@ struct inv_icm45600_sensor_state { #define INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS 0 #define INV_ICM45600_FIFO_CONFIG0_MODE_STREAM 1 #define INV_ICM45600_FIFO_CONFIG0_MODE_STOP_ON_FULL 2 +#define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MASK GENMASK(5, 0) #define INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX 0x1F #define INV_ICM45600_REG_FIFO_CONFIG2 0x0020 diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c new file mode 100644 index 0000000000000..5542ad8af2a64 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 Invensense, Inc. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "inv_icm45600_buffer.h" +#include "inv_icm45600.h" + +/* FIFO header: 1 byte */ +#define INV_ICM45600_FIFO_EXT_HEADER BIT(7) +#define INV_ICM45600_FIFO_HEADER_ACCEL BIT(6) +#define INV_ICM45600_FIFO_HEADER_GYRO BIT(5) +#define INV_ICM45600_FIFO_HEADER_HIGH_RES BIT(4) +#define INV_ICM45600_FIFO_HEADER_TMST_FSYNC GENMASK(3, 2) +#define INV_ICM45600_FIFO_HEADER_ODR_ACCEL BIT(1) +#define INV_ICM45600_FIFO_HEADER_ODR_GYRO BIT(0) + +struct inv_icm45600_fifo_1sensor_packet { + u8 header; + struct inv_icm45600_fifo_sensor_data data; + s8 temp; +} __packed; + +struct inv_icm45600_fifo_2sensors_packet { + u8 header; + struct inv_icm45600_fifo_sensor_data accel; + struct inv_icm45600_fifo_sensor_data gyro; + s8 temp; + __le16 timestamp; +} __packed; + +ssize_t inv_icm45600_fifo_decode_packet(const void *packet, + const struct inv_icm45600_fifo_sensor_data **accel, + const struct inv_icm45600_fifo_sensor_data **gyro, + const s8 **temp, + const __le16 **timestamp, unsigned int *odr) +{ + const struct inv_icm45600_fifo_1sensor_packet *pack1 = packet; + const struct inv_icm45600_fifo_2sensors_packet *pack2 = packet; + u8 header = *((const u8 *)packet); + + /* FIFO extended header */ + if (header & INV_ICM45600_FIFO_EXT_HEADER) { + /* Not yet supported */ + return 0; + } + + /* handle odr flags. */ + *odr = 0; + if (header & INV_ICM45600_FIFO_HEADER_ODR_GYRO) + *odr |= INV_ICM45600_SENSOR_GYRO; + if (header & INV_ICM45600_FIFO_HEADER_ODR_ACCEL) + *odr |= INV_ICM45600_SENSOR_ACCEL; + + /* Accel + Gyro data are present. */ + if ((header & INV_ICM45600_FIFO_HEADER_ACCEL) && + (header & INV_ICM45600_FIFO_HEADER_GYRO)) { + *accel = &pack2->accel; + *gyro = &pack2->gyro; + *temp = &pack2->temp; + *timestamp = &pack2->timestamp; + return sizeof(*pack2); + } + + /* Accel data only. */ + if (header & INV_ICM45600_FIFO_HEADER_ACCEL) { + *accel = &pack1->data; + *gyro = NULL; + *temp = &pack1->temp; + *timestamp = NULL; + return sizeof(*pack1); + } + + /* Gyro data only. */ + if (header & INV_ICM45600_FIFO_HEADER_GYRO) { + *accel = NULL; + *gyro = &pack1->data; + *temp = &pack1->temp; + *timestamp = NULL; + return sizeof(*pack1); + } + + /* Invalid packet if here. */ + return -EINVAL; +} + +void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st) +{ + u32 period_gyro, period_accel; + + if (st->fifo.en & INV_ICM45600_SENSOR_GYRO) + period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr); + else + period_gyro = U32_MAX; + + if (st->fifo.en & INV_ICM45600_SENSOR_ACCEL) + period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr); + else + period_accel = U32_MAX; + + st->fifo.period = min(period_gyro, period_accel); +} + +int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st, + unsigned int fifo_en) +{ + unsigned int mask; + int ret; + + mask = INV_ICM45600_FIFO_CONFIG3_GYRO_EN | + INV_ICM45600_FIFO_CONFIG3_ACCEL_EN; + + ret = regmap_assign_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, mask, + (fifo_en & INV_ICM45600_SENSOR_GYRO) || + (fifo_en & INV_ICM45600_SENSOR_ACCEL)); + if (ret) + return ret; + + st->fifo.en = fifo_en; + inv_icm45600_buffer_update_fifo_period(st); + + return 0; +} + +static unsigned int inv_icm45600_wm_truncate(unsigned int watermark, size_t packet_size, + unsigned int fifo_period) +{ + size_t watermark_max, grace_samples; + + /* Keep 20ms for processing FIFO.*/ + grace_samples = (20U * NSEC_PER_MSEC) / fifo_period; + if (grace_samples < 1) + grace_samples = 1; + + watermark_max = INV_ICM45600_FIFO_SIZE_MAX / packet_size; + watermark_max -= grace_samples; + + return min(watermark, watermark_max); +} + +/** + * inv_icm45600_buffer_update_watermark - update watermark FIFO threshold + * @st: driver internal state + * + * FIFO watermark threshold is computed based on the required watermark values + * set for gyro and accel sensors. Since watermark is all about acceptable data + * latency, use the smallest setting between the 2. It means choosing the + * smallest latency but this is not as simple as choosing the smallest watermark + * value. Latency depends on watermark and ODR. It requires several steps: + * 1) compute gyro and accel latencies and choose the smallest value. + * 2) adapt the chosen latency so that it is a multiple of both gyro and accel + * ones. Otherwise it is possible that you don't meet a requirement. (for + * example with gyro @100Hz wm 4 and accel @100Hz with wm 6, choosing the + * value of 4 will not meet accel latency requirement because 6 is not a + * multiple of 4. You need to use the value 2.) + * 3) Since all periods are multiple of each others, watermark is computed by + * dividing this computed latency by the smallest period, which corresponds + * to the FIFO frequency. + * + * Returns: 0 on success, a negative error code otherwise. + */ +int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st) +{ + const size_t packet_size = sizeof(struct inv_icm45600_fifo_2sensors_packet); + unsigned int wm_gyro, wm_accel, watermark; + u32 period_gyro, period_accel, period; + u32 latency_gyro, latency_accel, latency; + + /* Compute sensors latency, depending on sensor watermark and odr. */ + wm_gyro = inv_icm45600_wm_truncate(st->fifo.watermark.gyro, packet_size, + st->fifo.period); + wm_accel = inv_icm45600_wm_truncate(st->fifo.watermark.accel, packet_size, + st->fifo.period); + /* Use us for odr to avoid overflow using 32 bits values. */ + period_gyro = inv_icm45600_odr_to_period(st->conf.gyro.odr) / NSEC_PER_USEC; + period_accel = inv_icm45600_odr_to_period(st->conf.accel.odr) / NSEC_PER_USEC; + latency_gyro = period_gyro * wm_gyro; + latency_accel = period_accel * wm_accel; + + /* 0 value for watermark means that the sensor is turned off. */ + if (wm_gyro == 0 && wm_accel == 0) + return 0; + + if (latency_gyro == 0) { + watermark = wm_accel; + st->fifo.watermark.eff_accel = wm_accel; + } else if (latency_accel == 0) { + watermark = wm_gyro; + st->fifo.watermark.eff_gyro = wm_gyro; + } else { + /* Compute the smallest latency that is a multiple of both. */ + if (latency_gyro <= latency_accel) + latency = latency_gyro - (latency_accel % latency_gyro); + else + latency = latency_accel - (latency_gyro % latency_accel); + /* Use the shortest period. */ + period = min(period_gyro, period_accel); + /* All this works because periods are multiple of each others. */ + watermark = max(latency / period, 1); + /* Update effective watermark. */ + st->fifo.watermark.eff_gyro = max(latency / period_gyro, 1); + st->fifo.watermark.eff_accel = max(latency / period_accel, 1); + } + + st->buffer.u16 = cpu_to_le16(watermark); + return regmap_bulk_write(st->map, INV_ICM45600_REG_FIFO_WATERMARK, + &st->buffer.u16, sizeof(st->buffer.u16)); +} + +static int inv_icm45600_buffer_preenable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + struct inv_icm45600_sensor_state *sensor_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &sensor_st->ts; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + guard(mutex)(&st->lock); + inv_sensors_timestamp_reset(ts); + + return 0; +} + +/* + * Update_scan_mode callback is turning sensors on and setting data FIFO enable + * bits. + */ +static int inv_icm45600_buffer_postenable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + unsigned int val; + int ret; + + guard(mutex)(&st->lock); + + /* Exit if FIFO is already on. */ + if (st->fifo.on) { + st->fifo.on++; + return 0; + } + + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2, + INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH); + if (ret) + return ret; + + ret = regmap_set_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0, + INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN | + INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_STREAM); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + + /* Enable writing sensor data to FIFO. */ + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + + st->fifo.on++; + return 0; +} + +static int inv_icm45600_buffer_predisable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + unsigned int val; + int ret; + + guard(mutex)(&st->lock); + + /* Exit if there are several sensors using the FIFO. */ + if (st->fifo.on > 1) { + st->fifo.on--; + return 0; + } + + /* Disable writing sensor data to FIFO. */ + ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + + ret = regmap_clear_bits(st->map, INV_ICM45600_REG_INT1_CONFIG0, + INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN | + INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN); + if (ret) + return ret; + + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2, + INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH); + if (ret) + return ret; + + st->fifo.on--; + return 0; +} + +static int _inv_icm45600_buffer_postdisable(struct inv_icm45600_state *st, + unsigned int sensor, unsigned int *watermark, + unsigned int *sleep) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + ret = inv_icm45600_buffer_set_fifo_en(st, st->fifo.en & ~sensor); + if (ret) + return ret; + + *watermark = 0; + ret = inv_icm45600_buffer_update_watermark(st); + if (ret) + return ret; + + conf.mode = INV_ICM45600_SENSOR_MODE_OFF; + if (sensor == INV_ICM45600_SENSOR_GYRO) + return inv_icm45600_set_gyro_conf(st, &conf, sleep); + else + return inv_icm45600_set_accel_conf(st, &conf, sleep); +} + +static int inv_icm45600_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int sensor; + unsigned int *watermark; + unsigned int sleep; + int ret; + + if (indio_dev == st->indio_gyro) { + sensor = INV_ICM45600_SENSOR_GYRO; + watermark = &st->fifo.watermark.gyro; + } else if (indio_dev == st->indio_accel) { + sensor = INV_ICM45600_SENSOR_ACCEL; + watermark = &st->fifo.watermark.accel; + } else { + return -EINVAL; + } + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_buffer_postdisable(st, sensor, watermark, &sleep); + + /* Sleep required time. */ + if (sleep) + msleep(sleep); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +const struct iio_buffer_setup_ops inv_icm45600_buffer_ops = { + .preenable = inv_icm45600_buffer_preenable, + .postenable = inv_icm45600_buffer_postenable, + .predisable = inv_icm45600_buffer_predisable, + .postdisable = inv_icm45600_buffer_postdisable, +}; + +int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st) +{ + const ssize_t packet_size = sizeof(struct inv_icm45600_fifo_2sensors_packet); + __le16 *raw_fifo_count; + size_t fifo_nb, i; + ssize_t size; + const struct inv_icm45600_fifo_sensor_data *accel, *gyro; + const __le16 *timestamp; + const s8 *temp; + unsigned int odr; + int ret; + + /* Reset all samples counters. */ + st->fifo.count = 0; + st->fifo.nb.gyro = 0; + st->fifo.nb.accel = 0; + st->fifo.nb.total = 0; + + raw_fifo_count = &st->buffer.u16; + ret = regmap_bulk_read(st->map, INV_ICM45600_REG_FIFO_COUNT, + raw_fifo_count, sizeof(*raw_fifo_count)); + if (ret) + return ret; + + /* Check and limit number of samples if requested. */ + fifo_nb = le16_to_cpup(raw_fifo_count); + if (fifo_nb == 0) + return 0; + + /* Try to read all FIFO data in internal buffer. */ + st->fifo.count = fifo_nb * packet_size; + ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA, + st->fifo.data, st->fifo.count); + if (ret == -ENOTSUPP || ret == -EFBIG) { + /* Read full fifo is not supported, read samples one by one. */ + ret = 0; + for (i = 0; i < st->fifo.count && ret == 0; i += packet_size) + ret = regmap_noinc_read(st->map, INV_ICM45600_REG_FIFO_DATA, + &st->fifo.data[i], packet_size); + } + if (ret) + return ret; + + for (i = 0; i < st->fifo.count; i += size) { + size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i], &accel, &gyro, + &temp, ×tamp, &odr); + if (size <= 0) + /* No more sample in buffer */ + break; + if (gyro && inv_icm45600_fifo_is_data_valid(gyro)) + st->fifo.nb.gyro++; + if (accel && inv_icm45600_fifo_is_data_valid(accel)) + st->fifo.nb.accel++; + st->fifo.nb.total++; + } + + return 0; +} + +int inv_icm45600_buffer_init(struct inv_icm45600_state *st) +{ + int ret; + unsigned int val; + + st->fifo.watermark.eff_gyro = 1; + st->fifo.watermark.eff_accel = 1; + + /* Disable all FIFO EN bits. */ + ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG3, 0); + if (ret) + return ret; + + /* Disable FIFO and set depth. */ + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS) | + FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MASK, + INV_ICM45600_FIFO_CONFIG0_FIFO_DEPTH_MAX); + + ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG0, val); + if (ret) + return ret; + + /* Enable only timestamp in fifo, disable compression. */ + ret = regmap_write(st->map, INV_ICM45600_REG_FIFO_CONFIG4, + INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN); + if (ret) + return ret; + + /* Enable FIFO continuous watermark interrupt. */ + return regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG2, + INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH); +} diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h new file mode 100644 index 0000000000000..b4c700fa4e9f1 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2025 Invensense, Inc. */ + +#ifndef INV_ICM45600_BUFFER_H_ +#define INV_ICM45600_BUFFER_H_ + +#include +#include +#include + +#include + +#include + +struct inv_icm45600_state; + +#define INV_ICM45600_SENSOR_GYRO BIT(0) +#define INV_ICM45600_SENSOR_ACCEL BIT(1) +#define INV_ICM45600_SENSOR_TEMP BIT(2) + +/** + * struct inv_icm45600_fifo - FIFO state variables + * @on: reference counter for FIFO on. + * @en: bits field of INV_ICM45600_SENSOR_* for FIFO EN bits. + * @period: FIFO internal period. + * @watermark: watermark configuration values for accel and gyro. + * @watermark.gyro: requested watermark for gyro. + * @watermark.accel: requested watermark for accel. + * @watermark.eff_gyro: effective watermark for gyro. + * @watermark.eff_accel: effective watermark for accel. + * @count: number of bytes in the FIFO data buffer. + * @nb: gyro, accel and total samples in the FIFO data buffer. + * @data: FIFO data buffer aligned for DMA (8kB) + */ +struct inv_icm45600_fifo { + unsigned int on; + unsigned int en; + u32 period; + struct { + unsigned int gyro; + unsigned int accel; + unsigned int eff_gyro; + unsigned int eff_accel; + } watermark; + size_t count; + struct { + size_t gyro; + size_t accel; + size_t total; + } nb; + u8 *data; +}; + +/* FIFO data packet */ +struct inv_icm45600_fifo_sensor_data { + __le16 x; + __le16 y; + __le16 z; +} __packed; +#define INV_ICM45600_DATA_INVALID S16_MIN + +static inline bool +inv_icm45600_fifo_is_data_valid(const struct inv_icm45600_fifo_sensor_data *s) +{ + s16 x, y, z; + + x = le16_to_cpu(s->x); + y = le16_to_cpu(s->y); + z = le16_to_cpu(s->z); + + return (x != INV_ICM45600_DATA_INVALID || + y != INV_ICM45600_DATA_INVALID || + z != INV_ICM45600_DATA_INVALID); +} + +ssize_t inv_icm45600_fifo_decode_packet(const void *packet, + const struct inv_icm45600_fifo_sensor_data **accel, + const struct inv_icm45600_fifo_sensor_data **gyro, + const s8 **temp, + const __le16 **timestamp, unsigned int *odr); + +extern const struct iio_buffer_setup_ops inv_icm45600_buffer_ops; + +int inv_icm45600_buffer_init(struct inv_icm45600_state *st); + +void inv_icm45600_buffer_update_fifo_period(struct inv_icm45600_state *st); + +int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st, + unsigned int fifo_en); + +int inv_icm45600_buffer_update_watermark(struct inv_icm45600_state *st); + +int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st); + +int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st, + unsigned int count); + +#endif diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c index 280cdd40f86bb..118450b3c9f95 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c @@ -5,11 +5,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -19,6 +22,7 @@ #include +#include "inv_icm45600_buffer.h" #include "inv_icm45600.h" static int inv_icm45600_ireg_read(struct regmap *map, unsigned int reg, @@ -435,6 +439,94 @@ static int inv_icm45600_setup(struct inv_icm45600_state *st, return inv_icm45600_set_conf(st, chip_info->conf); } +static irqreturn_t inv_icm45600_irq_timestamp(int irq, void *_data) +{ + struct inv_icm45600_state *st = _data; + + st->timestamp.gyro = iio_get_time_ns(st->indio_gyro); + st->timestamp.accel = iio_get_time_ns(st->indio_accel); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t inv_icm45600_irq_handler(int irq, void *_data) +{ + struct inv_icm45600_state *st = _data; + struct device *dev = regmap_get_device(st->map); + unsigned int mask, status; + int ret; + + guard(mutex)(&st->lock); + + ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &status); + if (ret) + return IRQ_HANDLED; + + /* Read the FIFO data. */ + mask = INV_ICM45600_INT_STATUS_FIFO_THS | INV_ICM45600_INT_STATUS_FIFO_FULL; + if (status & mask) { + ret = inv_icm45600_buffer_fifo_read(st); + if (ret) { + dev_err(dev, "FIFO read error %d\n", ret); + return IRQ_HANDLED; + } + } + + /* FIFO full warning. */ + if (status & INV_ICM45600_INT_STATUS_FIFO_FULL) + dev_warn(dev, "FIFO full possible data lost!\n"); + + return IRQ_HANDLED; +} + +/** + * inv_icm45600_irq_init() - initialize int pin and interrupt handler + * @st: driver internal state + * @irq: irq number + * @irq_type: irq trigger type + * @open_drain: true if irq is open drain, false for push-pull + * + * Returns: 0 on success, a negative error code otherwise. + */ +static int inv_icm45600_irq_init(struct inv_icm45600_state *st, int irq, + int irq_type, bool open_drain) +{ + struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* Configure INT1 interrupt: default is active low on edge. */ + switch (irq_type) { + case IRQF_TRIGGER_RISING: + case IRQF_TRIGGER_HIGH: + val = INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH; + break; + default: + val = INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW; + break; + } + + switch (irq_type) { + case IRQF_TRIGGER_LOW: + case IRQF_TRIGGER_HIGH: + val |= INV_ICM45600_INT1_CONFIG2_LATCHED; + break; + default: + break; + } + + if (!open_drain) + val |= INV_ICM45600_INT1_CONFIG2_PUSH_PULL; + + ret = regmap_write(st->map, INV_ICM45600_REG_INT1_CONFIG2, val); + if (ret) + return ret; + + return devm_request_threaded_irq(dev, irq, inv_icm45600_irq_timestamp, + inv_icm45600_irq_handler, irq_type | IRQF_ONESHOT, + "inv_icm45600", st); +} + static int inv_icm45600_timestamp_setup(struct inv_icm45600_state *st) { /* Enable timestamps. */ @@ -476,8 +568,21 @@ int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chi struct device *dev = regmap_get_device(regmap); struct inv_icm45600_state *st; struct regmap *regmap_custom; + struct fwnode_handle *fwnode; + int irq, irq_type; + bool open_drain; int ret; + /* Get INT1 only supported interrupt. */ + fwnode = dev_fwnode(dev); + irq = fwnode_irq_get_byname(fwnode, "int1"); + if (irq < 0) + return dev_err_probe(dev, irq, "Missing int1 interrupt\n"); + + irq_type = irq_get_trigger_type(irq); + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus, regmap, &inv_icm45600_regmap_config); if (IS_ERR(regmap_custom)) @@ -489,6 +594,10 @@ int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chi dev_set_drvdata(dev, st); + st->fifo.data = devm_kzalloc(dev, 8192, GFP_KERNEL); + if (!st->fifo.data) + return -ENOMEM; + ret = devm_mutex_init(dev, &st->lock); if (ret) return ret; @@ -529,6 +638,14 @@ int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chi if (ret) return ret; + ret = inv_icm45600_buffer_init(st); + if (ret) + return ret; + + ret = inv_icm45600_irq_init(st, irq, irq_type, open_drain); + if (ret) + return ret; + ret = devm_pm_runtime_set_active_enabled(dev); if (ret) return ret; @@ -548,8 +665,26 @@ EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600"); static int inv_icm45600_suspend(struct device *dev) { struct inv_icm45600_state *st = dev_get_drvdata(dev); + int ret; scoped_guard(mutex, &st->lock) { + /* Disable FIFO data streaming. */ + if (st->fifo.on) { + unsigned int val; + + /* Clear FIFO_CONFIG3_IF_EN before changing the FIFO configuration */ + ret = regmap_clear_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_BYPASS); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + } + /* Save sensors states */ st->suspended.gyro = st->conf.gyro.mode; st->suspended.accel = st->conf.accel.mode; @@ -575,6 +710,29 @@ static int inv_icm45600_resume(struct device *dev) /* Restore sensors state. */ ret = inv_icm45600_set_pwr_mgmt0(st, st->suspended.gyro, st->suspended.accel, NULL); + if (ret) + return ret; + + /* Restore FIFO data streaming. */ + if (st->fifo.on) { + struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel); + unsigned int val; + + inv_sensors_timestamp_reset(&gyro_st->ts); + inv_sensors_timestamp_reset(&accel_st->ts); + val = FIELD_PREP(INV_ICM45600_FIFO_CONFIG0_MODE_MASK, + INV_ICM45600_FIFO_CONFIG0_MODE_STREAM); + ret = regmap_update_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG0, + INV_ICM45600_FIFO_CONFIG0_MODE_MASK, val); + if (ret) + return ret; + /* FIFO_CONFIG3_IF_EN must only be set at end of FIFO the configuration */ + ret = regmap_set_bits(st->map, INV_ICM45600_REG_FIFO_CONFIG3, + INV_ICM45600_FIFO_CONFIG3_IF_EN); + if (ret) + return ret; + } } return ret;