From 5f31df5075a7f8967401ded5d54be04f62b172a2 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 17 Sep 2025 18:03:17 -0500 Subject: [PATCH 001/150] iio: adc: ad7124: add debugfs to disable single cycle mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a boolean debugfs attribute to allow disabling the SINGLE_CYCLE bit in the FILTER registers. This causes data to be read on every conversion instead of doing the usual 3 or 4 conversions per sample (depending on the filter). This is only needed for very specific use cases, such as validating the performance of the ADC. So we just expose this feature through debugfs for the rare cases where it is needed by people who really know what they are doing. Signed-off-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index 910b40393f77..ad6edbc792db 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -223,6 +224,7 @@ struct ad7124_state { */ unsigned int gain_default; DECLARE_KFIFO(live_cfgs_fifo, struct ad7124_channel_config *, AD7124_MAX_CONFIGS); + bool enable_single_cycle; }; static const struct ad7124_chip_info ad7124_4_chip_info = { @@ -560,13 +562,15 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co * sampling frequency even when only one channel is enabled in a * buffered read. If it was not set, the N in ad7124_set_channel_odr() * would be 1 and we would get a faster sampling frequency than what - * was requested. + * was requested. It may only be disabled through debugfs for testing + * purposes. */ return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg->cfg_slot), 3, FIELD_PREP(AD7124_FILTER_FILTER, filter) | FIELD_PREP(AD7124_FILTER_REJ60, rej60) | FIELD_PREP(AD7124_FILTER_POST_FILTER, post) | - AD7124_FILTER_SINGLE_CYCLE | + FIELD_PREP(AD7124_FILTER_SINGLE_CYCLE, + st->enable_single_cycle) | FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits)); } @@ -1609,6 +1613,18 @@ static void ad7124_reg_disable(void *r) regulator_disable(r); } +static void ad7124_debugfs_init(struct iio_dev *indio_dev) +{ + struct dentry *dentry = iio_get_debugfs_dentry(indio_dev); + struct ad7124_state *st = iio_priv(indio_dev); + + if (!IS_ENABLED(CONFIG_DEBUG_FS)) + return; + + debugfs_create_bool("enable_single_cycle", 0644, dentry, + &st->enable_single_cycle); +} + static int ad7124_probe(struct spi_device *spi) { const struct ad7124_chip_info *info; @@ -1629,6 +1645,9 @@ static int ad7124_probe(struct spi_device *spi) st->chip_info = info; + /* Only disabled for debug/testing purposes. */ + st->enable_single_cycle = true; + indio_dev->name = st->chip_info->name; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->info = &ad7124_info; @@ -1686,6 +1705,8 @@ static int ad7124_probe(struct spi_device *spi) if (ret < 0) return dev_err_probe(dev, ret, "Failed to register iio device\n"); + ad7124_debugfs_init(indio_dev); + return 0; } From 0b02af932b7e7777074d78203e8e336a9dc30400 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 17 Sep 2025 15:39:22 -0500 Subject: [PATCH 002/150] iio: adc: ad7124: inline ad7124_enable_channel() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inline ad7124_enable_channel() at the only call site. This simplifies the code by avoiding a bit of extra indirection. ch->nr is replaced by address as that is the same value and avoids more indirection. Signed-off-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index ad6edbc792db..f4f445e28630 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -631,14 +631,6 @@ static int ad7124_push_config(struct ad7124_state *st, struct ad7124_channel_con return ad7124_write_config(st, cfg, free_cfg_slot); } -static int ad7124_enable_channel(struct ad7124_state *st, struct ad7124_channel *ch) -{ - ch->cfg.live = true; - return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(ch->nr), 2, ch->ain | - FIELD_PREP(AD7124_CHANNEL_SETUP, ch->cfg.cfg_slot) | - AD7124_CHANNEL_ENABLE); -} - static int ad7124_prepare_read(struct ad7124_state *st, int address) { struct ad7124_channel_config *cfg = &st->channels[address].cfg; @@ -658,7 +650,11 @@ static int ad7124_prepare_read(struct ad7124_state *st, int address) } /* point channel to the config slot and enable */ - return ad7124_enable_channel(st, &st->channels[address]); + cfg->live = true; + return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(address), 2, + st->channels[address].ain | + FIELD_PREP(AD7124_CHANNEL_SETUP, cfg->cfg_slot) | + AD7124_CHANNEL_ENABLE); } static int __ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel) @@ -1559,7 +1555,7 @@ static int __ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio * after full-scale calibration because the next * ad_sd_calibrate() call overwrites this via * ad_sigma_delta_set_channel() -> ad7124_set_channel() - * ... -> ad7124_enable_channel(). + * -> ad7124_prepare_read(). */ ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(st->channels[i].cfg.cfg_slot), 3, &st->channels[i].cfg.calibration_gain); From 4a579c175aad6d1b5d2288f4db366261c5c4f60b Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 17 Sep 2025 15:39:23 -0500 Subject: [PATCH 003/150] iio: adc: ad7124: remove unused `nr` field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the unused `nr` field from the `ad7124_channel` struct. There are no more users of this field (it is only assigned to but never read) so can be removed. Signed-off-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index f4f445e28630..950abdb75e6d 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -200,7 +200,6 @@ struct ad7124_channel_config { }; struct ad7124_channel { - unsigned int nr; struct ad7124_channel_config cfg; unsigned int ain; unsigned int slot; @@ -1305,7 +1304,6 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev, return dev_err_probe(dev, -EINVAL, "diff-channels property of %pfwP contains invalid data\n", child); - st->channels[channel].nr = channel; st->channels[channel].ain = FIELD_PREP(AD7124_CHANNEL_AINP, ain[0]) | FIELD_PREP(AD7124_CHANNEL_AINM, ain[1]); @@ -1332,7 +1330,6 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev, if (num_channels < AD7124_MAX_CHANNELS) { st->channels[num_channels] = (struct ad7124_channel) { - .nr = num_channels, .ain = FIELD_PREP(AD7124_CHANNEL_AINP, AD7124_CHANNEL_AINx_TEMPSENSOR) | FIELD_PREP(AD7124_CHANNEL_AINM, AD7124_CHANNEL_AINx_AVSS), .cfg = { From b8579b7c6e484f399f14bca2a9d2ca83a3c9b483 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 17 Sep 2025 09:17:23 -0500 Subject: [PATCH 004/150] iio: adc: ad7124: use AD7124_MAX_CHANNELS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use AD7124_MAX_CHANNELS macro instead of hardcoding 16 in ad7124_disable_all(). We already have the macro, so we should use it. Signed-off-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index 950abdb75e6d..b13a7824ae01 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -708,7 +708,7 @@ static int ad7124_disable_all(struct ad_sigma_delta *sd) int ret; int i; - for (i = 0; i < 16; i++) { + for (i = 0; i < AD7124_MAX_CHANNELS; i++) { ret = ad7124_disable_one(sd, i); if (ret < 0) return ret; From 0b028373f87a354ad5f7665034f71007b8f3d9e2 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 17 Sep 2025 10:22:30 -0500 Subject: [PATCH 005/150] iio: adc: ad7124: use devm_mutex_init() Use devm_mutex_init() to initialize the mutex to handle automatically freeing in debug builds. Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index b13a7824ae01..dff935360e6d 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -1486,7 +1486,10 @@ static int ad7124_setup(struct ad7124_state *st) st->adc_control &= ~AD7124_ADC_CONTROL_MODE; st->adc_control |= FIELD_PREP(AD7124_ADC_CONTROL_MODE, AD_SD_MODE_IDLE); - mutex_init(&st->cfgs_lock); + ret = devm_mutex_init(dev, &st->cfgs_lock); + if (ret) + return ret; + INIT_KFIFO(st->live_cfgs_fifo); for (i = 0; i < st->num_channels; i++) { struct ad7124_channel_config *cfg = &st->channels[i].cfg; From 97c8b5dedb407d389e7a947b8c037087a919aadc Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:39:39 -0500 Subject: [PATCH 006/150] iio: adc: ad7124: remove __ad7124_set_channel() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove __ad7124_set_channel() wrapper function. This just added an unnecessary layer of indirection with an extra call to container_of(). Signed-off-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index dff935360e6d..374e39736584 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -656,20 +656,13 @@ static int ad7124_prepare_read(struct ad7124_state *st, int address) AD7124_CHANNEL_ENABLE); } -static int __ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel) -{ - struct ad7124_state *st = container_of(sd, struct ad7124_state, sd); - - return ad7124_prepare_read(st, channel); -} - static int ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel) { struct ad7124_state *st = container_of(sd, struct ad7124_state, sd); int ret; mutex_lock(&st->cfgs_lock); - ret = __ad7124_set_channel(sd, channel); + ret = ad7124_prepare_read(st, channel); mutex_unlock(&st->cfgs_lock); return ret; @@ -964,7 +957,7 @@ static int ad7124_update_scan_mode(struct iio_dev *indio_dev, for (i = 0; i < st->num_channels; i++) { bit_set = test_bit(i, scan_mask); if (bit_set) - ret = __ad7124_set_channel(&st->sd, i); + ret = ad7124_prepare_read(st, i); else ret = ad7124_spi_write_mask(st, AD7124_CHANNEL(i), AD7124_CHANNEL_ENABLE, 0, 2); From 6b166e815cdcf2ddb755ca5311a5343635ff9610 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:02:51 -0500 Subject: [PATCH 007/150] iio: buffer: document iio_push_to_buffers_with_ts_unaligned() may sleep Add Context: documentation comment that iio_push_to_buffers_with_ts_unaligned() may sleep because it calls devm_krealloc(). Also document Return: value while here. Suggested-by: Andy Shevchenko Reviewed-by: Andy Shevchenko Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index a80f7cc25a27..7da43a1f2f75 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -2401,6 +2401,9 @@ EXPORT_SYMBOL_GPL(iio_push_to_buffers); * not require space for the timestamp, or 8 byte alignment of data. * It does however require an allocation on first call and additional * copies on all calls, so should be avoided if possible. + * + * Context: May sleep. + * Return: 0 on success, negative error code on failure. */ int iio_push_to_buffers_with_ts_unaligned(struct iio_dev *indio_dev, const void *data, From a8c8aad411554755ab59621dc6961db8a4c8af7a Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:02:52 -0500 Subject: [PATCH 008/150] iio: buffer: iio_push_to_buffers_with_ts_unaligned() might_sleep() Call might_sleep() in iio_push_to_buffers_with_ts_unaligned() since it can allocate memory, which may sleep. Suggested-by: Andy Shevchenko Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 7da43a1f2f75..5599fa37b698 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -2412,6 +2412,8 @@ int iio_push_to_buffers_with_ts_unaligned(struct iio_dev *indio_dev, { struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev); + might_sleep(); + /* * Conservative estimate - we can always safely copy the minimum * of either the data provided or the length of the destination buffer. From 536bf30d282a6b2f676c6106587f0e1946449aca Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:02:53 -0500 Subject: [PATCH 009/150] iio: buffer: document iio_push_to_buffers_with_ts() Document the iio_push_to_buffers_with_ts() function. This is copied and slightly cleaned up from iio_push_to_buffers_with_timestamp(). Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- include/linux/iio/buffer.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/linux/iio/buffer.h b/include/linux/iio/buffer.h index 5c84ec4a9810..e46b818981aa 100644 --- a/include/linux/iio/buffer.h +++ b/include/linux/iio/buffer.h @@ -45,6 +45,22 @@ static inline int iio_push_to_buffers_with_timestamp(struct iio_dev *indio_dev, return iio_push_to_buffers(indio_dev, data); } +/** + * iio_push_to_buffers_with_ts() - push data and timestamp to buffers + * @indio_dev: iio_dev structure for device. + * @data: Pointer to sample data buffer. + * @data_total_len: The size of @data in bytes. + * @timestamp: Timestamp for the sample data. + * + * Pushes data to the IIO device's buffers. If timestamps are enabled for the + * device the function will store the supplied timestamp as the last element in + * the sample data buffer before pushing it to the device buffers. The sample + * data buffer needs to be large enough to hold the additional timestamp + * (usually the buffer should be at least indio->scan_bytes bytes large). + * + * Context: Any context. + * Return: 0 on success, a negative error code otherwise. + */ static inline int iio_push_to_buffers_with_ts(struct iio_dev *indio_dev, void *data, size_t data_total_len, s64 timestamp) From 4992ce003b76ee1629ad4e7332a49ea2619e7523 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:02:54 -0500 Subject: [PATCH 010/150] iio: buffer: deprecated iio_push_to_buffers_with_timestamp() Replace the documentation of iio_push_to_buffers_with_timestamp() with a deprecation notice pointing to the preferred alternative. Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- include/linux/iio/buffer.h | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/include/linux/iio/buffer.h b/include/linux/iio/buffer.h index e46b818981aa..d37f82678f71 100644 --- a/include/linux/iio/buffer.h +++ b/include/linux/iio/buffer.h @@ -26,11 +26,7 @@ int iio_pop_from_buffer(struct iio_buffer *buffer, void *data); * @data: sample data * @timestamp: timestamp for the sample data * - * Pushes data to the IIO device's buffers. If timestamps are enabled for the - * device the function will store the supplied timestamp as the last element in - * the sample data buffer before pushing it to the device buffers. The sample - * data buffer needs to be large enough to hold the additional timestamp - * (usually the buffer should be indio->scan_bytes bytes large). + * DEPRECATED: Use iio_push_to_buffers_with_ts() instead. * * Returns 0 on success, a negative error code otherwise. */ From d87b03ced9e989a516cc000e1e65f4eb4d6d46aa Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:02:55 -0500 Subject: [PATCH 011/150] iio: buffer: document iio_push_to_buffers() calling context Document that iio_push_to_buffers() can be called from any context. Also document the Return: value while here. Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index 5599fa37b698..f1448ae1b843 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -2372,6 +2372,9 @@ static int iio_push_to_buffer(struct iio_buffer *buffer, const void *data) * iio_push_to_buffers() - push to a registered buffer. * @indio_dev: iio_dev structure for device. * @data: Full scan. + * + * Context: Any context. + * Return: 0 on success, negative error code on failure. */ int iio_push_to_buffers(struct iio_dev *indio_dev, const void *data) { From 748ed9fc8596015e7e136877465919b89c7d08d6 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:02:56 -0500 Subject: [PATCH 012/150] iio: buffer: document store_to() callback may be called in any context Document that the struct iio_buffer_access_funcs.store_to() callback must be safe to call from any context since it is called from iio_push_to_buffer() which may be called from any context. Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- include/linux/iio/buffer_impl.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/linux/iio/buffer_impl.h b/include/linux/iio/buffer_impl.h index e72552e026f3..0daff9ff20ce 100644 --- a/include/linux/iio/buffer_impl.h +++ b/include/linux/iio/buffer_impl.h @@ -24,7 +24,8 @@ struct sg_table; /** * struct iio_buffer_access_funcs - access functions for buffers. - * @store_to: actually store stuff to the buffer + * @store_to: actually store stuff to the buffer - must be safe to + * call from any context (e.g. must not sleep). * @read: try to get a specified number of bytes (must exist) * @data_available: indicates how much data is available for reading from * the buffer. From 592ae0ccecfac9af8f67444cab11cbb11770f571 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 16 Sep 2025 16:02:57 -0500 Subject: [PATCH 013/150] iio: buffer: document that buffer callback must be context safe Document that the callback registered with iio_channel_get_all_cb() must be safe to call from any context since it is called from by iio_push_to_buffer() which can be called in any context. Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/buffer/industrialio-buffer-cb.c | 1 + include/linux/iio/consumer.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/buffer/industrialio-buffer-cb.c b/drivers/iio/buffer/industrialio-buffer-cb.c index 3e27385069ed..f4ebff968493 100644 --- a/drivers/iio/buffer/industrialio-buffer-cb.c +++ b/drivers/iio/buffer/industrialio-buffer-cb.c @@ -13,6 +13,7 @@ struct iio_cb_buffer { struct iio_buffer buffer; + /* Must be safe to call from any context (e.g. must not sleep). */ int (*cb)(const void *data, void *private); void *private; struct iio_channel *channels; diff --git a/include/linux/iio/consumer.h b/include/linux/iio/consumer.h index a38b277c2c02..5039558267e4 100644 --- a/include/linux/iio/consumer.h +++ b/include/linux/iio/consumer.h @@ -131,7 +131,8 @@ struct iio_cb_buffer; /** * iio_channel_get_all_cb() - register callback for triggered capture * @dev: Pointer to client device. - * @cb: Callback function. + * @cb: Callback function. Must be safe to call from any context + * (e.g. must not sleep). * @private: Private data passed to callback. * * NB right now we have no ability to mux data from multiple devices. From b8af83efd67c2d36142bb411ff5218f02eaf0deb Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sat, 13 Sep 2025 18:39:22 +0300 Subject: [PATCH 014/150] dt-bindings: iio: accel: bosch,bma220 cleanup typo Cleanup typo present in the title. Acked-by: Krzysztof Kozlowski Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml b/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml index ec643de031a3..da047258aca3 100644 --- a/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml +++ b/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml @@ -4,7 +4,7 @@ $id: http://devicetree.org/schemas/iio/accel/bosch,bma220.yaml# $schema: http://devicetree.org/meta-schemas/core.yaml# -title: Bosch BMA220 Trixial Acceleration Sensor +title: Bosch BMA220 Triaxial Acceleration Sensor maintainers: - Jonathan Cameron From b8719569a0971f971a117c67c462360d7bbc0fcc Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sat, 13 Sep 2025 18:39:23 +0300 Subject: [PATCH 015/150] dt-bindings: iio: accel: bosch,bma220 setup SPI clock mode Assert CPOL for a high-idle clock signal and CPHA for sampling on the trailing (rising) edge. Quoting from the datasheet: "During the transitions on CSB, SCK must be high. SDI and SDO are driven at the falling edge of SCK and should be captured at the rising edge of SCK." The sensor does not function with the default SPI clock mode. Fixes: 7dbd479425d2 ("dt-bindings:iio:accel:bosch,bma220 device tree binding documentation") Reviewed-by: Krzysztof Kozlowski Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/accel/bosch,bma220.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml b/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml index da047258aca3..0e27ec74065a 100644 --- a/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml +++ b/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml @@ -20,6 +20,9 @@ properties: interrupts: maxItems: 1 + spi-cpha: true + spi-cpol: true + vdda-supply: true vddd-supply: true vddio-supply: true @@ -44,6 +47,8 @@ examples: compatible = "bosch,bma220"; reg = <0>; spi-max-frequency = <2500000>; + spi-cpol; + spi-cpha; interrupt-parent = <&gpio0>; interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; }; From 92c7ae3486143a44ecc974531704b905d22a784a Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sat, 13 Sep 2025 18:39:24 +0300 Subject: [PATCH 016/150] dt-bindings: iio: accel: bosch,bma220 set irq type in example block Set the interrupt type to rising edge within the example block in order to match the new driver. The entry that got replaced was not in use by the original driver. Signed-off-by: Petre Rodan Acked-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml b/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml index 0e27ec74065a..8c820c27f781 100644 --- a/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml +++ b/Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml @@ -50,7 +50,7 @@ examples: spi-cpol; spi-cpha; interrupt-parent = <&gpio0>; - interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <0 IRQ_TYPE_EDGE_RISING>; }; }; ... From 3c2a24f7e807fda3078e3f87839b09c5ce93c90b Mon Sep 17 00:00:00 2001 From: Gustavo Silva Date: Tue, 16 Sep 2025 20:38:23 -0300 Subject: [PATCH 017/150] iio: imu: bmi270: add support for motion events Any-motion event can be enabled on a per-axis basis and triggers a combined event when motion is detected on any axis. No-motion event is triggered if the rate of change on all axes falls below a specified threshold for a configurable duration. A fake channel is used to report this event. Threshold and duration can be configured from userspace. Signed-off-by: Gustavo Silva Signed-off-by: Jonathan Cameron --- drivers/iio/imu/bmi270/bmi270_core.c | 381 +++++++++++++++++++++++++-- 1 file changed, 361 insertions(+), 20 deletions(-) diff --git a/drivers/iio/imu/bmi270/bmi270_core.c b/drivers/iio/imu/bmi270/bmi270_core.c index 519f1c9d466d..2ad230788532 100644 --- a/drivers/iio/imu/bmi270/bmi270_core.c +++ b/drivers/iio/imu/bmi270/bmi270_core.c @@ -31,6 +31,8 @@ #define BMI270_INT_STATUS_0_REG 0x1c #define BMI270_INT_STATUS_0_STEP_CNT_MSK BIT(1) +#define BMI270_INT_STATUS_0_NOMOTION_MSK BIT(5) +#define BMI270_INT_STATUS_0_MOTION_MSK BIT(6) #define BMI270_INT_STATUS_1_REG 0x1d #define BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK GENMASK(7, 6) @@ -81,6 +83,8 @@ #define BMI270_INT1_MAP_FEAT_REG 0x56 #define BMI270_INT2_MAP_FEAT_REG 0x57 #define BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK BIT(1) +#define BMI270_INT_MAP_FEAT_NOMOTION_MSK BIT(5) +#define BMI270_INT_MAP_FEAT_ANYMOTION_MSK BIT(6) #define BMI270_INT_MAP_DATA_REG 0x58 #define BMI270_INT_MAP_DATA_DRDY_INT1_MSK BIT(2) @@ -106,6 +110,25 @@ #define BMI270_STEP_SC26_RST_CNT_MSK BIT(10) #define BMI270_STEP_SC26_EN_CNT_MSK BIT(12) +#define BMI270_FEAT_MOTION_DURATION_MSK GENMASK(12, 0) +#define BMI270_FEAT_MOTION_X_EN_MSK BIT(13) +#define BMI270_FEAT_MOTION_Y_EN_MSK BIT(14) +#define BMI270_FEAT_MOTION_Z_EN_MSK BIT(15) +#define BMI270_FEAT_MOTION_XYZ_EN_MSK GENMASK(15, 13) +#define BMI270_FEAT_MOTION_THRESHOLD_MSK GENMASK(10, 0) +#define BMI270_FEAT_MOTION_OUT_CONF_MSK GENMASK(14, 11) +#define BMI270_FEAT_MOTION_ENABLE_MSK BIT(15) + +#define BMI270_MOTION_XYZ_MSK GENMASK(2, 0) + +/* See pages 92 and 93 of the datasheet */ +#define BMI270_MOTION_THRES_FULL_SCALE GENMASK(10, 0) +#define BMI270_MOTION_DURAT_SCALE 50 +#define BMI270_MOTION_DURAT_MAX 162 + +/* 9.81 * 1000000 m/s^2 */ +#define BMI270_G_MICRO_M_S_2 9810000 + /* See datasheet section 4.6.14, Temperature Sensor */ #define BMI270_TEMP_OFFSET 11776 #define BMI270_TEMP_SCALE 1953125 @@ -114,6 +137,11 @@ #define BMI270_STEP_COUNTER_FACTOR 20 #define BMI270_STEP_COUNTER_MAX 20460 +#define BMI270_INT_MICRO_TO_RAW(val, val2, scale) \ + ((val) * (scale) + ((val2) * (scale)) / MEGA) +#define BMI270_RAW_TO_MICRO(raw, scale) \ + ((((raw) % (scale)) * MEGA) / scale) + #define BMI260_INIT_DATA_FILE "bmi260-init-data.fw" #define BMI270_INIT_DATA_FILE "bmi270-init-data.fw" @@ -309,6 +337,13 @@ static const struct bmi270_odr_item bmi270_odr_table[] = { }; enum bmi270_feature_reg_id { + /* Page 1 registers */ + BMI270_ANYMO1_REG, + BMI270_ANYMO2_REG, + /* Page 2 registers */ + BMI270_NOMO1_REG, + BMI270_NOMO2_REG, + /* Page 6 registers */ BMI270_SC_26_REG, }; @@ -318,6 +353,22 @@ struct bmi270_feature_reg { }; static const struct bmi270_feature_reg bmi270_feature_regs[] = { + [BMI270_ANYMO1_REG] = { + .page = 1, + .addr = 0x3c, + }, + [BMI270_ANYMO2_REG] = { + .page = 1, + .addr = 0x3e, + }, + [BMI270_NOMO1_REG] = { + .page = 2, + .addr = 0x30, + }, + [BMI270_NOMO2_REG] = { + .page = 2, + .addr = 0x32, + }, [BMI270_SC_26_REG] = { .page = 6, .addr = 0x32, @@ -439,6 +490,121 @@ static int bmi270_step_wtrmrk_en(struct bmi270_data *data, bool state) state)); } +static int bmi270_motion_reg(enum iio_event_type type, enum iio_event_info info) +{ + switch (info) { + case IIO_EV_INFO_PERIOD: + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return BMI270_ANYMO1_REG; + case IIO_EV_TYPE_ROC: + return BMI270_NOMO1_REG; + default: + return -EINVAL; + } + case IIO_EV_INFO_VALUE: + switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return BMI270_ANYMO2_REG; + case IIO_EV_TYPE_ROC: + return BMI270_NOMO2_REG; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int bmi270_anymotion_event_en(struct bmi270_data *data, + struct iio_chan_spec const *chan, + bool state) +{ + u16 axis_msk, axis_field_val, regval; + int ret, irq_reg; + bool axis_en; + + irq_reg = bmi270_int_map_reg(data->irq_pin); + if (irq_reg < 0) + return irq_reg; + + guard(mutex)(&data->mutex); + + ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG, ®val); + if (ret) + return ret; + + switch (chan->channel2) { + case IIO_MOD_X: + axis_msk = BMI270_FEAT_MOTION_X_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_X_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval); + break; + case IIO_MOD_Y: + axis_msk = BMI270_FEAT_MOTION_Y_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Y_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, regval); + break; + case IIO_MOD_Z: + axis_msk = BMI270_FEAT_MOTION_Z_EN_MSK; + axis_field_val = FIELD_PREP(BMI270_FEAT_MOTION_Z_EN_MSK, state); + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, regval) | + FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, regval); + break; + default: + return -EINVAL; + } + + ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG, axis_msk, + axis_field_val); + if (ret) + return ret; + + ret = bmi270_update_feature_reg(data, BMI270_ANYMO2_REG, + BMI270_FEAT_MOTION_ENABLE_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK, + state || axis_en)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, irq_reg, + BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + state || axis_en)); +} + +static int bmi270_nomotion_event_en(struct bmi270_data *data, bool state) +{ + int ret, irq_reg; + + irq_reg = bmi270_int_map_reg(data->irq_pin); + if (irq_reg < 0) + return irq_reg; + + guard(mutex)(&data->mutex); + + ret = bmi270_update_feature_reg(data, BMI270_NOMO1_REG, + BMI270_FEAT_MOTION_XYZ_EN_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK, + state ? BMI270_MOTION_XYZ_MSK : 0)); + if (ret) + return ret; + + ret = bmi270_update_feature_reg(data, BMI270_NOMO2_REG, + BMI270_FEAT_MOTION_ENABLE_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_ENABLE_MSK, + state)); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, irq_reg, + BMI270_INT_MAP_FEAT_NOMOTION_MSK, + FIELD_PREP(BMI270_INT_MAP_FEAT_NOMOTION_MSK, + state)); +} + static int bmi270_set_scale(struct bmi270_data *data, int chan_type, int uscale) { int i; @@ -479,8 +645,6 @@ static int bmi270_get_scale(struct bmi270_data *data, int chan_type, int *scale, unsigned int val; struct bmi270_scale_item bmi270_scale_item; - guard(mutex)(&data->mutex); - switch (chan_type) { case IIO_ACCEL: ret = regmap_read(data->regmap, BMI270_ACC_CONF_RANGE_REG, &val); @@ -614,6 +778,20 @@ static irqreturn_t bmi270_irq_thread_handler(int irq, void *private) if (FIELD_GET(BMI270_INT_STATUS_1_ACC_GYR_DRDY_MSK, status1)) iio_trigger_poll_nested(data->trig); + if (FIELD_GET(BMI270_INT_STATUS_0_MOTION_MSK, status0)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_OR_Y_OR_Z, + IIO_EV_TYPE_MAG_ADAPTIVE, + IIO_EV_DIR_RISING), + timestamp); + + if (FIELD_GET(BMI270_INT_STATUS_0_NOMOTION_MSK, status0)) + iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, + IIO_MOD_X_AND_Y_AND_Z, + IIO_EV_TYPE_ROC, + IIO_EV_DIR_RISING), + timestamp); + if (FIELD_GET(BMI270_INT_STATUS_0_STEP_CNT_MSK, status0)) iio_push_event(indio_dev, IIO_UNMOD_EVENT_CODE(IIO_STEPS, 0, IIO_EV_TYPE_CHANGE, @@ -827,6 +1005,39 @@ static int bmi270_read_avail(struct iio_dev *indio_dev, } } +static ssize_t in_accel_value_available_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev = dev_to_iio_dev(dev); + struct bmi270_data *data = iio_priv(indio_dev); + int ret, scale, uscale; + unsigned int step, max; + + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + max = BMI270_G_MICRO_M_S_2 / uscale; + step = max / BMI270_MOTION_THRES_FULL_SCALE; + + return sysfs_emit(buf, "[0 %u %u]\n", step, max); +} + +static IIO_DEVICE_ATTR_RO(in_accel_value_available, 0); + +static IIO_CONST_ATTR(in_accel_period_available, "[0.0 0.02 162.0]"); + +static struct attribute *bmi270_event_attributes[] = { + &iio_dev_attr_in_accel_value_available.dev_attr.attr, + &iio_const_attr_in_accel_period_available.dev_attr.attr, + NULL +}; + +static const struct attribute_group bmi270_event_attribute_group = { + .attrs = bmi270_event_attributes, +}; + static int bmi270_write_event_config(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -835,6 +1046,10 @@ static int bmi270_write_event_config(struct iio_dev *indio_dev, struct bmi270_data *data = iio_priv(indio_dev); switch (type) { + case IIO_EV_TYPE_MAG_ADAPTIVE: + return bmi270_anymotion_event_en(data, chan, state); + case IIO_EV_TYPE_ROC: + return bmi270_nomotion_event_en(data, state); case IIO_EV_TYPE_CHANGE: return bmi270_step_wtrmrk_en(data, state); default: @@ -848,21 +1063,55 @@ static int bmi270_read_event_config(struct iio_dev *indio_dev, enum iio_event_direction dir) { struct bmi270_data *data = iio_priv(indio_dev); + bool feat_en, axis_en; int ret, reg, regval; + u16 motion_reg; guard(mutex)(&data->mutex); + reg = bmi270_int_map_reg(data->irq_pin); + if (reg < 0) + return reg; + + ret = regmap_read(data->regmap, reg, ®val); + if (ret) + return ret; + switch (chan->type) { case IIO_STEPS: - reg = bmi270_int_map_reg(data->irq_pin); - if (reg) - return reg; + return !!FIELD_GET(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, regval); + case IIO_ACCEL: + switch (type) { + case IIO_EV_TYPE_ROC: + return !!FIELD_GET(BMI270_INT_MAP_FEAT_NOMOTION_MSK, regval); + case IIO_EV_TYPE_MAG_ADAPTIVE: + ret = bmi270_read_feature_reg(data, BMI270_ANYMO1_REG, + &motion_reg); + if (ret) + return ret; - ret = regmap_read(data->regmap, reg, ®val); - if (ret) - return ret; - return FIELD_GET(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK, - regval) ? 1 : 0; + feat_en = FIELD_GET(BMI270_INT_MAP_FEAT_ANYMOTION_MSK, + regval); + switch (chan->channel2) { + case IIO_MOD_X: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_X_EN_MSK, + motion_reg); + break; + case IIO_MOD_Y: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Y_EN_MSK, + motion_reg); + break; + case IIO_MOD_Z: + axis_en = FIELD_GET(BMI270_FEAT_MOTION_Z_EN_MSK, + motion_reg); + break; + default: + return -EINVAL; + } + return axis_en && feat_en; + default: + return -EINVAL; + } default: return -EINVAL; } @@ -876,20 +1125,50 @@ static int bmi270_write_event_value(struct iio_dev *indio_dev, int val, int val2) { struct bmi270_data *data = iio_priv(indio_dev); - unsigned int raw; + unsigned int raw, mask, regval; + int ret, reg, scale, uscale; + u64 tmp; guard(mutex)(&data->mutex); - switch (type) { - case IIO_EV_TYPE_CHANGE: + if (type == IIO_EV_TYPE_CHANGE) { if (!in_range(val, 0, BMI270_STEP_COUNTER_MAX + 1)) return -EINVAL; raw = val / BMI270_STEP_COUNTER_FACTOR; - return bmi270_update_feature_reg(data, BMI270_SC_26_REG, - BMI270_STEP_SC26_WTRMRK_MSK, - FIELD_PREP(BMI270_STEP_SC26_WTRMRK_MSK, - raw)); + mask = BMI270_STEP_SC26_WTRMRK_MSK; + regval = FIELD_PREP(BMI270_STEP_SC26_WTRMRK_MSK, raw); + return bmi270_update_feature_reg(data, BMI270_SC_26_REG, mask, + regval); + } + + reg = bmi270_motion_reg(type, info); + if (reg < 0) + return reg; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + if (!in_range(val, 0, (BMI270_G_MICRO_M_S_2 / uscale) + 1)) + return -EINVAL; + + tmp = (u64)val * BMI270_MOTION_THRES_FULL_SCALE * uscale; + raw = DIV_ROUND_CLOSEST_ULL(tmp, BMI270_G_MICRO_M_S_2); + mask = BMI270_FEAT_MOTION_THRESHOLD_MSK; + regval = FIELD_PREP(BMI270_FEAT_MOTION_THRESHOLD_MSK, raw); + return bmi270_update_feature_reg(data, reg, mask, regval); + case IIO_EV_INFO_PERIOD: + if (!in_range(val, 0, BMI270_MOTION_DURAT_MAX + 1)) + return -EINVAL; + + raw = BMI270_INT_MICRO_TO_RAW(val, val2, + BMI270_MOTION_DURAT_SCALE); + mask = BMI270_FEAT_MOTION_DURATION_MSK; + regval = FIELD_PREP(BMI270_FEAT_MOTION_DURATION_MSK, raw); + return bmi270_update_feature_reg(data, reg, mask, regval); default: return -EINVAL; } @@ -903,14 +1182,14 @@ static int bmi270_read_event_value(struct iio_dev *indio_dev, int *val, int *val2) { struct bmi270_data *data = iio_priv(indio_dev); + int ret, reg, scale, uscale; unsigned int raw; u16 regval; - int ret; + u64 tmp; guard(mutex)(&data->mutex); - switch (type) { - case IIO_EV_TYPE_CHANGE: + if (type == IIO_EV_TYPE_CHANGE) { ret = bmi270_read_feature_reg(data, BMI270_SC_26_REG, ®val); if (ret) return ret; @@ -918,6 +1197,36 @@ static int bmi270_read_event_value(struct iio_dev *indio_dev, raw = FIELD_GET(BMI270_STEP_SC26_WTRMRK_MSK, regval); *val = raw * BMI270_STEP_COUNTER_FACTOR; return IIO_VAL_INT; + } + + reg = bmi270_motion_reg(type, info); + if (reg < 0) + return reg; + + switch (info) { + case IIO_EV_INFO_VALUE: + ret = bmi270_read_feature_reg(data, reg, ®val); + if (ret) + return ret; + + ret = bmi270_get_scale(data, IIO_ACCEL, &scale, &uscale); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_FEAT_MOTION_THRESHOLD_MSK, regval); + tmp = (u64)raw * BMI270_G_MICRO_M_S_2; + *val = DIV_ROUND_CLOSEST_ULL(tmp, + BMI270_MOTION_THRES_FULL_SCALE * uscale); + return IIO_VAL_INT; + case IIO_EV_INFO_PERIOD: + ret = bmi270_read_feature_reg(data, reg, ®val); + if (ret) + return ret; + + raw = FIELD_GET(BMI270_FEAT_MOTION_DURATION_MSK, regval); + *val = raw / BMI270_MOTION_DURAT_SCALE; + *val2 = BMI270_RAW_TO_MICRO(raw, BMI270_MOTION_DURAT_SCALE); + return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } @@ -929,6 +1238,20 @@ static const struct iio_event_spec bmi270_step_wtrmrk_event = { .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) | BIT(IIO_EV_INFO_VALUE), }; +static const struct iio_event_spec bmi270_anymotion_event = { + .type = IIO_EV_TYPE_MAG_ADAPTIVE, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD), +}; + +static const struct iio_event_spec bmi270_nomotion_event = { + .type = IIO_EV_TYPE_ROC, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE), + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD), +}; + static const struct iio_info bmi270_info = { .read_raw = bmi270_read_raw, .write_raw = bmi270_write_raw, @@ -937,6 +1260,7 @@ static const struct iio_info bmi270_info = { .read_event_config = bmi270_read_event_config, .write_event_value = bmi270_write_event_value, .read_event_value = bmi270_read_event_value, + .event_attrs = &bmi270_event_attribute_group, }; #define BMI270_ACCEL_CHANNEL(_axis) { \ @@ -956,6 +1280,8 @@ static const struct iio_info bmi270_info = { .storagebits = 16, \ .endianness = IIO_LE, \ }, \ + .event_spec = &bmi270_anymotion_event, \ + .num_event_specs = 1, \ } #define BMI270_ANG_VEL_CHANNEL(_axis) { \ @@ -1000,6 +1326,14 @@ static const struct iio_chan_spec bmi270_channels[] = { .num_event_specs = 1, }, IIO_CHAN_SOFT_TIMESTAMP(BMI270_SCAN_TIMESTAMP), + { + .type = IIO_ACCEL, + .modified = 1, + .channel2 = IIO_MOD_X_AND_Y_AND_Z, + .scan_index = -1, /* Fake channel */ + .event_spec = &bmi270_nomotion_event, + .num_event_specs = 1, + }, }; static int bmi270_int_pin_config(struct bmi270_data *data, @@ -1107,6 +1441,13 @@ static int bmi270_trigger_probe(struct bmi270_data *data, return dev_err_probe(data->dev, ret, "Trigger registration failed\n"); + /* Disable axes for motion events */ + ret = bmi270_update_feature_reg(data, BMI270_ANYMO1_REG, + BMI270_FEAT_MOTION_XYZ_EN_MSK, + FIELD_PREP(BMI270_FEAT_MOTION_XYZ_EN_MSK, 0)); + if (ret) + return ret; + data->irq_pin = irq_pin; return 0; From 4f816702d5bf2d32a425957bec09dd99381223a0 Mon Sep 17 00:00:00 2001 From: Gustavo Silva Date: Tue, 16 Sep 2025 20:38:24 -0300 Subject: [PATCH 018/150] iio: ABI: document accelerometer event attributes Add ABI documentation for accelerometer event-related sysfs attributes exposed by the bmi270 driver. These include threshold, period, and enable controls for adaptive magnitude (any-motion) and rate of change (no-motion) event detection. Signed-off-by: Gustavo Silva Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 34 +++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 89b4740dcfa1..352ab7b8476c 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -926,6 +926,7 @@ What: /sys/.../iio:deviceX/events/in_accel_y_roc_rising_en What: /sys/.../iio:deviceX/events/in_accel_y_roc_falling_en What: /sys/.../iio:deviceX/events/in_accel_z_roc_rising_en What: /sys/.../iio:deviceX/events/in_accel_z_roc_falling_en +What: /sys/.../iio:deviceX/events/in_accel_x&y&z_roc_rising_en What: /sys/.../iio:deviceX/events/in_anglvel_x_roc_rising_en What: /sys/.../iio:deviceX/events/in_anglvel_x_roc_falling_en What: /sys/.../iio:deviceX/events/in_anglvel_y_roc_rising_en @@ -1001,6 +1002,7 @@ Description: to the raw signal, allowing slow tracking to resume and the adaptive threshold event detection to function as expected. +What: /sys/.../events/in_accel_mag_adaptive_rising_value What: /sys/.../events/in_accel_thresh_rising_value What: /sys/.../events/in_accel_thresh_falling_value What: /sys/.../events/in_accel_x_raw_thresh_rising_value @@ -1147,6 +1149,7 @@ Description: will get activated once in_voltage0_raw goes above 1200 and will become deactivated again once the value falls below 1150. +What: /sys/.../events/in_accel_roc_rising_value What: /sys/.../events/in_accel_x_raw_roc_rising_value What: /sys/.../events/in_accel_x_raw_roc_falling_value What: /sys/.../events/in_accel_y_raw_roc_rising_value @@ -1193,6 +1196,8 @@ Description: value is in raw device units or in processed units (as _raw and _input do on sysfs direct channel read attributes). +What: /sys/.../events/in_accel_mag_adaptive_rising_period +What: /sys/.../events/in_accel_roc_rising_period What: /sys/.../events/in_accel_x_thresh_rising_period What: /sys/.../events/in_accel_x_thresh_falling_period What: /sys/.../events/in_accel_x_roc_rising_period @@ -1362,6 +1367,15 @@ Description: number or direction is not specified, applies to all channels of this type. +What: /sys/.../iio:deviceX/events/in_accel_x_mag_adaptive_rising_en +What: /sys/.../iio:deviceX/events/in_accel_y_mag_adaptive_rising_en +What: /sys/.../iio:deviceX/events/in_accel_z_mag_adaptive_rising_en +KernelVersion: 2.6.37 +Contact: linux-iio@vger.kernel.org +Description: + Similar to in_accel_x_thresh[_rising|_falling]_en, but here the + magnitude of the channel is compared to the adaptive threshold. + What: /sys/.../iio:deviceX/events/in_accel_mag_referenced_en What: /sys/.../iio:deviceX/events/in_accel_mag_referenced_rising_en What: /sys/.../iio:deviceX/events/in_accel_mag_referenced_falling_en @@ -2422,3 +2436,23 @@ Description: Value representing the user's attention to the system expressed in units as percentage. This usually means if the user is looking at the screen or not. + +What: /sys/.../events/in_accel_value_available +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + List of available threshold values for acceleration event + generation. Applies to all event types on in_accel channels. + Units after application of scale and offset are m/s^2. + Expressed as: + + - a range specified as "[min step max]" + +What: /sys/.../events/in_accel_period_available +KernelVersion: 6.18 +Contact: linux-iio@vger.kernel.org +Description: + List of available periods for accelerometer event detection in + seconds, expressed as: + + - a range specified as "[min step max]" From adb729f677bd7e5b849386a0d4778a270a7041eb Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Mon, 22 Sep 2025 13:37:54 +0700 Subject: [PATCH 019/150] Documentation: iio: ade9000, adis*, adx*: Convert IIO subsystem cross-references Cross-references to iio_tools.rst (IIO Interfacing Tools) and iio_devbuf.rst (Industrial IIO device buffers) are shown in inline code instead. Convert them to proper cross-references. Signed-off-by: Bagas Sanjaya Reviewed-by: Randy Dunlap Tested-by: Randy Dunlap Signed-off-by: Jonathan Cameron --- Documentation/iio/ade9000.rst | 2 +- Documentation/iio/adis16475.rst | 4 ++-- Documentation/iio/adis16480.rst | 4 ++-- Documentation/iio/adis16550.rst | 4 ++-- Documentation/iio/adxl345.rst | 4 ++-- Documentation/iio/adxl380.rst | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Documentation/iio/ade9000.rst b/Documentation/iio/ade9000.rst index 43d4b8dc1cb7..c9ff702a4251 100644 --- a/Documentation/iio/ade9000.rst +++ b/Documentation/iio/ade9000.rst @@ -264,5 +264,5 @@ Configure RMS voltage event thresholds (requires interrupts): 8. IIO Interfacing Tools ======================== -See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +See Documentation/iio/iio_tools.rst for the description of the available IIO interfacing tools. diff --git a/Documentation/iio/adis16475.rst b/Documentation/iio/adis16475.rst index 4bf0998be36e..89a388490ab7 100644 --- a/Documentation/iio/adis16475.rst +++ b/Documentation/iio/adis16475.rst @@ -374,11 +374,11 @@ Obtain buffered data: 00001740 01 1a 00 00 ff ff fe 31 00 00 46 aa 00 03 37 f7 |.......1..F...7.| ... -See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered +See Documentation/iio/iio_devbuf.rst for more information about how buffered data is structured. 4. IIO Interfacing Tools ======================== -See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +See Documentation/iio/iio_tools.rst for the description of the available IIO interfacing tools. diff --git a/Documentation/iio/adis16480.rst b/Documentation/iio/adis16480.rst index 4a2d40e0daa7..cce5f3e01741 100644 --- a/Documentation/iio/adis16480.rst +++ b/Documentation/iio/adis16480.rst @@ -436,11 +436,11 @@ Obtain buffered data:: 00006b60 09 63 00 00 00 00 1b 13 00 00 22 2f 00 03 23 91 |.c........"/..#.| ... -See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered +See Documentation/iio/iio_devbuf.rst for more information about how buffered data is structured. 4. IIO Interfacing Tools ======================== -See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +See Documentation/iio/iio_tools.rst for the description of the available IIO interfacing tools. diff --git a/Documentation/iio/adis16550.rst b/Documentation/iio/adis16550.rst index 25db7b8060c4..c9bbc0a857b0 100644 --- a/Documentation/iio/adis16550.rst +++ b/Documentation/iio/adis16550.rst @@ -366,11 +366,11 @@ Obtain buffered data: 0000ceb0 00 00 0d 2f 00 00 05 25 00 00 07 8d 00 00 a2 ce |.../...%........| ... -See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered +See Documentation/iio/iio_devbuf.rst for more information about how buffered data is structured. 4. IIO Interfacing Tools ======================== -See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +See Documentation/iio/iio_tools.rst for the description of the available IIO interfacing tools. diff --git a/Documentation/iio/adxl345.rst b/Documentation/iio/adxl345.rst index afdb35f8b72e..bb19d64f67c3 100644 --- a/Documentation/iio/adxl345.rst +++ b/Documentation/iio/adxl345.rst @@ -433,11 +433,11 @@ Obtain buffered data: 00000f0 00004 00014 00015 00005 00012 00011 00005 00012 ... -See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered +See Documentation/iio/iio_devbuf.rst for more information about how buffered data is structured. 4. IIO Interfacing Tools ======================== -See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +See Documentation/iio/iio_tools.rst for the description of the available IIO interfacing tools. diff --git a/Documentation/iio/adxl380.rst b/Documentation/iio/adxl380.rst index 66c8a4d4f767..61cafa2f98bf 100644 --- a/Documentation/iio/adxl380.rst +++ b/Documentation/iio/adxl380.rst @@ -223,11 +223,11 @@ Obtain buffered data: 002bc3c0 f7 fd 00 cb fb 94 24 80 f7 e3 00 f2 fb b8 24 80 |......$.......$.| ... -See ``Documentation/iio/iio_devbuf.rst`` for more information about how buffered +See Documentation/iio/iio_devbuf.rst for more information about how buffered data is structured. 4. IIO Interfacing Tools ======================== -See ``Documentation/iio/iio_tools.rst`` for the description of the available IIO +See Documentation/iio/iio_tools.rst for the description of the available IIO interfacing tools. From 620636f497337a9faedba336d3388091c9a540e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 23 Sep 2025 09:41:47 +0100 Subject: [PATCH 020/150] iio: dac: ltc2688: make use of devm_mutex_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use devm_mutex_init() since it brings some benefits when CONFIG_DEBUG_MUTEXES is enabled. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ltc2688.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/dac/ltc2688.c b/drivers/iio/dac/ltc2688.c index 7a2ee26a7d68..57028d422868 100644 --- a/drivers/iio/dac/ltc2688.c +++ b/drivers/iio/dac/ltc2688.c @@ -953,7 +953,9 @@ static int ltc2688_probe(struct spi_device *spi) /* Just write this once. No need to do it in every regmap read. */ st->tx_data[3] = LTC2688_CMD_NOOP; - mutex_init(&st->lock); + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; st->regmap = devm_regmap_init(dev, <c2688_regmap_bus, st, <c2688_regmap_config); From 0649002e842000872c69caceff0d20da48b2cc44 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 23 Sep 2025 15:33:18 -0500 Subject: [PATCH 021/150] iio: adc: ad7124: add ext attributes to temperature channel Use the same .ext_info for the temperature channel as for the voltage channels. In the ADC, these all go though a mux to select the source and otherwise operate the same. These attributes probably won't be used much, but since it is trivial to add this, we might as well include them. Signed-off-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index 374e39736584..7ed31399a875 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -1348,6 +1348,7 @@ static int ad7124_parse_channel_config(struct iio_dev *indio_dev, }, .address = num_channels, .scan_index = num_channels, + .ext_info = ad7124_calibsys_ext_info, }; } From 9065197e0d41b0beb4bf15b72b91fa81b14455ad Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 23 Sep 2025 16:48:04 -0500 Subject: [PATCH 022/150] iio: adc: ad7124: change setup reg allocation strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the allocation strategy of the 8 SETUP registers from a least- recently-used (LRU) to a first-come-first-served basis. The AD7124 chips can have up to 16 channels enabled at a time in the sequencer for buffered reads, but only have 8 SETUP configurations (namely the OFFSET, GAIN, CONFIG and FILTER registers) that must be shared among the 16 channels. This means some of the channels must use the exact same configuration parameters so that they can share a single SETUP group of registers. The previous LRU strategy did not keep track of how many different configurations were requested at the same time, so if there were more than 8 different configurations requested, some channels would end up using the incorrect configuration because the slot assigned to them would also be assigned to a different configuration that wrote over it later. Adding such tracking to solve this would make an already complex algorithm even more complex. Instead we can replace it with a simpler first-come-first-serve strategy. This makes it easy to track how many different configurations are being requested at the same time. This comes at the expense of slightly longer setup times for buffered reads since all setup registers must be written each time when a buffered read is enabled. But this is generally not considered a hot path where performance is critical, so should be acceptable. This new strategy also makes hardware debugging easier since SETUPs are now assigned in a deterministic manner and in a logical order. Signed-off-by: David Lechner Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 233 +++++++++++++++++---------------------- 1 file changed, 100 insertions(+), 133 deletions(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index 7ed31399a875..9d58ced7371d 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -111,6 +111,8 @@ #define AD7124_FILTER_SINGLE_CYCLE BIT(16) #define AD7124_FILTER_FS GENMASK(10, 0) +#define AD7124_CFG_SLOT_UNASSIGNED ~0U + #define AD7124_MAX_CONFIGS 8 #define AD7124_MAX_CHANNELS 16 @@ -176,14 +178,13 @@ enum ad7124_filter_type { }; struct ad7124_channel_config { - bool live; unsigned int cfg_slot; unsigned int requested_odr; unsigned int requested_odr_micro; /* * Following fields are used to compare for equality. If you * make adaptations in it, you most likely also have to adapt - * ad7124_find_similar_live_cfg(), too. + * ad7124_config_equal(), too. */ struct_group(config_props, enum ad7124_ref_sel refsel; @@ -215,14 +216,13 @@ struct ad7124_state { unsigned int adc_control; unsigned int num_channels; struct mutex cfgs_lock; /* lock for configs access */ - unsigned long cfg_slots_status; /* bitmap with slot status (1 means it is used) */ + u8 cfg_slot_use_count[AD7124_MAX_CONFIGS]; /* * Stores the power-on reset value for the GAIN(x) registers which are * needed for measurements at gain 1 (i.e. CONFIG(x).PGA == 0) */ unsigned int gain_default; - DECLARE_KFIFO(live_cfgs_fifo, struct ad7124_channel_config *, AD7124_MAX_CONFIGS); bool enable_single_cycle; }; @@ -367,9 +367,6 @@ static void ad7124_set_channel_odr(struct ad7124_state *st, unsigned int channel cfg->requested_odr_micro * factor / MICRO; odr_sel_bits = clamp(DIV_ROUND_CLOSEST(fclk, divisor), 1, 2047); - if (odr_sel_bits != st->channels[channel].cfg.odr_sel_bits) - st->channels[channel].cfg.live = false; - st->channels[channel].cfg.odr_sel_bits = odr_sel_bits; } @@ -404,61 +401,6 @@ static int ad7124_get_3db_filter_factor(struct ad7124_state *st, } } -static struct ad7124_channel_config *ad7124_find_similar_live_cfg(struct ad7124_state *st, - struct ad7124_channel_config *cfg) -{ - struct ad7124_channel_config *cfg_aux; - int i; - - /* - * This is just to make sure that the comparison is adapted after - * struct ad7124_channel_config was changed. - */ - static_assert(sizeof_field(struct ad7124_channel_config, config_props) == - sizeof(struct { - enum ad7124_ref_sel refsel; - bool bipolar; - bool buf_positive; - bool buf_negative; - unsigned int vref_mv; - unsigned int pga_bits; - unsigned int odr_sel_bits; - enum ad7124_filter_type filter_type; - unsigned int calibration_offset; - unsigned int calibration_gain; - })); - - for (i = 0; i < st->num_channels; i++) { - cfg_aux = &st->channels[i].cfg; - - if (cfg_aux->live && - cfg->refsel == cfg_aux->refsel && - cfg->bipolar == cfg_aux->bipolar && - cfg->buf_positive == cfg_aux->buf_positive && - cfg->buf_negative == cfg_aux->buf_negative && - cfg->vref_mv == cfg_aux->vref_mv && - cfg->pga_bits == cfg_aux->pga_bits && - cfg->odr_sel_bits == cfg_aux->odr_sel_bits && - cfg->filter_type == cfg_aux->filter_type && - cfg->calibration_offset == cfg_aux->calibration_offset && - cfg->calibration_gain == cfg_aux->calibration_gain) - return cfg_aux; - } - - return NULL; -} - -static int ad7124_find_free_config_slot(struct ad7124_state *st) -{ - unsigned int free_cfg_slot; - - free_cfg_slot = find_first_zero_bit(&st->cfg_slots_status, AD7124_MAX_CONFIGS); - if (free_cfg_slot == AD7124_MAX_CONFIGS) - return -1; - - return free_cfg_slot; -} - /* Only called during probe, so dev_err_probe() can be used */ static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channel_config *cfg) { @@ -487,6 +429,21 @@ static int ad7124_init_config_vref(struct ad7124_state *st, struct ad7124_channe } } +static bool ad7124_config_equal(struct ad7124_channel_config *a, + struct ad7124_channel_config *b) +{ + return a->refsel == b->refsel && + a->bipolar == b->bipolar && + a->buf_positive == b->buf_positive && + a->buf_negative == b->buf_negative && + a->vref_mv == b->vref_mv && + a->pga_bits == b->pga_bits && + a->odr_sel_bits == b->odr_sel_bits && + a->filter_type == b->filter_type && + a->calibration_offset == b->calibration_offset && + a->calibration_gain == b->calibration_gain; +} + static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_config *cfg, unsigned int cfg_slot) { @@ -495,13 +452,13 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co unsigned int post = 0; int ret; - cfg->cfg_slot = cfg_slot; - - ret = ad_sd_write_reg(&st->sd, AD7124_OFFSET(cfg->cfg_slot), 3, cfg->calibration_offset); + ret = ad_sd_write_reg(&st->sd, AD7124_OFFSET(cfg_slot), 3, + cfg->calibration_offset); if (ret) return ret; - ret = ad_sd_write_reg(&st->sd, AD7124_GAIN(cfg->cfg_slot), 3, cfg->calibration_gain); + ret = ad_sd_write_reg(&st->sd, AD7124_GAIN(cfg_slot), 3, + cfg->calibration_gain); if (ret) return ret; @@ -511,7 +468,7 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co (cfg->buf_negative ? AD7124_CONFIG_AIN_BUFM : 0) | FIELD_PREP(AD7124_CONFIG_PGA, cfg->pga_bits); - ret = ad_sd_write_reg(&st->sd, AD7124_CONFIG(cfg->cfg_slot), 2, val); + ret = ad_sd_write_reg(&st->sd, AD7124_CONFIG(cfg_slot), 2, val); if (ret < 0) return ret; @@ -564,7 +521,7 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co * was requested. It may only be disabled through debugfs for testing * purposes. */ - return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg->cfg_slot), 3, + return ad_sd_write_reg(&st->sd, AD7124_FILTER(cfg_slot), 3, FIELD_PREP(AD7124_FILTER_FILTER, filter) | FIELD_PREP(AD7124_FILTER_REJ60, rej60) | FIELD_PREP(AD7124_FILTER_POST_FILTER, post) | @@ -573,83 +530,86 @@ static int ad7124_write_config(struct ad7124_state *st, struct ad7124_channel_co FIELD_PREP(AD7124_FILTER_FS, cfg->odr_sel_bits)); } -static struct ad7124_channel_config *ad7124_pop_config(struct ad7124_state *st) +/** + * ad7124_request_config_slot() - Request a config slot for a given config + * @st: Driver instance + * @channel: Channel to request a slot for + * + * Tries to find a matching config already in use, otherwise finds a free + * slot. If this function returns successfully, the use count for the slot is + * increased and the slot number is stored in cfg->cfg_slot. + * + * The slot must be released again with ad7124_release_config_slot() when no + * longer needed. + * + * Returns: 0 if a slot was successfully assigned, -EUSERS if no slot is + * available or other error if SPI communication fails. + */ +static int ad7124_request_config_slot(struct ad7124_state *st, u8 channel) { - struct ad7124_channel_config *lru_cfg; - struct ad7124_channel_config *cfg; - int ret; - int i; + unsigned int other, slot; + int last_used_slot = -1; - /* - * Pop least recently used config from the fifo - * in order to make room for the new one - */ - ret = kfifo_get(&st->live_cfgs_fifo, &lru_cfg); - if (ret <= 0) - return NULL; + /* Find another channel with a matching config, if any. */ + for (other = 0; other < st->num_channels; other++) { + if (other == channel) + continue; - lru_cfg->live = false; + if (st->channels[other].cfg.cfg_slot == AD7124_CFG_SLOT_UNASSIGNED) + continue; - /* mark slot as free */ - assign_bit(lru_cfg->cfg_slot, &st->cfg_slots_status, 0); + last_used_slot = max_t(int, last_used_slot, + st->channels[other].cfg.cfg_slot); - /* invalidate all other configs that pointed to this one */ - for (i = 0; i < st->num_channels; i++) { - cfg = &st->channels[i].cfg; + if (!ad7124_config_equal(&st->channels[other].cfg, + &st->channels[channel].cfg)) + continue; - if (cfg->cfg_slot == lru_cfg->cfg_slot) - cfg->live = false; + /* Found a match, re-use that slot. */ + slot = st->channels[other].cfg.cfg_slot; + st->cfg_slot_use_count[slot]++; + st->channels[channel].cfg.cfg_slot = slot; + + return 0; } - return lru_cfg; + /* No match, use next free slot. */ + slot = last_used_slot + 1; + if (slot >= AD7124_MAX_CONFIGS) + return -EUSERS; + + st->cfg_slot_use_count[slot]++; + st->channels[channel].cfg.cfg_slot = slot; + + return ad7124_write_config(st, &st->channels[channel].cfg, slot); } -static int ad7124_push_config(struct ad7124_state *st, struct ad7124_channel_config *cfg) +static void ad7124_release_config_slot(struct ad7124_state *st, u8 channel) { - struct ad7124_channel_config *lru_cfg; - int free_cfg_slot; + unsigned int slot = st->channels[channel].cfg.cfg_slot; - free_cfg_slot = ad7124_find_free_config_slot(st); - if (free_cfg_slot >= 0) { - /* push the new config in configs queue */ - kfifo_put(&st->live_cfgs_fifo, cfg); - } else { - /* pop one config to make room for the new one */ - lru_cfg = ad7124_pop_config(st); - if (!lru_cfg) - return -EINVAL; + /* + * All of these conditions can happen at probe when all channels are + * disabled. Otherwise, they should not happen normally. + */ + if (channel >= st->num_channels || slot == AD7124_CFG_SLOT_UNASSIGNED || + st->cfg_slot_use_count[slot] == 0) + return; - /* push the new config in configs queue */ - free_cfg_slot = lru_cfg->cfg_slot; - kfifo_put(&st->live_cfgs_fifo, cfg); - } - - /* mark slot as used */ - assign_bit(free_cfg_slot, &st->cfg_slots_status, 1); - - return ad7124_write_config(st, cfg, free_cfg_slot); + st->cfg_slot_use_count[slot]--; + st->channels[channel].cfg.cfg_slot = AD7124_CFG_SLOT_UNASSIGNED; } static int ad7124_prepare_read(struct ad7124_state *st, int address) { struct ad7124_channel_config *cfg = &st->channels[address].cfg; - struct ad7124_channel_config *live_cfg; + int ret; - /* - * Before doing any reads assign the channel a configuration. - * Check if channel's config is on the device - */ - if (!cfg->live) { - /* check if config matches another one */ - live_cfg = ad7124_find_similar_live_cfg(st, cfg); - if (!live_cfg) - ad7124_push_config(st, cfg); - else - cfg->cfg_slot = live_cfg->cfg_slot; - } + ret = ad7124_request_config_slot(st, address); + if (ret) + return ret; /* point channel to the config slot and enable */ - cfg->live = true; return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(address), 2, st->channels[address].ain | FIELD_PREP(AD7124_CHANNEL_SETUP, cfg->cfg_slot) | @@ -692,6 +652,8 @@ static int ad7124_disable_one(struct ad_sigma_delta *sd, unsigned int chan) { struct ad7124_state *st = container_of(sd, struct ad7124_state, sd); + ad7124_release_config_slot(st, chan); + /* The relevant thing here is that AD7124_CHANNEL_ENABLE is cleared. */ return ad_sd_write_reg(&st->sd, AD7124_CHANNEL(chan), 2, 0); } @@ -913,9 +875,6 @@ static int ad7124_write_raw(struct iio_dev *indio_dev, gain = DIV_ROUND_CLOSEST(res, val2); res = ad7124_find_closest_match(ad7124_gain, ARRAY_SIZE(ad7124_gain), gain); - if (st->channels[chan->address].cfg.pga_bits != res) - st->channels[chan->address].cfg.live = false; - st->channels[chan->address].cfg.pga_bits = res; return 0; default: @@ -1058,7 +1017,11 @@ static int ad7124_syscalib_locked(struct ad7124_state *st, const struct iio_chan if (ret < 0) return ret; - ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(ch->cfg.cfg_slot), 3, + /* + * Making the assumption that a single conversion will always + * use configuration slot 0 for the OFFSET/GAIN registers. + */ + ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(0), 3, &ch->cfg.calibration_offset); if (ret < 0) return ret; @@ -1073,7 +1036,7 @@ static int ad7124_syscalib_locked(struct ad7124_state *st, const struct iio_chan if (ret < 0) return ret; - ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(ch->cfg.cfg_slot), 3, + ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3, &ch->cfg.calibration_gain); if (ret < 0) return ret; @@ -1164,7 +1127,6 @@ static int ad7124_set_filter_type_attr(struct iio_dev *dev, guard(mutex)(&st->cfgs_lock); - cfg->live = false; cfg->filter_type = value; ad7124_set_channel_odr(st, chan->address); @@ -1484,7 +1446,6 @@ static int ad7124_setup(struct ad7124_state *st) if (ret) return ret; - INIT_KFIFO(st->live_cfgs_fifo); for (i = 0; i < st->num_channels; i++) { struct ad7124_channel_config *cfg = &st->channels[i].cfg; @@ -1492,6 +1453,8 @@ static int ad7124_setup(struct ad7124_state *st) if (ret < 0) return ret; + cfg->cfg_slot = AD7124_CFG_SLOT_UNASSIGNED; + /* Default filter type on the ADC after reset. */ cfg->filter_type = AD7124_FILTER_TYPE_SINC4; @@ -1551,7 +1514,7 @@ static int __ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio * ad_sigma_delta_set_channel() -> ad7124_set_channel() * -> ad7124_prepare_read(). */ - ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(st->channels[i].cfg.cfg_slot), 3, + ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3, &st->channels[i].cfg.calibration_gain); if (ret < 0) return ret; @@ -1561,7 +1524,11 @@ static int __ad7124_calibrate_all(struct ad7124_state *st, struct iio_dev *indio if (ret < 0) return ret; - ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(st->channels[i].cfg.cfg_slot), 3, + /* + * Making the assumption that a single conversion will always + * use configuration slot 0 for the OFFSET/GAIN registers. + */ + ret = ad_sd_read_reg(&st->sd, AD7124_OFFSET(0), 3, &st->channels[i].cfg.calibration_offset); if (ret < 0) return ret; From f11e4374b44777aa2b51205999469a057f6f7faa Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Thu, 2 Oct 2025 22:02:02 +0200 Subject: [PATCH 023/150] dt-bindings: iio: pressure: add binding for mpl3115 MPL3115 is an I2C pressure and temperature sensor. It features 2 interrupt lines which can be configured to indicate events such as data ready or pressure/temperature threshold reached. Reviewed-by: Rob Herring (Arm) Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- .../bindings/iio/pressure/fsl,mpl3115.yaml | 71 +++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 2 - 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/iio/pressure/fsl,mpl3115.yaml diff --git a/Documentation/devicetree/bindings/iio/pressure/fsl,mpl3115.yaml b/Documentation/devicetree/bindings/iio/pressure/fsl,mpl3115.yaml new file mode 100644 index 000000000000..2933c2e10695 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/pressure/fsl,mpl3115.yaml @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/pressure/fsl,mpl3115.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: MPL3115 precision pressure sensor with altimetry + +maintainers: + - Antoni Pokusinski + +description: | + MPL3115 is a pressure/altitude and temperature sensor with I2C interface. + It features two programmable interrupt lines which indicate events such as + data ready or pressure/temperature threshold reached. + https://www.nxp.com/docs/en/data-sheet/MPL3115A2.pdf + +properties: + compatible: + const: fsl,mpl3115 + + reg: + maxItems: 1 + + vdd-supply: true + + vddio-supply: true + + interrupts: + minItems: 1 + maxItems: 2 + + interrupt-names: + minItems: 1 + maxItems: 2 + items: + enum: + - INT1 + - INT2 + + drive-open-drain: + type: boolean + description: + set if the specified interrupt pins should be configured as + open drain. If not set, defaults to push-pull. + +required: + - compatible + - reg + - vdd-supply + - vddio-supply + +additionalProperties: false + +examples: + - | + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + pressure@60 { + compatible = "fsl,mpl3115"; + reg = <0x60>; + vdd-supply = <&vdd>; + vddio-supply = <&vddio>; + interrupt-parent = <&gpio1>; + interrupts = <4 IRQ_TYPE_EDGE_FALLING>; + interrupt-names = "INT2"; + }; + }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 58ff948d93c9..2be5fb41b410 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -113,8 +113,6 @@ properties: - fsl,mma7660 # MMA8450Q: Xtrinsic Low-power, 3-axis Xtrinsic Accelerometer - fsl,mma8450 - # MPL3115: Absolute Digital Pressure Sensor - - fsl,mpl3115 # MPR121: Proximity Capacitive Touch Sensor Controller - fsl,mpr121 # Honeywell Humidicon HIH-6130 humidity/temperature sensor From f692f0bfdf471b6afad0aa759ce215cb087c9b81 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Thu, 2 Oct 2025 22:02:03 +0200 Subject: [PATCH 024/150] iio: mpl3115: add separate function for triggered buffer data collection Factor out the code responsible for collecting data for the triggered buffer from the trigger handler into a separate function. Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mpl3115.c | 56 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index 579da60ef441..1da78081ca7e 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -148,6 +148,33 @@ static int mpl3115_read_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int mpl3115_fill_trig_buffer(struct iio_dev *indio_dev, u8 *buffer) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret, pos = 0; + + ret = mpl3115_request(data); + if (ret < 0) + return ret; + + if (test_bit(0, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, 3, &buffer[pos]); + if (ret < 0) + return ret; + pos += 4; + } + + if (test_bit(1, indio_dev->active_scan_mask)) { + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, 2, &buffer[pos]); + if (ret < 0) + return ret; + } + + return 0; +} + static irqreturn_t mpl3115_trigger_handler(int irq, void *p) { struct iio_poll_func *pf = p; @@ -161,34 +188,13 @@ static irqreturn_t mpl3115_trigger_handler(int irq, void *p) * use a simple structure definition to express this data layout. */ u8 buffer[16] __aligned(8) = { }; - int ret, pos = 0; + int ret; mutex_lock(&data->lock); - ret = mpl3115_request(data); - if (ret < 0) { - mutex_unlock(&data->lock); - goto done; - } - - if (test_bit(0, indio_dev->active_scan_mask)) { - ret = i2c_smbus_read_i2c_block_data(data->client, - MPL3115_OUT_PRESS, 3, &buffer[pos]); - if (ret < 0) { - mutex_unlock(&data->lock); - goto done; - } - pos += 4; - } - - if (test_bit(1, indio_dev->active_scan_mask)) { - ret = i2c_smbus_read_i2c_block_data(data->client, - MPL3115_OUT_TEMP, 2, &buffer[pos]); - if (ret < 0) { - mutex_unlock(&data->lock); - goto done; - } - } + ret = mpl3115_fill_trig_buffer(indio_dev, buffer); mutex_unlock(&data->lock); + if (ret) + goto done; iio_push_to_buffers_with_ts(indio_dev, buffer, sizeof(buffer), iio_get_time_ns(indio_dev)); From b4105b20312a48a7723ad13fe180fbe6c066ac34 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Thu, 2 Oct 2025 22:02:04 +0200 Subject: [PATCH 025/150] iio: mpl3115: rename CTRL_REG1 field macros Rename the bitfield macros of CTRL_REG1, so that their names clearly indicate their relation to CTRL_REG1. This is a preparation for introducing the support for the DRDY interrupt which requires the usage of other control registers. Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mpl3115.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index 1da78081ca7e..61830edd959b 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -30,10 +30,10 @@ #define MPL3115_STATUS_PRESS_RDY BIT(2) #define MPL3115_STATUS_TEMP_RDY BIT(1) -#define MPL3115_CTRL_RESET BIT(2) /* software reset */ -#define MPL3115_CTRL_OST BIT(1) /* initiate measurement */ -#define MPL3115_CTRL_ACTIVE BIT(0) /* continuous measurement */ -#define MPL3115_CTRL_OS_258MS (BIT(5) | BIT(4)) /* 64x oversampling */ +#define MPL3115_CTRL1_RESET BIT(2) /* software reset */ +#define MPL3115_CTRL1_OST BIT(1) /* initiate measurement */ +#define MPL3115_CTRL1_ACTIVE BIT(0) /* continuous measurement */ +#define MPL3115_CTRL1_OS_258MS GENMASK(5, 4) /* 64x oversampling */ struct mpl3115_data { struct i2c_client *client; @@ -47,7 +47,7 @@ static int mpl3115_request(struct mpl3115_data *data) /* trigger measurement */ ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, - data->ctrl_reg1 | MPL3115_CTRL_OST); + data->ctrl_reg1 | MPL3115_CTRL1_OST); if (ret < 0) return ret; @@ -56,7 +56,7 @@ static int mpl3115_request(struct mpl3115_data *data) if (ret < 0) return ret; /* wait for data ready, i.e. OST cleared */ - if (!(ret & MPL3115_CTRL_OST)) + if (!(ret & MPL3115_CTRL1_OST)) break; msleep(20); } @@ -268,10 +268,10 @@ static int mpl3115_probe(struct i2c_client *client) /* software reset, I2C transfer is aborted (fails) */ i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, - MPL3115_CTRL_RESET); + MPL3115_CTRL1_RESET); msleep(50); - data->ctrl_reg1 = MPL3115_CTRL_OS_258MS; + data->ctrl_reg1 = MPL3115_CTRL1_OS_258MS; ret = i2c_smbus_write_byte_data(client, MPL3115_CTRL_REG1, data->ctrl_reg1); if (ret < 0) @@ -295,7 +295,7 @@ buffer_cleanup: static int mpl3115_standby(struct mpl3115_data *data) { return i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, - data->ctrl_reg1 & ~MPL3115_CTRL_ACTIVE); + data->ctrl_reg1 & ~MPL3115_CTRL1_ACTIVE); } static void mpl3115_remove(struct i2c_client *client) From 8464f61099c2a6a434a995fecd565f12cdec21d5 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Thu, 2 Oct 2025 22:02:05 +0200 Subject: [PATCH 026/150] iio: mpl3115: add support for DRDY interrupt MPL3115 sensor features a "data ready" interrupt which indicates the presence of new measurements. Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mpl3115.c | 183 +++++++++++++++++++++++++++++++-- 1 file changed, 175 insertions(+), 8 deletions(-) diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index 61830edd959b..e1b2c9f2bb43 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -7,40 +7,66 @@ * (7-bit I2C slave address 0x60) * * TODO: FIFO buffer, altimeter mode, oversampling, continuous mode, - * interrupts, user offset correction, raw mode + * user offset correction, raw mode */ -#include +#include +#include #include +#include +#include + +#include #include #include -#include -#include #include -#include +#include +#include #define MPL3115_STATUS 0x00 #define MPL3115_OUT_PRESS 0x01 /* MSB first, 20 bit */ #define MPL3115_OUT_TEMP 0x04 /* MSB first, 12 bit */ #define MPL3115_WHO_AM_I 0x0c +#define MPL3115_INT_SOURCE 0x12 +#define MPL3115_PT_DATA_CFG 0x13 #define MPL3115_CTRL_REG1 0x26 +#define MPL3115_CTRL_REG3 0x28 +#define MPL3115_CTRL_REG4 0x29 +#define MPL3115_CTRL_REG5 0x2a #define MPL3115_DEVICE_ID 0xc4 #define MPL3115_STATUS_PRESS_RDY BIT(2) #define MPL3115_STATUS_TEMP_RDY BIT(1) +#define MPL3115_INT_SRC_DRDY BIT(7) + +#define MPL3115_PT_DATA_EVENT_ALL GENMASK(2, 0) + #define MPL3115_CTRL1_RESET BIT(2) /* software reset */ #define MPL3115_CTRL1_OST BIT(1) /* initiate measurement */ #define MPL3115_CTRL1_ACTIVE BIT(0) /* continuous measurement */ #define MPL3115_CTRL1_OS_258MS GENMASK(5, 4) /* 64x oversampling */ +#define MPL3115_CTRL3_IPOL1 BIT(5) +#define MPL3115_CTRL3_IPOL2 BIT(1) + +#define MPL3115_CTRL4_INT_EN_DRDY BIT(7) + +#define MPL3115_CTRL5_INT_CFG_DRDY BIT(7) + struct mpl3115_data { struct i2c_client *client; + struct iio_trigger *drdy_trig; struct mutex lock; u8 ctrl_reg1; }; +enum mpl3115_irq_pin { + MPL3115_IRQ_INT1, + MPL3115_IRQ_INT2, +}; + static int mpl3115_request(struct mpl3115_data *data) { int ret, tries = 15; @@ -153,9 +179,11 @@ static int mpl3115_fill_trig_buffer(struct iio_dev *indio_dev, u8 *buffer) struct mpl3115_data *data = iio_priv(indio_dev); int ret, pos = 0; - ret = mpl3115_request(data); - if (ret < 0) - return ret; + if (!(data->ctrl_reg1 & MPL3115_CTRL1_ACTIVE)) { + ret = mpl3115_request(data); + if (ret < 0) + return ret; + } if (test_bit(0, indio_dev->active_scan_mask)) { ret = i2c_smbus_read_i2c_block_data(data->client, @@ -234,10 +262,145 @@ static const struct iio_chan_spec mpl3115_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(2), }; +static irqreturn_t mpl3115_interrupt_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + + ret = i2c_smbus_read_byte_data(data->client, MPL3115_INT_SOURCE); + if (ret < 0) + return IRQ_HANDLED; + + if (!(ret & MPL3115_INT_SRC_DRDY)) + return IRQ_NONE; + + iio_trigger_poll_nested(data->drdy_trig); + + return IRQ_HANDLED; +} + +static int mpl3115_config_interrupt(struct mpl3115_data *data, + u8 ctrl_reg1, u8 ctrl_reg4) +{ + int ret; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + ctrl_reg1); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG4, + ctrl_reg4); + if (ret < 0) + goto reg1_cleanup; + + data->ctrl_reg1 = ctrl_reg1; + + return 0; + +reg1_cleanup: + i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG1, + data->ctrl_reg1); + return ret; +} + +static int mpl3115_set_trigger_state(struct iio_trigger *trig, bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct mpl3115_data *data = iio_priv(indio_dev); + u8 ctrl_reg1 = data->ctrl_reg1; + u8 ctrl_reg4 = state ? MPL3115_CTRL4_INT_EN_DRDY : 0; + + if (state) + ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; + else + ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + + guard(mutex)(&data->lock); + + return mpl3115_config_interrupt(data, ctrl_reg1, ctrl_reg4); +} + +static const struct iio_trigger_ops mpl3115_trigger_ops = { + .set_trigger_state = mpl3115_set_trigger_state, +}; + static const struct iio_info mpl3115_info = { .read_raw = &mpl3115_read_raw, }; +static int mpl3115_trigger_probe(struct mpl3115_data *data, + struct iio_dev *indio_dev) +{ + struct fwnode_handle *fwnode = dev_fwnode(&data->client->dev); + int ret, irq, irq_type, irq_pin = MPL3115_IRQ_INT1; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq < 0) { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq < 0) + return 0; + + irq_pin = MPL3115_IRQ_INT2; + } + + irq_type = irq_get_trigger_type(irq); + if (irq_type != IRQF_TRIGGER_RISING && irq_type != IRQF_TRIGGER_FALLING) + return -EINVAL; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_PT_DATA_CFG, + MPL3115_PT_DATA_EVENT_ALL); + if (ret < 0) + return ret; + + if (irq_pin == MPL3115_IRQ_INT1) { + ret = i2c_smbus_write_byte_data(data->client, + MPL3115_CTRL_REG5, + MPL3115_CTRL5_INT_CFG_DRDY); + if (ret) + return ret; + + if (irq_type == IRQF_TRIGGER_RISING) { + ret = i2c_smbus_write_byte_data(data->client, + MPL3115_CTRL_REG3, + MPL3115_CTRL3_IPOL1); + if (ret) + return ret; + } + } else if (irq_type == IRQF_TRIGGER_RISING) { + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG3, + MPL3115_CTRL3_IPOL2); + if (ret) + return ret; + } + + data->drdy_trig = devm_iio_trigger_alloc(&data->client->dev, + "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->drdy_trig) + return -ENOMEM; + + data->drdy_trig->ops = &mpl3115_trigger_ops; + iio_trigger_set_drvdata(data->drdy_trig, indio_dev); + + ret = devm_request_threaded_irq(&data->client->dev, irq, NULL, + mpl3115_interrupt_handler, + IRQF_ONESHOT, + "mpl3115_irq", indio_dev); + if (ret) + return ret; + + ret = devm_iio_trigger_register(&data->client->dev, data->drdy_trig); + if (ret) + return ret; + + indio_dev->trig = iio_trigger_get(data->drdy_trig); + + return 0; +} + static int mpl3115_probe(struct i2c_client *client) { const struct i2c_device_id *id = i2c_client_get_device_id(client); @@ -277,6 +440,10 @@ static int mpl3115_probe(struct i2c_client *client) if (ret < 0) return ret; + ret = mpl3115_trigger_probe(data, indio_dev); + if (ret) + return ret; + ret = iio_triggered_buffer_setup(indio_dev, NULL, mpl3115_trigger_handler, NULL); if (ret < 0) From 1d09cf18cc91d29f650ad9811ed4868d9304d6c7 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Thu, 2 Oct 2025 22:02:06 +0200 Subject: [PATCH 027/150] iio: mpl3115: add support for sampling frequency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the device is in ACTIVE mode the temperature and pressure measurements are collected with a frequency determined by the ST[3:0] bits of CTRL_REG2 register. Reviewed-by: Nuno Sá Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mpl3115.c | 82 ++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index e1b2c9f2bb43..c212dfdf59ff 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -10,6 +10,7 @@ * user offset correction, raw mode */ +#include #include #include #include @@ -30,6 +31,7 @@ #define MPL3115_INT_SOURCE 0x12 #define MPL3115_PT_DATA_CFG 0x13 #define MPL3115_CTRL_REG1 0x26 +#define MPL3115_CTRL_REG2 0x27 #define MPL3115_CTRL_REG3 0x28 #define MPL3115_CTRL_REG4 0x29 #define MPL3115_CTRL_REG5 0x2a @@ -48,6 +50,8 @@ #define MPL3115_CTRL1_ACTIVE BIT(0) /* continuous measurement */ #define MPL3115_CTRL1_OS_258MS GENMASK(5, 4) /* 64x oversampling */ +#define MPL3115_CTRL2_ST GENMASK(3, 0) + #define MPL3115_CTRL3_IPOL1 BIT(5) #define MPL3115_CTRL3_IPOL2 BIT(1) @@ -55,6 +59,25 @@ #define MPL3115_CTRL5_INT_CFG_DRDY BIT(7) +static const unsigned int mpl3115_samp_freq_table[][2] = { + { 1, 0 }, + { 0, 500000 }, + { 0, 250000 }, + { 0, 125000 }, + { 0, 62500 }, + { 0, 31250 }, + { 0, 15625 }, + { 0, 7812 }, + { 0, 3906 }, + { 0, 1953 }, + { 0, 976 }, + { 0, 488 }, + { 0, 244 }, + { 0, 122 }, + { 0, 61 }, + { 0, 30 }, +}; + struct mpl3115_data { struct i2c_client *client; struct iio_trigger *drdy_trig; @@ -170,10 +193,61 @@ static int mpl3115_read_raw(struct iio_dev *indio_dev, default: return -EINVAL; } + case IIO_CHAN_INFO_SAMP_FREQ: + ret = i2c_smbus_read_byte_data(data->client, MPL3115_CTRL_REG2); + if (ret < 0) + return ret; + + ret = FIELD_GET(MPL3115_CTRL2_ST, ret); + + *val = mpl3115_samp_freq_table[ret][0]; + *val2 = mpl3115_samp_freq_table[ret][1]; + return IIO_VAL_INT_PLUS_MICRO; } return -EINVAL; } +static int mpl3115_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(mpl3115_samp_freq_table) * 2; + *vals = (int *)mpl3115_samp_freq_table; + return IIO_AVAIL_LIST; +} + +static int mpl3115_write_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val, int val2, long mask) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int i, ret; + + if (mask != IIO_CHAN_INFO_SAMP_FREQ) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mpl3115_samp_freq_table); i++) + if (val == mpl3115_samp_freq_table[i][0] && + val2 == mpl3115_samp_freq_table[i][1]) + break; + + if (i == ARRAY_SIZE(mpl3115_samp_freq_table)) + return -EINVAL; + + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + + ret = i2c_smbus_write_byte_data(data->client, MPL3115_CTRL_REG2, + FIELD_PREP(MPL3115_CTRL2_ST, i)); + iio_device_release_direct(indio_dev); + return ret; +} + static int mpl3115_fill_trig_buffer(struct iio_dev *indio_dev, u8 *buffer) { struct mpl3115_data *data = iio_priv(indio_dev); @@ -237,6 +311,9 @@ static const struct iio_chan_spec mpl3115_channels[] = { .type = IIO_PRESSURE, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 0, .scan_type = { .sign = 'u', @@ -250,6 +327,9 @@ static const struct iio_chan_spec mpl3115_channels[] = { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = + BIT(IIO_CHAN_INFO_SAMP_FREQ), .scan_index = 1, .scan_type = { .sign = 's', @@ -328,6 +408,8 @@ static const struct iio_trigger_ops mpl3115_trigger_ops = { static const struct iio_info mpl3115_info = { .read_raw = &mpl3115_read_raw, + .read_avail = &mpl3115_read_avail, + .write_raw = &mpl3115_write_raw, }; static int mpl3115_trigger_probe(struct mpl3115_data *data, From 67f31f0b5f27ed10cb7976c8140c52b0235e3d68 Mon Sep 17 00:00:00 2001 From: Sameeksha Sankpal Date: Fri, 3 Oct 2025 23:14:25 +0530 Subject: [PATCH 028/150] iio: light: ltr390: Fix typo in variable name Corrected a spelling mistake in the ltr390 driver: 'recieve_buffer' was renamed to 'receive_buffer'. This improves code readibility without changing functionality. Signed-off-by: Sameeksha Sankpal Signed-off-by: Jonathan Cameron --- drivers/iio/light/ltr390.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c index a2b804e9089a..fc387426fa87 100644 --- a/drivers/iio/light/ltr390.c +++ b/drivers/iio/light/ltr390.c @@ -160,16 +160,16 @@ static int ltr390_register_read(struct ltr390_data *data, u8 register_address) { struct device *dev = &data->client->dev; int ret; - u8 recieve_buffer[3]; + u8 receive_buffer[3]; - ret = regmap_bulk_read(data->regmap, register_address, recieve_buffer, - sizeof(recieve_buffer)); + ret = regmap_bulk_read(data->regmap, register_address, receive_buffer, + sizeof(receive_buffer)); if (ret) { dev_err(dev, "failed to read measurement data"); return ret; } - return get_unaligned_le24(recieve_buffer); + return get_unaligned_le24(receive_buffer); } static int ltr390_set_mode(struct ltr390_data *data, enum ltr390_mode mode) From ed1a82401bc08b43efbf1c10e653b8775a000ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:10 +0100 Subject: [PATCH 029/150] iio: adc: ad4030: replace sprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the ad4030_read_label() function to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4030.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c index 1bc2f9a22470..4393160c7c77 100644 --- a/drivers/iio/adc/ad4030.c +++ b/drivers/iio/adc/ad4030.c @@ -852,8 +852,8 @@ static int ad4030_read_label(struct iio_dev *indio_dev, char *label) { if (chan->differential) - return sprintf(label, "differential%lu\n", chan->address); - return sprintf(label, "common-mode%lu\n", chan->address); + return sysfs_emit(label, "differential%lu\n", chan->address); + return sysfs_emit(label, "common-mode%lu\n", chan->address); } static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev, From 69911a64ba24c1077a38f3083202f93248e1c970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:11 +0100 Subject: [PATCH 030/150] iio: adc: ad7768-1: replace sprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the ad7768_read_label() function to use sysfs_emit(() for generating labels. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7768-1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ad7768-1.c b/drivers/iio/adc/ad7768-1.c index 872c88d0c86c..d96802b7847a 100644 --- a/drivers/iio/adc/ad7768-1.c +++ b/drivers/iio/adc/ad7768-1.c @@ -899,7 +899,7 @@ static int ad7768_read_label(struct iio_dev *indio_dev, { struct ad7768_state *st = iio_priv(indio_dev); - return sprintf(label, "%s\n", st->labels[chan->channel]); + return sysfs_emit(label, "%s\n", st->labels[chan->channel]); } static int ad7768_get_current_scan_type(const struct iio_dev *indio_dev, From 08be56ebe994d8038751cc52a4a02e0135df34bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:12 +0100 Subject: [PATCH 031/150] iio: adc: mcp3564: replace sprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the mcp3564_read_label() function to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Reviewed-by: Marius Cristea Signed-off-by: Jonathan Cameron --- drivers/iio/adc/mcp3564.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/mcp3564.c b/drivers/iio/adc/mcp3564.c index cd679ff10a97..fcdf13f49c48 100644 --- a/drivers/iio/adc/mcp3564.c +++ b/drivers/iio/adc/mcp3564.c @@ -987,7 +987,7 @@ static int mcp3564_read_label(struct iio_dev *indio_dev, { struct mcp3564_state *adc = iio_priv(indio_dev); - return sprintf(label, "%s\n", adc->labels[chan->scan_index]); + return sysfs_emit(label, "%s\n", adc->labels[chan->scan_index]); } static int mcp3564_parse_fw_children(struct iio_dev *indio_dev) From f24a5ef74c350ec5432037f1d270b6ec4d56d269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:13 +0100 Subject: [PATCH 032/150] iio: adc: meson_saradc: replace sprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the read_label() function to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/meson_saradc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/meson_saradc.c b/drivers/iio/adc/meson_saradc.c index f7e7172ef4f6..47cd350498a0 100644 --- a/drivers/iio/adc/meson_saradc.c +++ b/drivers/iio/adc/meson_saradc.c @@ -1181,12 +1181,12 @@ static int read_label(struct iio_dev *indio_dev, char *label) { if (chan->type == IIO_TEMP) - return sprintf(label, "temp-sensor\n"); + return sysfs_emit(label, "temp-sensor\n"); if (chan->type == IIO_VOLTAGE && chan->channel >= NUM_MUX_0_VSS) - return sprintf(label, "%s\n", + return sysfs_emit(label, "%s\n", chan7_mux_names[chan->channel - NUM_MUX_0_VSS]); if (chan->type == IIO_VOLTAGE) - return sprintf(label, "channel-%d\n", chan->channel); + return sysfs_emit(label, "channel-%d\n", chan->channel); return 0; } From 31ab988b9f2f6c4ef0b3782bebc8c662bb0ce257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:14 +0100 Subject: [PATCH 033/150] iio: adc: mt6360-adc: replace snprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the mt6360_adc_read_label() function to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/mt6360-adc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/mt6360-adc.c b/drivers/iio/adc/mt6360-adc.c index 69b3569c90e5..e0e4df418612 100644 --- a/drivers/iio/adc/mt6360-adc.c +++ b/drivers/iio/adc/mt6360-adc.c @@ -216,7 +216,7 @@ static const char *mt6360_channel_labels[MT6360_CHAN_MAX] = { static int mt6360_adc_read_label(struct iio_dev *iio_dev, const struct iio_chan_spec *chan, char *label) { - return snprintf(label, PAGE_SIZE, "%s\n", mt6360_channel_labels[chan->channel]); + return sysfs_emit(label, "%s\n", mt6360_channel_labels[chan->channel]); } static const struct iio_info mt6360_adc_iio_info = { From 4367d24b156313b4b75a290ea5162328dbaa8e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:15 +0100 Subject: [PATCH 034/150] iio: adc: pac1921: replace sprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the pac1921_read_label() function to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/pac1921.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/pac1921.c b/drivers/iio/adc/pac1921.c index 35433250b008..a0227b57f238 100644 --- a/drivers/iio/adc/pac1921.c +++ b/drivers/iio/adc/pac1921.c @@ -672,13 +672,13 @@ static int pac1921_read_label(struct iio_dev *indio_dev, { switch (chan->channel) { case PAC1921_CHAN_VBUS: - return sprintf(label, "vbus\n"); + return sysfs_emit(label, "vbus\n"); case PAC1921_CHAN_VSENSE: - return sprintf(label, "vsense\n"); + return sysfs_emit(label, "vsense\n"); case PAC1921_CHAN_CURRENT: - return sprintf(label, "current\n"); + return sysfs_emit(label, "current\n"); case PAC1921_CHAN_POWER: - return sprintf(label, "power\n"); + return sysfs_emit(label, "power\n"); default: return -EINVAL; } From fdc9719a630fea1a2304529aa8f1b929a3e7ea75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:16 +0100 Subject: [PATCH 035/150] iio: adc: qcom-spmi-rradc: replace snprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the rradc_read_label() function to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/adc/qcom-spmi-rradc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/qcom-spmi-rradc.c b/drivers/iio/adc/qcom-spmi-rradc.c index f61ad0510f04..b245416bae12 100644 --- a/drivers/iio/adc/qcom-spmi-rradc.c +++ b/drivers/iio/adc/qcom-spmi-rradc.c @@ -769,7 +769,7 @@ static int rradc_read_raw(struct iio_dev *indio_dev, static int rradc_read_label(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, char *label) { - return snprintf(label, PAGE_SIZE, "%s\n", + return sysfs_emit(label, "%s\n", rradc_chans[chan->address].label); } From dd72a3880fc46fa383bff349033e96debcd401bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:17 +0100 Subject: [PATCH 036/150] iio: position: hid-sensor-custom-intel-hinge: replace sprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the hinge_read_label() function to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Acked-by: Srinivas Pandruvada Signed-off-by: Jonathan Cameron --- drivers/iio/position/hid-sensor-custom-intel-hinge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/position/hid-sensor-custom-intel-hinge.c b/drivers/iio/position/hid-sensor-custom-intel-hinge.c index bff7039690ac..a26d391661fd 100644 --- a/drivers/iio/position/hid-sensor-custom-intel-hinge.c +++ b/drivers/iio/position/hid-sensor-custom-intel-hinge.c @@ -176,7 +176,7 @@ static int hinge_read_label(struct iio_dev *indio_dev, { struct hinge_state *st = iio_priv(indio_dev); - return sprintf(label, "%s\n", st->labels[chan->channel]); + return sysfs_emit(label, "%s\n", st->labels[chan->channel]); } static const struct iio_info hinge_info = { From 7798b50e002591c14bd4492b794f2d1b7addb5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 30 Sep 2025 16:33:18 +0100 Subject: [PATCH 037/150] iio: resolver: ad2s1210: replace sprintf() with sysfs_emit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the ad2s1210_read_label() and ad2s1210_read_event_label() functions to use sysfs_emit() for generating labels. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/resolver/ad2s1210.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/iio/resolver/ad2s1210.c b/drivers/iio/resolver/ad2s1210.c index 9b028c8bb1db..06d9c784f93e 100644 --- a/drivers/iio/resolver/ad2s1210.c +++ b/drivers/iio/resolver/ad2s1210.c @@ -1132,23 +1132,23 @@ static int ad2s1210_read_label(struct iio_dev *indio_dev, { if (chan->type == IIO_ANGL) { if (chan->channel == 0) - return sprintf(label, "position\n"); + return sysfs_emit(label, "position\n"); if (chan->channel == 1) - return sprintf(label, "tracking error\n"); + return sysfs_emit(label, "tracking error\n"); } if (chan->type == IIO_ANGL_VEL) - return sprintf(label, "velocity\n"); + return sysfs_emit(label, "velocity\n"); if (chan->type == IIO_PHASE) - return sprintf(label, "synthetic reference\n"); + return sysfs_emit(label, "synthetic reference\n"); if (chan->type == IIO_ALTVOLTAGE) { if (chan->output) - return sprintf(label, "excitation\n"); + return sysfs_emit(label, "excitation\n"); if (chan->channel == 0) - return sprintf(label, "monitor signal\n"); + return sysfs_emit(label, "monitor signal\n"); if (chan->channel == 1) - return sprintf(label, "cosine\n"); + return sysfs_emit(label, "cosine\n"); if (chan->channel == 2) - return sprintf(label, "sine\n"); + return sysfs_emit(label, "sine\n"); } return -EINVAL; @@ -1239,24 +1239,24 @@ static int ad2s1210_read_event_label(struct iio_dev *indio_dev, char *label) { if (chan->type == IIO_ANGL) - return sprintf(label, "LOT\n"); + return sysfs_emit(label, "LOT\n"); if (chan->type == IIO_ANGL_VEL) - return sprintf(label, "max tracking rate\n"); + return sysfs_emit(label, "max tracking rate\n"); if (chan->type == IIO_PHASE) - return sprintf(label, "phase lock\n"); + return sysfs_emit(label, "phase lock\n"); if (chan->type == IIO_ALTVOLTAGE) { if (chan->channel == 0) { if (type == IIO_EV_TYPE_THRESH && dir == IIO_EV_DIR_FALLING) - return sprintf(label, "LOS\n"); + return sysfs_emit(label, "LOS\n"); if (type == IIO_EV_TYPE_THRESH && dir == IIO_EV_DIR_RISING) - return sprintf(label, "DOS overrange\n"); + return sysfs_emit(label, "DOS overrange\n"); if (type == IIO_EV_TYPE_MAG) - return sprintf(label, "DOS mismatch\n"); + return sysfs_emit(label, "DOS mismatch\n"); } if (chan->channel == 1 || chan->channel == 2) - return sprintf(label, "clipped\n"); + return sysfs_emit(label, "clipped\n"); } return -EINVAL; From 70fde04883609dcfd785bfe94649317c88a9840f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Mon, 29 Sep 2025 14:35:32 +0100 Subject: [PATCH 038/150] iio: dac: ltc2688: use the auto lock API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make use of the cleanup API so that we can simplify some code paths. Signed-off-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ltc2688.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/iio/dac/ltc2688.c b/drivers/iio/dac/ltc2688.c index 57028d422868..02f408229681 100644 --- a/drivers/iio/dac/ltc2688.c +++ b/drivers/iio/dac/ltc2688.c @@ -6,6 +6,7 @@ */ #include #include +#include #include #include #include @@ -208,12 +209,12 @@ static int ltc2688_dac_code_write(struct ltc2688_state *st, u32 chan, u32 input, code = FIELD_PREP(LTC2688_DITHER_RAW_MASK, code); } - mutex_lock(&st->lock); + guard(mutex)(&st->lock); /* select the correct input register to read from */ ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan), input << chan); if (ret) - goto out_unlock; + return ret; /* * If in dither/toggle mode the dac should be updated by an @@ -224,10 +225,7 @@ static int ltc2688_dac_code_write(struct ltc2688_state *st, u32 chan, u32 input, else reg = LTC2688_CMD_CH_CODE(chan); - ret = regmap_write(st->regmap, reg, code); -out_unlock: - mutex_unlock(&st->lock); - return ret; + return regmap_write(st->regmap, reg, code); } static int ltc2688_dac_code_read(struct ltc2688_state *st, u32 chan, u32 input, @@ -236,20 +234,20 @@ static int ltc2688_dac_code_read(struct ltc2688_state *st, u32 chan, u32 input, struct ltc2688_chan *c = &st->channels[chan]; int ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); ret = regmap_update_bits(st->regmap, LTC2688_CMD_A_B_SELECT, BIT(chan), input << chan); if (ret) - goto out_unlock; + return ret; ret = regmap_read(st->regmap, LTC2688_CMD_CH_CODE(chan), code); -out_unlock: - mutex_unlock(&st->lock); + if (ret) + return ret; if (!c->toggle_chan && input == LTC2688_INPUT_B) *code = FIELD_GET(LTC2688_DITHER_RAW_MASK, *code); - return ret; + return 0; } static const int ltc2688_raw_range[] = {0, 1, U16_MAX}; @@ -359,17 +357,15 @@ static ssize_t ltc2688_dither_toggle_set(struct iio_dev *indio_dev, if (ret) return ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); ret = regmap_update_bits(st->regmap, LTC2688_CMD_TOGGLE_DITHER_EN, BIT(chan->channel), en << chan->channel); if (ret) - goto out_unlock; + return ret; c->mode = en ? LTC2688_MODE_DITHER_TOGGLE : LTC2688_MODE_DEFAULT; -out_unlock: - mutex_unlock(&st->lock); - return ret ?: len; + return len; } static ssize_t ltc2688_reg_bool_get(struct iio_dev *indio_dev, From 4d8d58987cb57b3fe9721590888a99310a0e2cda Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Sun, 5 Oct 2025 14:13:17 +0300 Subject: [PATCH 039/150] dt-bindings: iio: adc: document RZ/T2H and RZ/N2H ADC Document the A/D 12-Bit successive approximation converters found in the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. RZ/T2H has two ADCs with 4 channels and one with 6. RZ/N2H has two ADCs with 4 channels and one with 15. Signed-off-by: Cosmin Tanislav Acked-by: Conor Dooley Reviewed-by: Geert Uytterhoeven Signed-off-by: Jonathan Cameron --- .../iio/adc/renesas,r9a09g077-adc.yaml | 135 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 142 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml new file mode 100644 index 000000000000..dc0206b28231 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml @@ -0,0 +1,135 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/renesas,r9a09g077-adc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/T2H / RZ/N2H ADC12 + +maintainers: + - Cosmin Tanislav + +description: | + A/D Converter block is a successive approximation analog-to-digital converter + with a 12-bit accuracy. Up to 16 analog input channels can be selected. + Conversions can be performed in single or continuous mode. Result of the ADC + is stored in a 16-bit data register corresponding to each channel. + +properties: + compatible: + oneOf: + - items: + - const: renesas,r9a09g087-adc # RZ/N2H + - const: renesas,r9a09g077-adc # RZ/T2H + - items: + - const: renesas,r9a09g077-adc # RZ/T2H + + reg: + maxItems: 1 + + interrupts: + items: + - description: A/D scan end interrupt + - description: A/D scan end interrupt for Group B + - description: A/D scan end interrupt for Group C + - description: Window A compare match + - description: Window B compare match + - description: Compare match + - description: Compare mismatch + + interrupt-names: + items: + - const: adi + - const: gbadi + - const: gcadi + - const: cmpai + - const: cmpbi + - const: wcmpm + - const: wcmpum + + clocks: + items: + - description: Converter clock + - description: Peripheral clock + + clock-names: + items: + - const: adclk + - const: pclk + + power-domains: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + "#io-channel-cells": + const: 1 + +patternProperties: + "^channel@[0-9a-f]$": + $ref: adc.yaml + type: object + description: The external channels which are connected to the ADC. + + properties: + reg: + description: The channel number. + maximum: 15 + + required: + - reg + + additionalProperties: false + +required: + - compatible + - reg + - interrupts + - clocks + - clock-names + - power-domains + +additionalProperties: false + +examples: + - | + #include + #include + + adc@80008000 { + compatible = "renesas,r9a09g077-adc"; + reg = <0x80008000 0x400>; + interrupts = , + , + , + , + , + , + ; + interrupt-names = "adi", "gbadi", "gcadi", + "cmpai", "cmpbi", "wcmpm", "wcmpum"; + clocks = <&cpg CPG_CORE R9A09G077_CLK_PCLKL>, + <&cpg CPG_MOD 225>; + clock-names = "adclk", "pclk"; + power-domains = <&cpg>; + #address-cells = <1>; + #size-cells = <0>; + #io-channel-cells = <1>; + + channel@0 { + reg = <0x0>; + }; + channel@1 { + reg = <0x1>; + }; + channel@2 { + reg = <0x2>; + }; + channel@3 { + reg = <0x3>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 46126ce2f968..5c10d4df8279 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21853,6 +21853,13 @@ S: Supported F: Documentation/devicetree/bindings/timer/renesas,rz-mtu3.yaml F: drivers/counter/rz-mtu3-cnt.c +RENESAS RZ/T2H / RZ/N2H A/D DRIVER +M: Cosmin Tanislav +L: linux-iio@vger.kernel.org +L: linux-renesas-soc@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml + RENESAS RTCA-3 RTC DRIVER M: Claudiu Beznea L: linux-rtc@vger.kernel.org From fc3b97dd71ab428d6cf6a49c7f67b7067feb6456 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Sun, 5 Oct 2025 14:13:18 +0300 Subject: [PATCH 040/150] iio: adc: add RZ/T2H / RZ/N2H ADC driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the A/D 12-Bit successive approximation converters found in the Renesas RZ/T2H (R9A09G077) and RZ/N2H (R9A09G087) SoCs. RZ/T2H has two ADCs with 4 channels and one with 6. RZ/N2H has two ADCs with 4 channels and one with 15. Conversions can be performed in single or continuous mode. Result of the conversion is stored in a 16-bit data register corresponding to each channel. The conversions can be started by a software trigger, a synchronous trigger (from MTU or from ELC) or an asynchronous external trigger (from ADTRGn# pin). Only single mode with software trigger is supported for now. Signed-off-by: Cosmin Tanislav Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 11 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/rzt2h_adc.c | 304 ++++++++++++++++++++++++++++++++++++ 4 files changed, 317 insertions(+) create mode 100644 drivers/iio/adc/rzt2h_adc.c diff --git a/MAINTAINERS b/MAINTAINERS index 5c10d4df8279..08fcce3208cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21859,6 +21859,7 @@ L: linux-iio@vger.kernel.org L: linux-renesas-soc@vger.kernel.org S: Supported F: Documentation/devicetree/bindings/iio/adc/renesas,r9a09g077-adc.yaml +F: drivers/iio/adc/rzt2h_adc.c RENESAS RTCA-3 RTC DRIVER M: Claudiu Beznea diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 58a14e6833f6..b0580fcefef5 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1403,6 +1403,17 @@ config RZG2L_ADC To compile this driver as a module, choose M here: the module will be called rzg2l_adc. +config RZT2H_ADC + tristate "Renesas RZ/T2H / RZ/N2H ADC driver" + depends on ARCH_RENESAS || COMPILE_TEST + select IIO_ADC_HELPER + help + Say yes here to build support for the ADC found in Renesas + RZ/T2H / RZ/N2H SoCs. + + To compile this driver as a module, choose M here: the + module will be called rzt2h_adc. + config SC27XX_ADC tristate "Spreadtrum SC27xx series PMICs ADC" depends on MFD_SC27XX_PMIC || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index d008f78dc010..ed647a734c51 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -123,6 +123,7 @@ obj-$(CONFIG_ROHM_BD79112) += rohm-bd79112.o obj-$(CONFIG_ROHM_BD79124) += rohm-bd79124.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_RZG2L_ADC) += rzg2l_adc.o +obj-$(CONFIG_RZT2H_ADC) += rzt2h_adc.o obj-$(CONFIG_SC27XX_ADC) += sc27xx_adc.o obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o obj-$(CONFIG_SOPHGO_CV1800B_ADC) += sophgo-cv1800b-adc.o diff --git a/drivers/iio/adc/rzt2h_adc.c b/drivers/iio/adc/rzt2h_adc.c new file mode 100644 index 000000000000..33ce5cc44ff4 --- /dev/null +++ b/drivers/iio/adc/rzt2h_adc.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZT2H_ADCSR_REG 0x00 +#define RZT2H_ADCSR_ADIE_MASK BIT(12) +#define RZT2H_ADCSR_ADCS_MASK GENMASK(14, 13) +#define RZT2H_ADCSR_ADCS_SINGLE 0b00 +#define RZT2H_ADCSR_ADST_MASK BIT(15) + +#define RZT2H_ADANSA0_REG 0x04 +#define RZT2H_ADANSA0_CH_MASK(x) BIT(x) + +#define RZT2H_ADDR_REG(x) (0x20 + 0x2 * (x)) + +#define RZT2H_ADCALCTL_REG 0x1f0 +#define RZT2H_ADCALCTL_CAL_MASK BIT(0) +#define RZT2H_ADCALCTL_CAL_RDY_MASK BIT(1) +#define RZT2H_ADCALCTL_CAL_ERR_MASK BIT(2) + +#define RZT2H_ADC_MAX_CHANNELS 16 + +struct rzt2h_adc { + void __iomem *base; + struct device *dev; + + struct completion completion; + /* lock to protect against multiple access to the device */ + struct mutex lock; + + const struct iio_chan_spec *channels; + unsigned int num_channels; + unsigned int max_channels; +}; + +static void rzt2h_adc_start(struct rzt2h_adc *adc, unsigned int conversion_type) +{ + u16 reg; + + reg = readw(adc->base + RZT2H_ADCSR_REG); + + /* Set conversion type */ + FIELD_MODIFY(RZT2H_ADCSR_ADCS_MASK, ®, conversion_type); + + /* Set end of conversion interrupt and start bit. */ + reg |= RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK; + + writew(reg, adc->base + RZT2H_ADCSR_REG); +} + +static void rzt2h_adc_stop(struct rzt2h_adc *adc) +{ + u16 reg; + + reg = readw(adc->base + RZT2H_ADCSR_REG); + + /* Clear end of conversion interrupt and start bit. */ + reg &= ~(RZT2H_ADCSR_ADIE_MASK | RZT2H_ADCSR_ADST_MASK); + + writew(reg, adc->base + RZT2H_ADCSR_REG); +} + +static int rzt2h_adc_read_single(struct rzt2h_adc *adc, unsigned int ch, int *val) +{ + int ret; + + ret = pm_runtime_resume_and_get(adc->dev); + if (ret) + return ret; + + mutex_lock(&adc->lock); + + reinit_completion(&adc->completion); + + /* Enable a single channel */ + writew(RZT2H_ADANSA0_CH_MASK(ch), adc->base + RZT2H_ADANSA0_REG); + + rzt2h_adc_start(adc, RZT2H_ADCSR_ADCS_SINGLE); + + /* + * Datasheet Page 2770, Table 41.1: + * 0.32us per channel when sample-and-hold circuits are not in use. + */ + ret = wait_for_completion_timeout(&adc->completion, usecs_to_jiffies(1)); + if (!ret) { + ret = -ETIMEDOUT; + goto disable; + } + + *val = readw(adc->base + RZT2H_ADDR_REG(ch)); + ret = IIO_VAL_INT; + +disable: + rzt2h_adc_stop(adc); + + mutex_unlock(&adc->lock); + + pm_runtime_put_autosuspend(adc->dev); + + return ret; +} + +static void rzt2h_adc_set_cal(struct rzt2h_adc *adc, bool cal) +{ + u16 val; + + val = readw(adc->base + RZT2H_ADCALCTL_REG); + if (cal) + val |= RZT2H_ADCALCTL_CAL_MASK; + else + val &= ~RZT2H_ADCALCTL_CAL_MASK; + + writew(val, adc->base + RZT2H_ADCALCTL_REG); +} + +static int rzt2h_adc_calibrate(struct rzt2h_adc *adc) +{ + u16 val; + int ret; + + rzt2h_adc_set_cal(adc, true); + + ret = read_poll_timeout(readw, val, val & RZT2H_ADCALCTL_CAL_RDY_MASK, + 200, 1000, true, adc->base + RZT2H_ADCALCTL_REG); + if (ret) { + dev_err(adc->dev, "Calibration timed out: %d\n", ret); + return ret; + } + + rzt2h_adc_set_cal(adc, false); + + if (val & RZT2H_ADCALCTL_CAL_ERR_MASK) { + dev_err(adc->dev, "Calibration failed\n"); + return -EINVAL; + } + + return 0; +} + +static int rzt2h_adc_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rzt2h_adc *adc = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + return rzt2h_adc_read_single(adc, chan->channel, val); + case IIO_CHAN_INFO_SCALE: + *val = 1800; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct iio_info rzt2h_adc_iio_info = { + .read_raw = rzt2h_adc_read_raw, +}; + +static irqreturn_t rzt2h_adc_isr(int irq, void *private) +{ + struct rzt2h_adc *adc = private; + + complete(&adc->completion); + + return IRQ_HANDLED; +} + +static const struct iio_chan_spec rzt2h_adc_chan_template = { + .indexed = 1, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + .type = IIO_VOLTAGE, +}; + +static int rzt2h_adc_parse_properties(struct rzt2h_adc *adc) +{ + struct iio_chan_spec *chan_array; + unsigned int i; + int ret; + + ret = devm_iio_adc_device_alloc_chaninfo_se(adc->dev, + &rzt2h_adc_chan_template, + RZT2H_ADC_MAX_CHANNELS - 1, + &chan_array); + if (ret < 0) + return dev_err_probe(adc->dev, ret, "Failed to read channel info"); + + adc->num_channels = ret; + adc->channels = chan_array; + + for (i = 0; i < adc->num_channels; i++) + if (chan_array[i].channel + 1 > adc->max_channels) + adc->max_channels = chan_array[i].channel + 1; + + return 0; +} + +static int rzt2h_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rzt2h_adc *adc; + int ret, irq; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*adc)); + if (!indio_dev) + return -ENOMEM; + + adc = iio_priv(indio_dev); + adc->dev = dev; + init_completion(&adc->completion); + + ret = devm_mutex_init(dev, &adc->lock); + if (ret) + return ret; + + platform_set_drvdata(pdev, adc); + + ret = rzt2h_adc_parse_properties(adc); + if (ret) + return ret; + + adc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(adc->base)) + return PTR_ERR(adc->base); + + pm_runtime_set_autosuspend_delay(dev, 300); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + irq = platform_get_irq_byname(pdev, "adi"); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, rzt2h_adc_isr, 0, dev_name(dev), adc); + if (ret) + return ret; + + indio_dev->name = "rzt2h-adc"; + indio_dev->info = &rzt2h_adc_iio_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = adc->channels; + indio_dev->num_channels = adc->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id rzt2h_adc_match[] = { + { .compatible = "renesas,r9a09g077-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rzt2h_adc_match); + +static int rzt2h_adc_pm_runtime_resume(struct device *dev) +{ + struct rzt2h_adc *adc = dev_get_drvdata(dev); + + /* + * Datasheet Page 2810, Section 41.5.6: + * After release from the module-stop state, wait for at least + * 0.5 µs before starting A/D conversion. + */ + fsleep(1); + + return rzt2h_adc_calibrate(adc); +} + +static const struct dev_pm_ops rzt2h_adc_pm_ops = { + RUNTIME_PM_OPS(NULL, rzt2h_adc_pm_runtime_resume, NULL) +}; + +static struct platform_driver rzt2h_adc_driver = { + .probe = rzt2h_adc_probe, + .driver = { + .name = "rzt2h-adc", + .of_match_table = rzt2h_adc_match, + .pm = pm_ptr(&rzt2h_adc_pm_ops), + }, +}; + +module_platform_driver(rzt2h_adc_driver); + +MODULE_AUTHOR("Cosmin Tanislav "); +MODULE_DESCRIPTION("Renesas RZ/T2H / RZ/N2H ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DRIVER"); From 9cc2d6566f0bac8efd4e9f75a07c11c1a12b20ba Mon Sep 17 00:00:00 2001 From: Taimoor Zaeem Date: Mon, 6 Oct 2025 14:40:24 +0500 Subject: [PATCH 041/150] staging: iio: ad9834: remove empty ad9834.h file Remove drivers/staging/iio/frequency/ad9834.h header file because it contains nothing except the include guards. Signed-off-by: Taimoor Zaeem Signed-off-by: Jonathan Cameron --- drivers/staging/iio/frequency/ad9834.c | 3 +-- drivers/staging/iio/frequency/ad9834.h | 10 ---------- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 drivers/staging/iio/frequency/ad9834.h diff --git a/drivers/staging/iio/frequency/ad9834.c b/drivers/staging/iio/frequency/ad9834.c index 0038eb234d40..d339d5e8e043 100644 --- a/drivers/staging/iio/frequency/ad9834.c +++ b/drivers/staging/iio/frequency/ad9834.c @@ -21,9 +21,8 @@ #include #include -#include "dds.h" -#include "ad9834.h" +#include "dds.h" /* Registers */ diff --git a/drivers/staging/iio/frequency/ad9834.h b/drivers/staging/iio/frequency/ad9834.h deleted file mode 100644 index 521943aa0e61..000000000000 --- a/drivers/staging/iio/frequency/ad9834.h +++ /dev/null @@ -1,10 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * AD9833/AD9834/AD9837/AD9838 SPI DDS driver - * - * Copyright 2010-2011 Analog Devices Inc. - */ -#ifndef IIO_DDS_AD9834_H_ -#define IIO_DDS_AD9834_H_ - -#endif /* IIO_DDS_AD9834_H_ */ From 679bf18ec0497ed2a8acb9d6a7acf56a341f3f9f Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:10 +0300 Subject: [PATCH 042/150] iio: accel: bma220: remove incorrect kernel-doc marking Remove incorrect use of kernel-doc marking. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 01592eebf05b..505ad7091257 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -223,7 +223,7 @@ static int bma220_power(struct spi_device *spi, bool up) { int i, ret; - /** + /* * The chip can be suspended/woken up by a simple register read. * So, we need up to 2 register reads of the suspend register * to make sure that the device is in the desired state. From f3f42da58803e606d50b01d6ae63454a4f0cfa2b Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:11 +0300 Subject: [PATCH 043/150] iio: accel: bma220: relax constraints during probe() Do not return error if the chip id being read is not the expected one. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_spi.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 505ad7091257..02ee6b4d51c0 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -202,10 +202,15 @@ static const struct iio_info bma220_info = { static int bma220_init(struct spi_device *spi) { int ret; + struct device *dev = &spi->dev; ret = bma220_read_reg(spi, BMA220_REG_ID); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read chip id register\n"); + if (ret != BMA220_CHIP_ID) - return -ENODEV; + dev_info(dev, "Unknown chip found: 0x%02x\n", ret); /* Make sure the chip is powered on */ ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); From b4cecec7dc0b09b8e7260abd0d45ec58214dfa9a Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:12 +0300 Subject: [PATCH 044/150] iio: accel: bma220: cleanup license string Fix checkpatch warning about use of "GPL v2" license: Prefer "GPL" over "GPL v2" - see commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity") Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 02ee6b4d51c0..8c313debc1df 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -332,4 +332,4 @@ module_spi_driver(bma220_driver); MODULE_AUTHOR("Tiberiu Breana "); MODULE_DESCRIPTION("BMA220 acceleration sensor driver"); -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); From 5dbac275dcb909b281b9f68727aace0e350e3ab3 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:13 +0300 Subject: [PATCH 045/150] iio: accel: bma220: shorten spi->dev calls Provide functions easier access to device struct. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_spi.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 8c313debc1df..a5d2906321ae 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -258,8 +258,9 @@ static int bma220_probe(struct spi_device *spi) int ret; struct iio_dev *indio_dev; struct bma220_data *data; + struct device *dev = &spi->dev; - indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*data)); + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; @@ -278,19 +279,19 @@ static int bma220_probe(struct spi_device *spi) if (ret) return ret; - ret = devm_add_action_or_reset(&spi->dev, bma220_deinit, spi); + ret = devm_add_action_or_reset(dev, bma220_deinit, spi); if (ret) return ret; - ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, iio_pollfunc_store_time, bma220_trigger_handler, NULL); if (ret < 0) { - dev_err(&spi->dev, "iio triggered buffer setup failed\n"); + dev_err(dev, "iio triggered buffer setup failed\n"); return ret; } - return devm_iio_device_register(&spi->dev, indio_dev); + return devm_iio_device_register(dev, indio_dev); } static int bma220_suspend(struct device *dev) From a9865410f4deec4db752941b87c8b387152aa103 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:14 +0300 Subject: [PATCH 046/150] iio: accel: bma220: move bma220_power function Move bma220_power() before bma220_init() as a precursor to a patch that removes code duplication. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_spi.c | 49 +++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index a5d2906321ae..45ac0d7ee27d 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -199,6 +199,31 @@ static const struct iio_info bma220_info = { .read_avail = bma220_read_avail, }; +static int bma220_power(struct spi_device *spi, bool up) +{ + int ret; + unsigned int i; + + /* + * The chip can be suspended/woken up by a simple register read. + * So, we need up to 2 register reads of the suspend register + * to make sure that the device is in the desired state. + */ + for (i = 0; i < 2; i++) { + ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); + if (ret < 0) + return ret; + + if (up && ret == BMA220_SUSPEND_SLEEP) + return 0; + + if (!up && ret == BMA220_SUSPEND_WAKE) + return 0; + } + + return -EBUSY; +} + static int bma220_init(struct spi_device *spi) { int ret; @@ -224,30 +249,6 @@ static int bma220_init(struct spi_device *spi) return 0; } -static int bma220_power(struct spi_device *spi, bool up) -{ - int i, ret; - - /* - * The chip can be suspended/woken up by a simple register read. - * So, we need up to 2 register reads of the suspend register - * to make sure that the device is in the desired state. - */ - for (i = 0; i < 2; i++) { - ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); - if (ret < 0) - return ret; - - if (up && ret == BMA220_SUSPEND_SLEEP) - return 0; - - if (!up && ret == BMA220_SUSPEND_WAKE) - return 0; - } - - return -EBUSY; -} - static void bma220_deinit(void *spi) { bma220_power(spi, false); From 7fe8e142f1edc770bb90aa0559c5c9ae6c17ffdf Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:15 +0300 Subject: [PATCH 047/150] iio: accel: bma220: cleanup includes Tweak includes based on requirements. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron #added linux/errno.h --- drivers/iio/accel/bma220_spi.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 45ac0d7ee27d..bf78fb052812 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -6,9 +6,11 @@ */ #include -#include +#include #include #include +#include +#include #include #include From c785d9803b861ad1ebf52386c7ac2fa6ee54d355 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:16 +0300 Subject: [PATCH 048/150] iio: accel: bma220: split original driver In preparation for the i2c module, move the original code into multiple source files without any other functional change. Create the additional bma220_core module which currently is not providing an abstracted bus type (this will change with the regmap patch). Fix a few includes in the context of this patch. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 8 +- drivers/iio/accel/Makefile | 3 +- drivers/iio/accel/bma220.h | 19 ++ drivers/iio/accel/bma220_core.c | 320 ++++++++++++++++++++++++++++++++ drivers/iio/accel/bma220_spi.c | 310 +------------------------------ 5 files changed, 356 insertions(+), 304 deletions(-) create mode 100644 drivers/iio/accel/bma220.h create mode 100644 drivers/iio/accel/bma220_core.c diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 8c3f7cf55d5f..4648be329917 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -221,12 +221,18 @@ config BMA220 depends on SPI select IIO_BUFFER select IIO_TRIGGERED_BUFFER + select BMA220_SPI if SPI help Say yes here to add support for the Bosch BMA220 triaxial acceleration sensor. To compile this driver as a module, choose M here: the - module will be called bma220_spi. + module will be called bma220_core and you will also get + bma220_spi if SPI is enabled. + +config BMA220_SPI + tristate + depends on BMA220 config BMA400 tristate "Bosch BMA400 3-Axis Accelerometer Driver" diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index ca8569e25aba..56a9f848f7f9 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -25,7 +25,8 @@ obj-$(CONFIG_ADXL380) += adxl380.o obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o obj-$(CONFIG_BMA180) += bma180.o -obj-$(CONFIG_BMA220) += bma220_spi.o +obj-$(CONFIG_BMA220) += bma220_core.o +obj-$(CONFIG_BMA220_SPI) += bma220_spi.o obj-$(CONFIG_BMA400) += bma400_core.o obj-$(CONFIG_BMA400_I2C) += bma400_i2c.o obj-$(CONFIG_BMA400_SPI) += bma400_spi.o diff --git a/drivers/iio/accel/bma220.h b/drivers/iio/accel/bma220.h new file mode 100644 index 000000000000..b181f2b510fd --- /dev/null +++ b/drivers/iio/accel/bma220.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Forward declarations needed by the bma220 sources. + * + * Copyright 2025 Petre Rodan + */ + +#ifndef _BMA220_H +#define _BMA220_H + +#include + +struct spi_device; + +extern const struct dev_pm_ops bma220_pm_ops; + +int bma220_common_probe(struct spi_device *dev); + +#endif diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c new file mode 100644 index 000000000000..050282f20d90 --- /dev/null +++ b/drivers/iio/accel/bma220_core.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BMA220 Digital triaxial acceleration sensor driver + * + * Copyright (c) 2016,2020 Intel Corporation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "bma220.h" + +#define BMA220_REG_ID 0x00 +#define BMA220_REG_ACCEL_X 0x02 +#define BMA220_REG_ACCEL_Y 0x03 +#define BMA220_REG_ACCEL_Z 0x04 +#define BMA220_REG_RANGE 0x11 +#define BMA220_REG_SUSPEND 0x18 + +#define BMA220_CHIP_ID 0xDD +#define BMA220_READ_MASK BIT(7) +#define BMA220_RANGE_MASK GENMASK(1, 0) +#define BMA220_SUSPEND_SLEEP 0xFF +#define BMA220_SUSPEND_WAKE 0x00 + +#define BMA220_DEVICE_NAME "bma220" + +#define BMA220_ACCEL_CHANNEL(index, reg, axis) { \ + .type = IIO_ACCEL, \ + .address = reg, \ + .modified = 1, \ + .channel2 = IIO_MOD_##axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 6, \ + .storagebits = 8, \ + .shift = 2, \ + .endianness = IIO_CPU, \ + }, \ +} + +enum bma220_axis { + AXIS_X, + AXIS_Y, + AXIS_Z, +}; + +static const int bma220_scale_table[][2] = { + {0, 623000}, {1, 248000}, {2, 491000}, {4, 983000}, +}; + +struct bma220_data { + struct spi_device *spi_device; + struct mutex lock; + struct { + s8 chans[3]; + /* Ensure timestamp is naturally aligned. */ + aligned_s64 timestamp; + } scan; + u8 tx_buf[2] __aligned(IIO_DMA_MINALIGN); +}; + +static const struct iio_chan_spec bma220_channels[] = { + BMA220_ACCEL_CHANNEL(0, BMA220_REG_ACCEL_X, X), + BMA220_ACCEL_CHANNEL(1, BMA220_REG_ACCEL_Y, Y), + BMA220_ACCEL_CHANNEL(2, BMA220_REG_ACCEL_Z, Z), + IIO_CHAN_SOFT_TIMESTAMP(3), +}; + +static inline int bma220_read_reg(struct spi_device *spi, u8 reg) +{ + return spi_w8r8(spi, reg | BMA220_READ_MASK); +} + +static const unsigned long bma220_accel_scan_masks[] = { + BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), + 0 +}; + +static irqreturn_t bma220_trigger_handler(int irq, void *p) +{ + int ret; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct bma220_data *data = iio_priv(indio_dev); + struct spi_device *spi = data->spi_device; + + mutex_lock(&data->lock); + data->tx_buf[0] = BMA220_REG_ACCEL_X | BMA220_READ_MASK; + ret = spi_write_then_read(spi, data->tx_buf, 1, &data->scan.chans, + ARRAY_SIZE(bma220_channels) - 1); + if (ret < 0) + goto err; + + iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), + pf->timestamp); +err: + mutex_unlock(&data->lock); + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int bma220_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + int ret; + u8 range_idx; + struct bma220_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = bma220_read_reg(data->spi_device, chan->address); + if (ret < 0) + return -EINVAL; + *val = sign_extend32(ret >> chan->scan_type.shift, + chan->scan_type.realbits - 1); + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + ret = bma220_read_reg(data->spi_device, BMA220_REG_RANGE); + if (ret < 0) + return ret; + range_idx = ret & BMA220_RANGE_MASK; + *val = bma220_scale_table[range_idx][0]; + *val2 = bma220_scale_table[range_idx][1]; + return IIO_VAL_INT_PLUS_MICRO; + } + + return -EINVAL; +} + +static int bma220_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + int i; + int ret; + int index = -1; + struct bma220_data *data = iio_priv(indio_dev); + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + for (i = 0; i < ARRAY_SIZE(bma220_scale_table); i++) + if (val == bma220_scale_table[i][0] && + val2 == bma220_scale_table[i][1]) { + index = i; + break; + } + if (index < 0) + return -EINVAL; + + mutex_lock(&data->lock); + data->tx_buf[0] = BMA220_REG_RANGE; + data->tx_buf[1] = index; + ret = spi_write(data->spi_device, data->tx_buf, + sizeof(data->tx_buf)); + if (ret < 0) + return ret; + mutex_unlock(&data->lock); + + return 0; + } + + return -EINVAL; +} + +static int bma220_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, int *length, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = (int *)bma220_scale_table; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(bma220_scale_table) * 2; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static const struct iio_info bma220_info = { + .read_raw = bma220_read_raw, + .write_raw = bma220_write_raw, + .read_avail = bma220_read_avail, +}; + +static int bma220_power(struct spi_device *spi, bool up) +{ + int ret; + unsigned int i; + + /* + * The chip can be suspended/woken up by a simple register read. + * So, we need up to 2 register reads of the suspend register + * to make sure that the device is in the desired state. + */ + for (i = 0; i < 2; i++) { + ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); + if (ret < 0) + return ret; + + if (up && ret == BMA220_SUSPEND_SLEEP) + return 0; + + if (!up && ret == BMA220_SUSPEND_WAKE) + return 0; + } + + return -EBUSY; +} + +static int bma220_init(struct spi_device *spi) +{ + int ret; + struct device *dev = &spi->dev; + + ret = bma220_read_reg(spi, BMA220_REG_ID); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read chip id register\n"); + + if (ret != BMA220_CHIP_ID) + dev_info(dev, "Unknown chip found: 0x%02x\n", ret); + + /* Make sure the chip is powered on */ + ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); + if (ret == BMA220_SUSPEND_WAKE) + ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); + if (ret < 0) + return ret; + if (ret == BMA220_SUSPEND_WAKE) + return -EBUSY; + + return 0; +} + +static void bma220_deinit(void *spi) +{ + bma220_power(spi, false); +} + +int bma220_common_probe(struct spi_device *spi) +{ + int ret; + struct iio_dev *indio_dev; + struct bma220_data *data; + struct device *dev = &spi->dev; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->spi_device = spi; + mutex_init(&data->lock); + + indio_dev->info = &bma220_info; + indio_dev->name = BMA220_DEVICE_NAME; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = bma220_channels; + indio_dev->num_channels = ARRAY_SIZE(bma220_channels); + indio_dev->available_scan_masks = bma220_accel_scan_masks; + + ret = bma220_init(data->spi_device); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, bma220_deinit, spi); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + bma220_trigger_handler, NULL); + if (ret < 0) { + dev_err(dev, "iio triggered buffer setup failed\n"); + return ret; + } + + return devm_iio_device_register(dev, indio_dev); +} +EXPORT_SYMBOL_NS(bma220_common_probe, "IIO_BOSCH_BMA220"); + +static int bma220_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + return bma220_power(spi, false); +} + +static int bma220_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + + return bma220_power(spi, true); +} +EXPORT_NS_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume, + IIO_BOSCH_BMA220); + +MODULE_AUTHOR("Tiberiu Breana "); +MODULE_DESCRIPTION("BMA220 acceleration sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index bf78fb052812..761f475a5942 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -5,313 +5,18 @@ * Copyright (c) 2016,2020 Intel Corporation. */ -#include -#include #include #include -#include -#include #include #include -#include -#include -#include -#include -#include +#include "bma220.h" -#define BMA220_REG_ID 0x00 -#define BMA220_REG_ACCEL_X 0x02 -#define BMA220_REG_ACCEL_Y 0x03 -#define BMA220_REG_ACCEL_Z 0x04 -#define BMA220_REG_RANGE 0x11 -#define BMA220_REG_SUSPEND 0x18 - -#define BMA220_CHIP_ID 0xDD -#define BMA220_READ_MASK BIT(7) -#define BMA220_RANGE_MASK GENMASK(1, 0) -#define BMA220_SUSPEND_SLEEP 0xFF -#define BMA220_SUSPEND_WAKE 0x00 - -#define BMA220_DEVICE_NAME "bma220" - -#define BMA220_ACCEL_CHANNEL(index, reg, axis) { \ - .type = IIO_ACCEL, \ - .address = reg, \ - .modified = 1, \ - .channel2 = IIO_MOD_##axis, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ - .scan_index = index, \ - .scan_type = { \ - .sign = 's', \ - .realbits = 6, \ - .storagebits = 8, \ - .shift = 2, \ - .endianness = IIO_CPU, \ - }, \ -} - -enum bma220_axis { - AXIS_X, - AXIS_Y, - AXIS_Z, -}; - -static const int bma220_scale_table[][2] = { - {0, 623000}, {1, 248000}, {2, 491000}, {4, 983000}, -}; - -struct bma220_data { - struct spi_device *spi_device; - struct mutex lock; - struct { - s8 chans[3]; - /* Ensure timestamp is naturally aligned. */ - aligned_s64 timestamp; - } scan; - u8 tx_buf[2] __aligned(IIO_DMA_MINALIGN); -}; - -static const struct iio_chan_spec bma220_channels[] = { - BMA220_ACCEL_CHANNEL(0, BMA220_REG_ACCEL_X, X), - BMA220_ACCEL_CHANNEL(1, BMA220_REG_ACCEL_Y, Y), - BMA220_ACCEL_CHANNEL(2, BMA220_REG_ACCEL_Z, Z), - IIO_CHAN_SOFT_TIMESTAMP(3), -}; - -static inline int bma220_read_reg(struct spi_device *spi, u8 reg) +static int bma220_spi_probe(struct spi_device *spi) { - return spi_w8r8(spi, reg | BMA220_READ_MASK); + return bma220_common_probe(spi); } -static const unsigned long bma220_accel_scan_masks[] = { - BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), - 0 -}; - -static irqreturn_t bma220_trigger_handler(int irq, void *p) -{ - int ret; - struct iio_poll_func *pf = p; - struct iio_dev *indio_dev = pf->indio_dev; - struct bma220_data *data = iio_priv(indio_dev); - struct spi_device *spi = data->spi_device; - - mutex_lock(&data->lock); - data->tx_buf[0] = BMA220_REG_ACCEL_X | BMA220_READ_MASK; - ret = spi_write_then_read(spi, data->tx_buf, 1, &data->scan.chans, - ARRAY_SIZE(bma220_channels) - 1); - if (ret < 0) - goto err; - - iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), - pf->timestamp); -err: - mutex_unlock(&data->lock); - iio_trigger_notify_done(indio_dev->trig); - - return IRQ_HANDLED; -} - -static int bma220_read_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int *val, int *val2, long mask) -{ - int ret; - u8 range_idx; - struct bma220_data *data = iio_priv(indio_dev); - - switch (mask) { - case IIO_CHAN_INFO_RAW: - ret = bma220_read_reg(data->spi_device, chan->address); - if (ret < 0) - return -EINVAL; - *val = sign_extend32(ret >> chan->scan_type.shift, - chan->scan_type.realbits - 1); - return IIO_VAL_INT; - case IIO_CHAN_INFO_SCALE: - ret = bma220_read_reg(data->spi_device, BMA220_REG_RANGE); - if (ret < 0) - return ret; - range_idx = ret & BMA220_RANGE_MASK; - *val = bma220_scale_table[range_idx][0]; - *val2 = bma220_scale_table[range_idx][1]; - return IIO_VAL_INT_PLUS_MICRO; - } - - return -EINVAL; -} - -static int bma220_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, int val2, long mask) -{ - int i; - int ret; - int index = -1; - struct bma220_data *data = iio_priv(indio_dev); - - switch (mask) { - case IIO_CHAN_INFO_SCALE: - for (i = 0; i < ARRAY_SIZE(bma220_scale_table); i++) - if (val == bma220_scale_table[i][0] && - val2 == bma220_scale_table[i][1]) { - index = i; - break; - } - if (index < 0) - return -EINVAL; - - mutex_lock(&data->lock); - data->tx_buf[0] = BMA220_REG_RANGE; - data->tx_buf[1] = index; - ret = spi_write(data->spi_device, data->tx_buf, - sizeof(data->tx_buf)); - if (ret < 0) - dev_err(&data->spi_device->dev, - "failed to set measurement range\n"); - mutex_unlock(&data->lock); - - return 0; - } - - return -EINVAL; -} - -static int bma220_read_avail(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - const int **vals, int *type, int *length, - long mask) -{ - switch (mask) { - case IIO_CHAN_INFO_SCALE: - *vals = (int *)bma220_scale_table; - *type = IIO_VAL_INT_PLUS_MICRO; - *length = ARRAY_SIZE(bma220_scale_table) * 2; - return IIO_AVAIL_LIST; - default: - return -EINVAL; - } -} - -static const struct iio_info bma220_info = { - .read_raw = bma220_read_raw, - .write_raw = bma220_write_raw, - .read_avail = bma220_read_avail, -}; - -static int bma220_power(struct spi_device *spi, bool up) -{ - int ret; - unsigned int i; - - /* - * The chip can be suspended/woken up by a simple register read. - * So, we need up to 2 register reads of the suspend register - * to make sure that the device is in the desired state. - */ - for (i = 0; i < 2; i++) { - ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); - if (ret < 0) - return ret; - - if (up && ret == BMA220_SUSPEND_SLEEP) - return 0; - - if (!up && ret == BMA220_SUSPEND_WAKE) - return 0; - } - - return -EBUSY; -} - -static int bma220_init(struct spi_device *spi) -{ - int ret; - struct device *dev = &spi->dev; - - ret = bma220_read_reg(spi, BMA220_REG_ID); - if (ret < 0) - return dev_err_probe(dev, ret, - "Failed to read chip id register\n"); - - if (ret != BMA220_CHIP_ID) - dev_info(dev, "Unknown chip found: 0x%02x\n", ret); - - /* Make sure the chip is powered on */ - ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); - if (ret == BMA220_SUSPEND_WAKE) - ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); - if (ret < 0) - return ret; - if (ret == BMA220_SUSPEND_WAKE) - return -EBUSY; - - return 0; -} - -static void bma220_deinit(void *spi) -{ - bma220_power(spi, false); -} - -static int bma220_probe(struct spi_device *spi) -{ - int ret; - struct iio_dev *indio_dev; - struct bma220_data *data; - struct device *dev = &spi->dev; - - indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); - if (!indio_dev) - return -ENOMEM; - - data = iio_priv(indio_dev); - data->spi_device = spi; - mutex_init(&data->lock); - - indio_dev->info = &bma220_info; - indio_dev->name = BMA220_DEVICE_NAME; - indio_dev->modes = INDIO_DIRECT_MODE; - indio_dev->channels = bma220_channels; - indio_dev->num_channels = ARRAY_SIZE(bma220_channels); - indio_dev->available_scan_masks = bma220_accel_scan_masks; - - ret = bma220_init(data->spi_device); - if (ret) - return ret; - - ret = devm_add_action_or_reset(dev, bma220_deinit, spi); - if (ret) - return ret; - - ret = devm_iio_triggered_buffer_setup(dev, indio_dev, - iio_pollfunc_store_time, - bma220_trigger_handler, NULL); - if (ret < 0) { - dev_err(dev, "iio triggered buffer setup failed\n"); - return ret; - } - - return devm_iio_device_register(dev, indio_dev); -} - -static int bma220_suspend(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - - return bma220_power(spi, false); -} - -static int bma220_resume(struct device *dev) -{ - struct spi_device *spi = to_spi_device(dev); - - return bma220_power(spi, true); -} -static DEFINE_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume); - static const struct spi_device_id bma220_spi_id[] = { {"bma220", 0}, { } @@ -323,17 +28,18 @@ static const struct acpi_device_id bma220_acpi_id[] = { }; MODULE_DEVICE_TABLE(spi, bma220_spi_id); -static struct spi_driver bma220_driver = { +static struct spi_driver bma220_spi_driver = { .driver = { .name = "bma220_spi", .pm = pm_sleep_ptr(&bma220_pm_ops), .acpi_match_table = bma220_acpi_id, }, - .probe = bma220_probe, + .probe = bma220_spi_probe, .id_table = bma220_spi_id, }; -module_spi_driver(bma220_driver); +module_spi_driver(bma220_spi_driver); MODULE_AUTHOR("Tiberiu Breana "); -MODULE_DESCRIPTION("BMA220 acceleration sensor driver"); +MODULE_DESCRIPTION("BMA220 triaxial acceleration sensor spi driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BOSCH_BMA220"); From 4a719f182674d30f060dde7414ba7d265fb0388c Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:17 +0300 Subject: [PATCH 049/150] iio: accel: bma220: add open firmware table Add open firmware entry to the spi driver. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_spi.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 761f475a5942..e1c25f48d9b3 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -28,10 +28,17 @@ static const struct acpi_device_id bma220_acpi_id[] = { }; MODULE_DEVICE_TABLE(spi, bma220_spi_id); +static const struct of_device_id bma220_of_spi_match[] = { + { .compatible = "bosch,bma220" }, + { } +}; +MODULE_DEVICE_TABLE(of, bma220_of_spi_match); + static struct spi_driver bma220_spi_driver = { .driver = { .name = "bma220_spi", .pm = pm_sleep_ptr(&bma220_pm_ops), + .of_match_table = bma220_of_spi_match, .acpi_match_table = bma220_acpi_id, }, .probe = bma220_spi_probe, From dfea5f181c9238bead123f777d5f3b401dae432e Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:18 +0300 Subject: [PATCH 050/150] iio: accel: bma220: turn power supplies on Add devm_regulator_bulk_get_enable() to device probe(). Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 050282f20d90..31fbea971230 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -231,6 +232,12 @@ static int bma220_init(struct spi_device *spi) { int ret; struct device *dev = &spi->dev; + static const char * const regulator_names[] = { "vddd", "vddio", "vdda" }; + + ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + regulator_names); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); ret = bma220_read_reg(spi, BMA220_REG_ID); if (ret < 0) From 2027b1a201eafe1697b34a5d87c41127864eb4f9 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:19 +0300 Subject: [PATCH 051/150] iio: accel: bma220: reset registers during init stage Bring all configuration registers to default values during device probe(). Remove trivial code duplication regarding bma220_power() in _init() Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 44 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 31fbea971230..c2ed789f9658 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -29,12 +29,15 @@ #define BMA220_REG_ACCEL_Z 0x04 #define BMA220_REG_RANGE 0x11 #define BMA220_REG_SUSPEND 0x18 +#define BMA220_REG_SOFTRESET 0x19 #define BMA220_CHIP_ID 0xDD #define BMA220_READ_MASK BIT(7) #define BMA220_RANGE_MASK GENMASK(1, 0) #define BMA220_SUSPEND_SLEEP 0xFF #define BMA220_SUSPEND_WAKE 0x00 +#define BMA220_RESET_MODE 0xFF +#define BMA220_NONRESET_MODE 0x00 #define BMA220_DEVICE_NAME "bma220" @@ -203,6 +206,31 @@ static const struct iio_info bma220_info = { .read_avail = bma220_read_avail, }; +static int bma220_reset(struct spi_device *spi, bool up) +{ + int ret; + unsigned int i; + + /* + * The chip can be reset by a simple register read. + * We need up to 2 register reads of the softreset register + * to make sure that the device is in the desired state. + */ + for (i = 0; i < 2; i++) { + ret = bma220_read_reg(spi, BMA220_REG_SOFTRESET); + if (ret < 0) + return ret; + + if (up && ret == BMA220_RESET_MODE) + return 0; + + if (!up && ret == BMA220_NONRESET_MODE) + return 0; + } + + return -EBUSY; +} + static int bma220_power(struct spi_device *spi, bool up) { int ret; @@ -247,14 +275,14 @@ static int bma220_init(struct spi_device *spi) if (ret != BMA220_CHIP_ID) dev_info(dev, "Unknown chip found: 0x%02x\n", ret); - /* Make sure the chip is powered on */ - ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); - if (ret == BMA220_SUSPEND_WAKE) - ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); - if (ret < 0) - return ret; - if (ret == BMA220_SUSPEND_WAKE) - return -EBUSY; + /* Make sure the chip is powered on and config registers are reset */ + ret = bma220_power(spi, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to power-on chip\n"); + + ret = bma220_reset(spi, true); + if (ret) + return dev_err_probe(dev, ret, "Failed to soft reset chip\n"); return 0; } From 5c27f1332282522de8da839bbdd2d1b2f0a6c960 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:20 +0300 Subject: [PATCH 052/150] iio: accel: bma220: migrate to regmap API Switch to regmap API. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 2 + drivers/iio/accel/bma220.h | 6 +- drivers/iio/accel/bma220_core.c | 231 ++++++++++++++++++++++---------- drivers/iio/accel/bma220_spi.c | 10 +- 4 files changed, 172 insertions(+), 77 deletions(-) diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 4648be329917..988fe4b1f9a5 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -219,6 +219,7 @@ config BMA180 config BMA220 tristate "Bosch BMA220 3-Axis Accelerometer Driver" depends on SPI + select REGMAP select IIO_BUFFER select IIO_TRIGGERED_BUFFER select BMA220_SPI if SPI @@ -232,6 +233,7 @@ config BMA220 config BMA220_SPI tristate + select REGMAP_SPI depends on BMA220 config BMA400 diff --git a/drivers/iio/accel/bma220.h b/drivers/iio/accel/bma220.h index b181f2b510fd..695f491bc5a0 100644 --- a/drivers/iio/accel/bma220.h +++ b/drivers/iio/accel/bma220.h @@ -9,11 +9,13 @@ #define _BMA220_H #include +#include -struct spi_device; +struct device; +extern const struct regmap_config bma220_spi_regmap_config; extern const struct dev_pm_ops bma220_pm_ops; -int bma220_common_probe(struct spi_device *dev); +int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq); #endif diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index c2ed789f9658..9a9fc98186a0 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -3,17 +3,21 @@ * BMA220 Digital triaxial acceleration sensor driver * * Copyright (c) 2016,2020 Intel Corporation. + * Copyright (c) 2025 Petre Rodan */ #include +#include +#include +#include #include #include #include #include #include +#include #include #include -#include #include #include @@ -24,16 +28,63 @@ #include "bma220.h" #define BMA220_REG_ID 0x00 +#define BMA220_REG_REVISION_ID 0x01 #define BMA220_REG_ACCEL_X 0x02 #define BMA220_REG_ACCEL_Y 0x03 #define BMA220_REG_ACCEL_Z 0x04 +#define BMA220_REG_CONF0 0x05 +#define BMA220_HIGH_DUR_MSK GENMASK(5, 0) +#define BMA220_HIGH_HY_MSK GENMASK(7, 6) +#define BMA220_REG_CONF1 0x06 +#define BMA220_HIGH_TH_MSK GENMASK(3, 0) +#define BMA220_LOW_TH_MSK GENMASK(7, 4) +#define BMA220_REG_CONF2 0x07 +#define BMA220_LOW_DUR_MSK GENMASK(5, 0) +#define BMA220_LOW_HY_MSK GENMASK(7, 6) +#define BMA220_REG_CONF3 0x08 +#define BMA220_TT_DUR_MSK GENMASK(2, 0) +#define BMA220_TT_TH_MSK GENMASK(6, 3) +#define BMA220_REG_CONF4 0x09 +#define BMA220_SLOPE_DUR_MSK GENMASK(1, 0) +#define BMA220_SLOPE_TH_MSK GENMASK(5, 2) +#define BMA220_REG_CONF5 0x0a +#define BMA220_TIP_EN_MSK BIT(4) +#define BMA220_REG_IF0 0x0b +#define BMA220_REG_IF1 0x0c +#define BMA220_IF_SLOPE BIT(0) +#define BMA220_IF_DRDY BIT(1) +#define BMA220_IF_HIGH BIT(2) +#define BMA220_IF_LOW BIT(3) +#define BMA220_IF_TT BIT(4) +#define BMA220_REG_IE0 0x0d +#define BMA220_INT_EN_TAP_Z_MSK BIT(0) +#define BMA220_INT_EN_TAP_Y_MSK BIT(1) +#define BMA220_INT_EN_TAP_X_MSK BIT(2) +#define BMA220_INT_EN_SLOPE_Z_MSK BIT(3) +#define BMA220_INT_EN_SLOPE_Y_MSK BIT(4) +#define BMA220_INT_EN_SLOPE_X_MSK BIT(5) +#define BMA220_INT_EN_DRDY_MSK BIT(7) +#define BMA220_REG_IE1 0x0e +#define BMA220_INT_EN_HIGH_Z_MSK BIT(0) +#define BMA220_INT_EN_HIGH_Y_MSK BIT(1) +#define BMA220_INT_EN_HIGH_X_MSK BIT(2) +#define BMA220_INT_EN_LOW_MSK BIT(3) +#define BMA220_INT_LATCH_MSK GENMASK(6, 4) +#define BMA220_INT_RST_MSK BIT(7) +#define BMA220_REG_IE2 0x0f +#define BMA220_REG_FILTER 0x10 +#define BMA220_FILTER_MASK GENMASK(3, 0) #define BMA220_REG_RANGE 0x11 +#define BMA220_RANGE_MASK GENMASK(1, 0) +#define BMA220_REG_WDT 0x17 +#define BMA220_WDT_MASK GENMASK(2, 1) +#define BMA220_WDT_OFF 0x0 +#define BMA220_WDT_1MS 0x2 +#define BMA220_WDT_10MS 0x3 #define BMA220_REG_SUSPEND 0x18 #define BMA220_REG_SOFTRESET 0x19 #define BMA220_CHIP_ID 0xDD -#define BMA220_READ_MASK BIT(7) -#define BMA220_RANGE_MASK GENMASK(1, 0) #define BMA220_SUSPEND_SLEEP 0xFF #define BMA220_SUSPEND_WAKE 0x00 #define BMA220_RESET_MODE 0xFF @@ -69,14 +120,14 @@ static const int bma220_scale_table[][2] = { }; struct bma220_data { - struct spi_device *spi_device; + struct regmap *regmap; struct mutex lock; + u8 range_idx; struct { s8 chans[3]; /* Ensure timestamp is naturally aligned. */ aligned_s64 timestamp; - } scan; - u8 tx_buf[2] __aligned(IIO_DMA_MINALIGN); + } scan __aligned(IIO_DMA_MINALIGN); }; static const struct iio_chan_spec bma220_channels[] = { @@ -86,35 +137,57 @@ static const struct iio_chan_spec bma220_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(3), }; -static inline int bma220_read_reg(struct spi_device *spi, u8 reg) -{ - return spi_w8r8(spi, reg | BMA220_READ_MASK); -} - static const unsigned long bma220_accel_scan_masks[] = { BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), 0 }; +static bool bma220_is_writable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BMA220_REG_CONF0: + case BMA220_REG_CONF1: + case BMA220_REG_CONF2: + case BMA220_REG_CONF3: + case BMA220_REG_CONF4: + case BMA220_REG_CONF5: + case BMA220_REG_IE0: + case BMA220_REG_IE1: + case BMA220_REG_IE2: + case BMA220_REG_FILTER: + case BMA220_REG_RANGE: + case BMA220_REG_WDT: + return true; + default: + return false; + } +} + +const struct regmap_config bma220_spi_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .read_flag_mask = BIT(7), + .max_register = BMA220_REG_SOFTRESET, + .cache_type = REGCACHE_NONE, + .writeable_reg = bma220_is_writable_reg, +}; +EXPORT_SYMBOL_NS_GPL(bma220_spi_regmap_config, "IIO_BOSCH_BMA220"); + static irqreturn_t bma220_trigger_handler(int irq, void *p) { int ret; struct iio_poll_func *pf = p; struct iio_dev *indio_dev = pf->indio_dev; struct bma220_data *data = iio_priv(indio_dev); - struct spi_device *spi = data->spi_device; - mutex_lock(&data->lock); - data->tx_buf[0] = BMA220_REG_ACCEL_X | BMA220_READ_MASK; - ret = spi_write_then_read(spi, data->tx_buf, 1, &data->scan.chans, - ARRAY_SIZE(bma220_channels) - 1); + ret = regmap_bulk_read(data->regmap, BMA220_REG_ACCEL_X, + &data->scan.chans, + sizeof(data->scan.chans)); if (ret < 0) - goto err; + return IRQ_NONE; iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), pf->timestamp); -err: - mutex_unlock(&data->lock); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; @@ -125,24 +198,24 @@ static int bma220_read_raw(struct iio_dev *indio_dev, int *val, int *val2, long mask) { int ret; - u8 range_idx; + u8 index; + unsigned int reg; struct bma220_data *data = iio_priv(indio_dev); + guard(mutex)(&data->lock); + switch (mask) { case IIO_CHAN_INFO_RAW: - ret = bma220_read_reg(data->spi_device, chan->address); + ret = regmap_read(data->regmap, chan->address, ®); if (ret < 0) return -EINVAL; - *val = sign_extend32(ret >> chan->scan_type.shift, + *val = sign_extend32(reg >> chan->scan_type.shift, chan->scan_type.realbits - 1); return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: - ret = bma220_read_reg(data->spi_device, BMA220_REG_RANGE); - if (ret < 0) - return ret; - range_idx = ret & BMA220_RANGE_MASK; - *val = bma220_scale_table[range_idx][0]; - *val2 = bma220_scale_table[range_idx][1]; + index = data->range_idx; + *val = bma220_scale_table[index][0]; + *val2 = bma220_scale_table[index][1]; return IIO_VAL_INT_PLUS_MICRO; } @@ -158,6 +231,8 @@ static int bma220_write_raw(struct iio_dev *indio_dev, int index = -1; struct bma220_data *data = iio_priv(indio_dev); + guard(mutex)(&data->lock); + switch (mask) { case IIO_CHAN_INFO_SCALE: for (i = 0; i < ARRAY_SIZE(bma220_scale_table); i++) @@ -169,14 +244,12 @@ static int bma220_write_raw(struct iio_dev *indio_dev, if (index < 0) return -EINVAL; - mutex_lock(&data->lock); - data->tx_buf[0] = BMA220_REG_RANGE; - data->tx_buf[1] = index; - ret = spi_write(data->spi_device, data->tx_buf, - sizeof(data->tx_buf)); + ret = regmap_update_bits(data->regmap, BMA220_REG_RANGE, + BMA220_RANGE_MASK, + FIELD_PREP(BMA220_RANGE_MASK, index)); if (ret < 0) return ret; - mutex_unlock(&data->lock); + data->range_idx = index; return 0; } @@ -206,10 +279,10 @@ static const struct iio_info bma220_info = { .read_avail = bma220_read_avail, }; -static int bma220_reset(struct spi_device *spi, bool up) +static int bma220_reset(struct bma220_data *data, bool up) { int ret; - unsigned int i; + unsigned int i, val; /* * The chip can be reset by a simple register read. @@ -217,24 +290,24 @@ static int bma220_reset(struct spi_device *spi, bool up) * to make sure that the device is in the desired state. */ for (i = 0; i < 2; i++) { - ret = bma220_read_reg(spi, BMA220_REG_SOFTRESET); + ret = regmap_read(data->regmap, BMA220_REG_SOFTRESET, &val); if (ret < 0) return ret; - if (up && ret == BMA220_RESET_MODE) + if (up && val == BMA220_RESET_MODE) return 0; - if (!up && ret == BMA220_NONRESET_MODE) + if (!up && val == BMA220_NONRESET_MODE) return 0; } return -EBUSY; } -static int bma220_power(struct spi_device *spi, bool up) +static int bma220_power(struct bma220_data *data, bool up) { int ret; - unsigned int i; + unsigned int i, val; /* * The chip can be suspended/woken up by a simple register read. @@ -242,70 +315,84 @@ static int bma220_power(struct spi_device *spi, bool up) * to make sure that the device is in the desired state. */ for (i = 0; i < 2; i++) { - ret = bma220_read_reg(spi, BMA220_REG_SUSPEND); + ret = regmap_read(data->regmap, BMA220_REG_SUSPEND, &val); if (ret < 0) return ret; - if (up && ret == BMA220_SUSPEND_SLEEP) + if (up && val == BMA220_SUSPEND_SLEEP) return 0; - if (!up && ret == BMA220_SUSPEND_WAKE) + if (!up && val == BMA220_SUSPEND_WAKE) return 0; } return -EBUSY; } -static int bma220_init(struct spi_device *spi) +static int bma220_init(struct device *dev, struct bma220_data *data) { int ret; - struct device *dev = &spi->dev; + unsigned int val; static const char * const regulator_names[] = { "vddd", "vddio", "vdda" }; - ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names), + ret = devm_regulator_bulk_get_enable(dev, + ARRAY_SIZE(regulator_names), regulator_names); if (ret) return dev_err_probe(dev, ret, "Failed to get regulators\n"); - ret = bma220_read_reg(spi, BMA220_REG_ID); - if (ret < 0) + ret = regmap_read(data->regmap, BMA220_REG_ID, &val); + if (ret) return dev_err_probe(dev, ret, "Failed to read chip id register\n"); - if (ret != BMA220_CHIP_ID) - dev_info(dev, "Unknown chip found: 0x%02x\n", ret); + if (val != BMA220_CHIP_ID) + dev_info(dev, "Unknown chip found: 0x%02x\n", val); - /* Make sure the chip is powered on and config registers are reset */ - ret = bma220_power(spi, true); + ret = bma220_power(data, true); if (ret) return dev_err_probe(dev, ret, "Failed to power-on chip\n"); - ret = bma220_reset(spi, true); + ret = bma220_reset(data, true); if (ret) return dev_err_probe(dev, ret, "Failed to soft reset chip\n"); return 0; } -static void bma220_deinit(void *spi) +static void bma220_deinit(void *data_ptr) { - bma220_power(spi, false); + struct bma220_data *data = data_ptr; + int ret; + struct device *dev = regmap_get_device(data->regmap); + + ret = bma220_power(data, false); + if (ret) + dev_warn(dev, + "Failed to put device into suspend mode (%pe)\n", + ERR_PTR(ret)); } -int bma220_common_probe(struct spi_device *spi) +int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq) { int ret; struct iio_dev *indio_dev; struct bma220_data *data; - struct device *dev = &spi->dev; indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); - data->spi_device = spi; - mutex_init(&data->lock); + data->regmap = regmap; + + ret = bma220_init(dev, data); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; indio_dev->info = &bma220_info; indio_dev->name = BMA220_DEVICE_NAME; @@ -314,38 +401,34 @@ int bma220_common_probe(struct spi_device *spi) indio_dev->num_channels = ARRAY_SIZE(bma220_channels); indio_dev->available_scan_masks = bma220_accel_scan_masks; - ret = bma220_init(data->spi_device); - if (ret) - return ret; - - ret = devm_add_action_or_reset(dev, bma220_deinit, spi); + ret = devm_add_action_or_reset(dev, bma220_deinit, data); if (ret) return ret; ret = devm_iio_triggered_buffer_setup(dev, indio_dev, iio_pollfunc_store_time, bma220_trigger_handler, NULL); - if (ret < 0) { - dev_err(dev, "iio triggered buffer setup failed\n"); - return ret; - } + if (ret < 0) + dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); return devm_iio_device_register(dev, indio_dev); } -EXPORT_SYMBOL_NS(bma220_common_probe, "IIO_BOSCH_BMA220"); +EXPORT_SYMBOL_NS_GPL(bma220_common_probe, "IIO_BOSCH_BMA220"); static int bma220_suspend(struct device *dev) { - struct spi_device *spi = to_spi_device(dev); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bma220_data *data = iio_priv(indio_dev); - return bma220_power(spi, false); + return bma220_power(data, false); } static int bma220_resume(struct device *dev) { - struct spi_device *spi = to_spi_device(dev); + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct bma220_data *data = iio_priv(indio_dev); - return bma220_power(spi, true); + return bma220_power(data, true); } EXPORT_NS_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume, IIO_BOSCH_BMA220); diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index e1c25f48d9b3..7aced4017373 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -14,7 +15,14 @@ static int bma220_spi_probe(struct spi_device *spi) { - return bma220_common_probe(spi); + struct regmap *regmap; + + regmap = devm_regmap_init_spi(spi, &bma220_spi_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "failed to create regmap\n"); + + return bma220_common_probe(&spi->dev, regmap, spi->irq); } static const struct spi_device_id bma220_spi_id[] = { From 480f08a6892e3de415c8063121d5e198c4120ed7 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:21 +0300 Subject: [PATCH 053/150] iio: accel: bma220: populate buffer ts in trigger handler Populate buffer timestamps in trigger handler since not all triggers can run the top half handler that provides pf->timestamp. Fixes failing unit test that triggers based on the INT signal. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 9a9fc98186a0..6975076802a2 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -187,7 +187,7 @@ static irqreturn_t bma220_trigger_handler(int irq, void *p) return IRQ_NONE; iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan), - pf->timestamp); + iio_get_time_ns(indio_dev)); iio_trigger_notify_done(indio_dev->trig); return IRQ_HANDLED; @@ -405,8 +405,7 @@ int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq) if (ret) return ret; - ret = devm_iio_triggered_buffer_setup(dev, indio_dev, - iio_pollfunc_store_time, + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, bma220_trigger_handler, NULL); if (ret < 0) dev_err_probe(dev, ret, "iio triggered buffer setup failed\n"); From 0e3c7fd44244a1dbb925ade1a87e699bdea03dab Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:22 +0300 Subject: [PATCH 054/150] iio: accel: bma220: use find_match_table fct Clean up the code a bit by using a find_match_table function. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 6975076802a2..404fea26e3f7 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -222,11 +222,23 @@ static int bma220_read_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int bma220_find_match_2dt(const int (*tbl)[2], const int n, + const int val, const int val2) +{ + int i; + + for (i = 0; i < n; i++) { + if (tbl[i][0] == val && tbl[i][1] == val2) + return i; + } + + return -EINVAL; +} + static int bma220_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { - int i; int ret; int index = -1; struct bma220_data *data = iio_priv(indio_dev); @@ -235,12 +247,9 @@ static int bma220_write_raw(struct iio_dev *indio_dev, switch (mask) { case IIO_CHAN_INFO_SCALE: - for (i = 0; i < ARRAY_SIZE(bma220_scale_table); i++) - if (val == bma220_scale_table[i][0] && - val2 == bma220_scale_table[i][1]) { - index = i; - break; - } + index = bma220_find_match_2dt(bma220_scale_table, + ARRAY_SIZE(bma220_scale_table), + val, val2); if (index < 0) return -EINVAL; From 7a23e6d922efc77e783084c4f3b2859c2a0bc7f3 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:23 +0300 Subject: [PATCH 055/150] iio: accel: bma220: add i2c module Add the bma220_i2c module. Note that this kernel module transparently shifts all register addresses 1 bit to the left, so all functions will operate based on the SPI memory map. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/Kconfig | 11 +++++-- drivers/iio/accel/Makefile | 1 + drivers/iio/accel/bma220.h | 1 + drivers/iio/accel/bma220_core.c | 18 ++++++++++ drivers/iio/accel/bma220_i2c.c | 58 +++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 drivers/iio/accel/bma220_i2c.c diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig index 988fe4b1f9a5..76911278fb21 100644 --- a/drivers/iio/accel/Kconfig +++ b/drivers/iio/accel/Kconfig @@ -218,10 +218,11 @@ config BMA180 config BMA220 tristate "Bosch BMA220 3-Axis Accelerometer Driver" - depends on SPI + depends on I2C || SPI select REGMAP select IIO_BUFFER select IIO_TRIGGERED_BUFFER + select BMA220_I2C if I2C select BMA220_SPI if SPI help Say yes here to add support for the Bosch BMA220 triaxial @@ -229,7 +230,13 @@ config BMA220 To compile this driver as a module, choose M here: the module will be called bma220_core and you will also get - bma220_spi if SPI is enabled. + bma220_i2c if I2C is enabled and bma220_spi if SPI is + enabled. + +config BMA220_I2C + tristate + select REGMAP_I2C + depends on BMA220 config BMA220_SPI tristate diff --git a/drivers/iio/accel/Makefile b/drivers/iio/accel/Makefile index 56a9f848f7f9..fa440a859283 100644 --- a/drivers/iio/accel/Makefile +++ b/drivers/iio/accel/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_ADXL380_I2C) += adxl380_i2c.o obj-$(CONFIG_ADXL380_SPI) += adxl380_spi.o obj-$(CONFIG_BMA180) += bma180.o obj-$(CONFIG_BMA220) += bma220_core.o +obj-$(CONFIG_BMA220_I2C) += bma220_i2c.o obj-$(CONFIG_BMA220_SPI) += bma220_spi.o obj-$(CONFIG_BMA400) += bma400_core.o obj-$(CONFIG_BMA400_I2C) += bma400_i2c.o diff --git a/drivers/iio/accel/bma220.h b/drivers/iio/accel/bma220.h index 695f491bc5a0..e53ca63de54b 100644 --- a/drivers/iio/accel/bma220.h +++ b/drivers/iio/accel/bma220.h @@ -13,6 +13,7 @@ struct device; +extern const struct regmap_config bma220_i2c_regmap_config; extern const struct regmap_config bma220_spi_regmap_config; extern const struct dev_pm_ops bma220_pm_ops; diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 404fea26e3f7..050827bfb9e6 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -173,6 +173,24 @@ const struct regmap_config bma220_spi_regmap_config = { }; EXPORT_SYMBOL_NS_GPL(bma220_spi_regmap_config, "IIO_BOSCH_BMA220"); +/* + * Based on the datasheet the memory map differs between the SPI and the I2C + * implementations. I2C register addresses are simply shifted to the left + * by 1 bit yet the register size remains unchanged. + * This driver employs the SPI memory map to correlate register names to + * addresses regardless of the bus type. + */ + +const struct regmap_config bma220_i2c_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .reg_shift = -1, + .max_register = BMA220_REG_SOFTRESET, + .cache_type = REGCACHE_NONE, + .writeable_reg = bma220_is_writable_reg, +}; +EXPORT_SYMBOL_NS_GPL(bma220_i2c_regmap_config, "IIO_BOSCH_BMA220"); + static irqreturn_t bma220_trigger_handler(int irq, void *p) { int ret; diff --git a/drivers/iio/accel/bma220_i2c.c b/drivers/iio/accel/bma220_i2c.c new file mode 100644 index 000000000000..5dc7c38f53b3 --- /dev/null +++ b/drivers/iio/accel/bma220_i2c.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Bosch triaxial acceleration sensor + * + * Copyright (c) 2025 Petre Rodan + * + * Datasheet: https://media.digikey.com/pdf/Data%20Sheets/Bosch/BMA220.pdf + * I2C address is either 0x0b or 0x0a depending on CSB (pin 10) + */ + +#include +#include +#include +#include +#include +#include + +#include "bma220.h" + +static int bma220_i2c_probe(struct i2c_client *client) +{ + struct regmap *regmap; + + regmap = devm_regmap_init_i2c(client, &bma220_i2c_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&client->dev, PTR_ERR(regmap), + "failed to create regmap\n"); + + return bma220_common_probe(&client->dev, regmap, client->irq); +} + +static const struct of_device_id bma220_i2c_match[] = { + { .compatible = "bosch,bma220" }, + { } +}; +MODULE_DEVICE_TABLE(of, bma220_i2c_match); + +static const struct i2c_device_id bma220_i2c_id[] = { + { "bma220" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, bma220_i2c_id); + +static struct i2c_driver bma220_i2c_driver = { + .driver = { + .name = "bma220_i2c", + .pm = pm_sleep_ptr(&bma220_pm_ops), + .of_match_table = bma220_i2c_match, + }, + .probe = bma220_i2c_probe, + .id_table = bma220_i2c_id, +}; +module_i2c_driver(bma220_i2c_driver); + +MODULE_AUTHOR("Petre Rodan "); +MODULE_DESCRIPTION("Bosch triaxial acceleration sensor i2c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_BOSCH_BMA220"); From 3499375209ca839a741e775d579f8bb9b85529d5 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:24 +0300 Subject: [PATCH 056/150] iio: accel: bma220: add i2c watchdog feature Sometimes the sensor gets stuck and enters a condition in which it pulls SDA low, thus making the entire i2c bus unusable. This problem is mitigated by activating a 1ms watchdog implemented in the sensor. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 050827bfb9e6..38a2f36783cd 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -356,6 +357,12 @@ static int bma220_power(struct bma220_data *data, bool up) return -EBUSY; } +static int bma220_set_wdt(struct bma220_data *data, const u8 val) +{ + return regmap_update_bits(data->regmap, BMA220_REG_WDT, BMA220_WDT_MASK, + FIELD_PREP(BMA220_WDT_MASK, val)); +} + static int bma220_init(struct device *dev, struct bma220_data *data) { int ret; @@ -384,6 +391,13 @@ static int bma220_init(struct device *dev, struct bma220_data *data) if (ret) return dev_err_probe(dev, ret, "Failed to soft reset chip\n"); + if (i2c_verify_client(dev)) { + ret = bma220_set_wdt(data, BMA220_WDT_1MS); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set i2c watchdog\n"); + } + return 0; } From b7e17ca107931c83c4b4edc8a02118121fe8ab9e Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:25 +0300 Subject: [PATCH 057/150] iio: accel: bma220: add interrupt trigger Add interrupt trigger. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 38a2f36783cd..08c4dec67d5e 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -124,6 +125,7 @@ struct bma220_data { struct regmap *regmap; struct mutex lock; u8 range_idx; + struct iio_trigger *trig; struct { s8 chans[3]; /* Ensure timestamp is naturally aligned. */ @@ -192,6 +194,22 @@ const struct regmap_config bma220_i2c_regmap_config = { }; EXPORT_SYMBOL_NS_GPL(bma220_i2c_regmap_config, "IIO_BOSCH_BMA220"); +static int bma220_data_rdy_trigger_set_state(struct iio_trigger *trig, + bool state) +{ + struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); + struct bma220_data *data = iio_priv(indio_dev); + + return regmap_update_bits(data->regmap, BMA220_REG_IE0, + BMA220_INT_EN_DRDY_MSK, + FIELD_PREP(BMA220_INT_EN_DRDY_MSK, state)); +} + +static const struct iio_trigger_ops bma220_trigger_ops = { + .set_trigger_state = &bma220_data_rdy_trigger_set_state, + .validate_device = &iio_trigger_validate_own_device, +}; + static irqreturn_t bma220_trigger_handler(int irq, void *p) { int ret; @@ -414,6 +432,23 @@ static void bma220_deinit(void *data_ptr) ERR_PTR(ret)); } +static irqreturn_t bma220_irq_handler(int irq, void *private) +{ + struct iio_dev *indio_dev = private; + struct bma220_data *data = iio_priv(indio_dev); + int ret; + unsigned int bma220_reg_if1; + + ret = regmap_read(data->regmap, BMA220_REG_IF1, &bma220_reg_if1); + if (ret) + return IRQ_NONE; + + if (FIELD_GET(BMA220_IF_DRDY, bma220_reg_if1)) + iio_trigger_poll_nested(data->trig); + + return IRQ_HANDLED; +} + int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq) { int ret; @@ -442,6 +477,29 @@ int bma220_common_probe(struct device *dev, struct regmap *regmap, int irq) indio_dev->num_channels = ARRAY_SIZE(bma220_channels); indio_dev->available_scan_masks = bma220_accel_scan_masks; + if (irq > 0) { + data->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &bma220_trigger_ops; + iio_trigger_set_drvdata(data->trig, indio_dev); + + ret = devm_iio_trigger_register(dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, + "iio trigger register fail\n"); + indio_dev->trig = iio_trigger_get(data->trig); + ret = devm_request_threaded_irq(dev, irq, NULL, + &bma220_irq_handler, IRQF_ONESHOT, + indio_dev->name, indio_dev); + if (ret) + return dev_err_probe(dev, ret, + "request irq %d failed\n", irq); + } + ret = devm_add_action_or_reset(dev, bma220_deinit, data); if (ret) return ret; From 59a212d3c3d4cf239f4d1c71a19b47c0e72eca2d Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:26 +0300 Subject: [PATCH 058/150] iio: accel: bma220: add LPF cut-off frequency mapping Add mapping for the low pass filter cut-off frequency. Make valid values visible for both the cut-off frequency and the scale. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 59 ++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 08c4dec67d5e..0ceffc412664 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -94,13 +94,23 @@ #define BMA220_DEVICE_NAME "bma220" +#define BMA220_COF_1000Hz 0x0 +#define BMA220_COF_500Hz 0x1 +#define BMA220_COF_250Hz 0x2 +#define BMA220_COF_125Hz 0x3 +#define BMA220_COF_64Hz 0x4 +#define BMA220_COF_32Hz 0x5 + #define BMA220_ACCEL_CHANNEL(index, reg, axis) { \ .type = IIO_ACCEL, \ .address = reg, \ .modified = 1, \ .channel2 = IIO_MOD_##axis, \ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE) |\ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ .scan_index = index, \ .scan_type = { \ .sign = 's', \ @@ -124,6 +134,7 @@ static const int bma220_scale_table[][2] = { struct bma220_data { struct regmap *regmap; struct mutex lock; + u8 lpf_3dB_freq_idx; u8 range_idx; struct iio_trigger *trig; struct { @@ -140,6 +151,16 @@ static const struct iio_chan_spec bma220_channels[] = { IIO_CHAN_SOFT_TIMESTAMP(3), }; +/* Available cut-off frequencies of the low pass filter in Hz. */ +static const int bma220_lpf_3dB_freq_Hz_table[] = { + [BMA220_COF_1000Hz] = 1000, + [BMA220_COF_500Hz] = 500, + [BMA220_COF_250Hz] = 250, + [BMA220_COF_125Hz] = 125, + [BMA220_COF_64Hz] = 64, + [BMA220_COF_32Hz] = 32, +}; + static const unsigned long bma220_accel_scan_masks[] = { BIT(AXIS_X) | BIT(AXIS_Y) | BIT(AXIS_Z), 0 @@ -254,6 +275,10 @@ static int bma220_read_raw(struct iio_dev *indio_dev, *val = bma220_scale_table[index][0]; *val2 = bma220_scale_table[index][1]; return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + index = data->lpf_3dB_freq_idx; + *val = bma220_lpf_3dB_freq_Hz_table[index]; + return IIO_VAL_INT; } return -EINVAL; @@ -272,6 +297,18 @@ static int bma220_find_match_2dt(const int (*tbl)[2], const int n, return -EINVAL; } +static int bma220_find_match(const int *arr, const int n, const int val) +{ + int i; + + for (i = 0; i < n; i++) { + if (arr[i] == val) + return i; + } + + return -EINVAL; +} + static int bma220_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) @@ -297,6 +334,21 @@ static int bma220_write_raw(struct iio_dev *indio_dev, return ret; data->range_idx = index; + return 0; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + index = bma220_find_match(bma220_lpf_3dB_freq_Hz_table, + ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table), + val); + if (index < 0) + return -EINVAL; + + ret = regmap_update_bits(data->regmap, BMA220_REG_FILTER, + BMA220_FILTER_MASK, + FIELD_PREP(BMA220_FILTER_MASK, index)); + if (ret < 0) + return ret; + data->lpf_3dB_freq_idx = index; + return 0; } @@ -314,6 +366,11 @@ static int bma220_read_avail(struct iio_dev *indio_dev, *type = IIO_VAL_INT_PLUS_MICRO; *length = ARRAY_SIZE(bma220_scale_table) * 2; return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = (const int *)bma220_lpf_3dB_freq_Hz_table; + *type = IIO_VAL_INT; + *length = ARRAY_SIZE(bma220_lpf_3dB_freq_Hz_table); + return IIO_AVAIL_LIST; default: return -EINVAL; } From c3ee72998b33a5c9fc9ffc99b660a0f33c380af3 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:27 +0300 Subject: [PATCH 059/150] iio: accel: bma220: add debugfs reg access Allow read/write access to sensor registers for use in unit-tests. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 0ceffc412664..2531d6a54ff0 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -376,10 +376,21 @@ static int bma220_read_avail(struct iio_dev *indio_dev, } } +static int bma220_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct bma220_data *data = iio_priv(indio_dev); + + if (readval) + return regmap_read(data->regmap, reg, readval); + return regmap_write(data->regmap, reg, writeval); +} + static const struct iio_info bma220_info = { .read_raw = bma220_read_raw, .write_raw = bma220_write_raw, .read_avail = bma220_read_avail, + .debugfs_reg_access = &bma220_reg_access, }; static int bma220_reset(struct bma220_data *data, bool up) From efbce18231b4b78959bb562906728e515ab17463 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Sun, 5 Oct 2025 16:12:28 +0300 Subject: [PATCH 060/150] iio: accel: bma220: add maintainer Add maintainer for this driver. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 08fcce3208cf..3760f14493b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4467,6 +4467,13 @@ F: include/net/bond* F: include/uapi/linux/if_bonding.h F: tools/testing/selftests/drivers/net/bonding/ +BOSCH SENSORTEC BMA220 ACCELEROMETER IIO DRIVER +M: Petre Rodan +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/accel/bosch,bma220.yaml +F: drivers/iio/accel/bma220* + BOSCH SENSORTEC BMA400 ACCELEROMETER IIO DRIVER M: Dan Robertson L: linux-iio@vger.kernel.org From 95f934b2dd518016747aefbaecff8017e65f835d Mon Sep 17 00:00:00 2001 From: Eddie James Date: Tue, 7 Oct 2025 14:16:12 -0500 Subject: [PATCH 061/150] dt-bindings: iio: Add Infineon DPS310 sensor documentation The DPS310 is a barometric pressure and temperature sensor with an I2C interface. Remove it from trivial-devices.yaml and add its own documentation to allow for consumers of this device such as the iio/hwmon bridge. Signed-off-by: Eddie James Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../iio/pressure/infineon,dps310.yaml | 54 +++++++++++++++++++ .../devicetree/bindings/trivial-devices.yaml | 2 - MAINTAINERS | 1 + 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml diff --git a/Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml b/Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml new file mode 100644 index 000000000000..e5d1e6c48939 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/pressure/infineon,dps310.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Infineon DPS310 barometric pressure and temperature sensor + +maintainers: + - Eddie James + +description: + The DPS310 is a barometric pressure and temperature sensor with an I2C + interface. + +properties: + compatible: + enum: + - infineon,dps310 + + reg: + maxItems: 1 + + "#io-channel-cells": + const: 0 + + vdd-supply: + description: + Voltage supply for the chip's analog blocks. + + vddio-supply: + description: + Digital voltage supply for the chip's digital blocks and I/O interface. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + dps: pressure-sensor@76 { + compatible = "infineon,dps310"; + reg = <0x76>; + #io-channel-cells = <0>; + vdd-supply = <&vref1>; + vddio-supply = <&vref2>; + }; + }; diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 2be5fb41b410..8a10b0a9ccc5 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -125,8 +125,6 @@ properties: - ibm,cffps2 # IBM On-Chip Controller hwmon device - ibm,p8-occ-hwmon - # Infineon barometric pressure and temperature sensor - - infineon,dps310 # Infineon IR36021 digital POL buck controller - infineon,ir36021 # Infineon IRPS5401 Voltage Regulator (PMIC) diff --git a/MAINTAINERS b/MAINTAINERS index 3760f14493b9..371ac55d0edc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12228,6 +12228,7 @@ INFINEON DPS310 Driver M: Eddie James L: linux-iio@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/iio/pressure/infineon,dps310.yaml F: drivers/iio/pressure/dps310.c INFINEON PEB2466 ASoC CODEC From 7d55d00185ddfba53b66efbbde33b8cf87fe7a1d Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:02 +0000 Subject: [PATCH 062/150] dt-bindings: iio: imu: Add inv_icm45600 Document the ICM-45600 devices devicetree bindings. Specific variants of the device are defined because of their differences in terms of FSR or advanced features like eDMP. Signed-off-by: Remi Buisson Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../bindings/iio/imu/invensense,icm45600.yaml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml new file mode 100644 index 000000000000..e0b78d14420f --- /dev/null +++ b/Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/imu/invensense,icm45600.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: InvenSense ICM-45600 Inertial Measurement Unit + +maintainers: + - Remi Buisson + +description: | + 6-axis MotionTracking device that combines a 3-axis gyroscope and a 3-axis + accelerometer. + + It has a configurable host interface that supports I3C, I2C and SPI serial + communication, features up to 8kB FIFO and 2 programmable interrupts with + ultra-low-power wake-on-motion support to minimize system power consumption. + + Other industry-leading features include InvenSense on-chip APEX Motion + Processing engine for gesture recognition, activity classification, and + pedometer, along with programmable digital filters, and an embedded + temperature sensor. + + https://invensense.tdk.com/wp-content/uploads/documentation/DS-000576_ICM-45605.pdf + +properties: + compatible: + enum: + - invensense,icm45605 + - invensense,icm45606 + - invensense,icm45608 + - invensense,icm45634 + - invensense,icm45686 + - invensense,icm45687 + - invensense,icm45688p + - invensense,icm45689 + + reg: + maxItems: 1 + + interrupts: + minItems: 1 + maxItems: 2 + + interrupt-names: + minItems: 1 + items: + - enum: [int1, int2] + - const: int2 + description: Choose chip interrupt pin to be used as interrupt input. + + drive-open-drain: + type: boolean + + vdd-supply: true + + vddio-supply: true + + mount-matrix: true + +required: + - compatible + - reg + - vdd-supply + - vddio-supply + +unevaluatedProperties: false + +examples: + - | + #include + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + imu@68 { + compatible = "invensense,icm45605"; + reg = <0x68>; + interrupt-parent = <&gpio2>; + interrupt-names = "int1"; + interrupts = <7 IRQ_TYPE_EDGE_RISING>; + vdd-supply = <&vdd>; + vddio-supply = <&vddio>; + mount-matrix = "0", "-1", "0", + "1", "0", "0", + "0", "0", "1"; + }; + }; From 7ff021a3faca4233d53c994d7bc85823caa714db Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:03 +0000 Subject: [PATCH 063/150] iio: imu: inv_icm45600: add new inv_icm45600 driver Core component of a new driver for InvenSense ICM-45600 devices. It includes registers definition, main probe/setup, and device utility functions. ICM-456xx devices are latest generation of 6-axis IMU, gyroscope+accelerometer and temperature sensor. This device includes a 8K FIFO, supports I2C/I3C/SPI, and provides intelligent motion features like pedometer, tilt detection, and tap detection. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/inv_icm45600/Kconfig | 5 + drivers/iio/imu/inv_icm45600/Makefile | 4 + drivers/iio/imu/inv_icm45600/inv_icm45600.h | 331 ++++++++++ .../iio/imu/inv_icm45600/inv_icm45600_core.c | 621 ++++++++++++++++++ 6 files changed, 963 insertions(+) create mode 100644 drivers/iio/imu/inv_icm45600/Kconfig create mode 100644 drivers/iio/imu/inv_icm45600/Makefile create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600.h create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_core.c diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 15612f0f189b..9d732bed9fcd 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -109,6 +109,7 @@ config KMX61 be called kmx61. source "drivers/iio/imu/inv_icm42600/Kconfig" +source "drivers/iio/imu/inv_icm45600/Kconfig" source "drivers/iio/imu/inv_mpu6050/Kconfig" config SMI240 diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index e901aea498d3..2ae6344f8469 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_FXOS8700_I2C) += fxos8700_i2c.o obj-$(CONFIG_FXOS8700_SPI) += fxos8700_spi.o obj-y += inv_icm42600/ +obj-y += inv_icm45600/ obj-y += inv_mpu6050/ obj-$(CONFIG_KMX61) += kmx61.o diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig new file mode 100644 index 000000000000..8cb5543e0a58 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +config INV_ICM45600 + tristate + select IIO_INV_SENSORS_TIMESTAMP diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile new file mode 100644 index 000000000000..4f442b61896e --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o +inv-icm45600-y += inv_icm45600_core.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h new file mode 100644 index 000000000000..5f637e2f2ec8 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h @@ -0,0 +1,331 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2025 Invensense, Inc. */ + +#ifndef INV_ICM45600_H_ +#define INV_ICM45600_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define INV_ICM45600_REG_BANK_MASK GENMASK(15, 8) +#define INV_ICM45600_REG_ADDR_MASK GENMASK(7, 0) + +enum inv_icm45600_sensor_mode { + INV_ICM45600_SENSOR_MODE_OFF, + INV_ICM45600_SENSOR_MODE_STANDBY, + INV_ICM45600_SENSOR_MODE_LOW_POWER, + INV_ICM45600_SENSOR_MODE_LOW_NOISE, + INV_ICM45600_SENSOR_MODE_MAX +}; + +/* gyroscope fullscale values */ +enum inv_icm45600_gyro_fs { + INV_ICM45600_GYRO_FS_2000DPS, + INV_ICM45600_GYRO_FS_1000DPS, + INV_ICM45600_GYRO_FS_500DPS, + INV_ICM45600_GYRO_FS_250DPS, + INV_ICM45600_GYRO_FS_125DPS, + INV_ICM45600_GYRO_FS_62_5DPS, + INV_ICM45600_GYRO_FS_31_25DPS, + INV_ICM45600_GYRO_FS_15_625DPS, + INV_ICM45600_GYRO_FS_MAX +}; + +enum inv_icm45686_gyro_fs { + INV_ICM45686_GYRO_FS_4000DPS, + INV_ICM45686_GYRO_FS_2000DPS, + INV_ICM45686_GYRO_FS_1000DPS, + INV_ICM45686_GYRO_FS_500DPS, + INV_ICM45686_GYRO_FS_250DPS, + INV_ICM45686_GYRO_FS_125DPS, + INV_ICM45686_GYRO_FS_62_5DPS, + INV_ICM45686_GYRO_FS_31_25DPS, + INV_ICM45686_GYRO_FS_15_625DPS, + INV_ICM45686_GYRO_FS_MAX +}; + +/* accelerometer fullscale values */ +enum inv_icm45600_accel_fs { + INV_ICM45600_ACCEL_FS_16G, + INV_ICM45600_ACCEL_FS_8G, + INV_ICM45600_ACCEL_FS_4G, + INV_ICM45600_ACCEL_FS_2G, + INV_ICM45600_ACCEL_FS_MAX +}; + +enum inv_icm45686_accel_fs { + INV_ICM45686_ACCEL_FS_32G, + INV_ICM45686_ACCEL_FS_16G, + INV_ICM45686_ACCEL_FS_8G, + INV_ICM45686_ACCEL_FS_4G, + INV_ICM45686_ACCEL_FS_2G, + INV_ICM45686_ACCEL_FS_MAX +}; + +/* ODR suffixed by LN or LP are Low-Noise or Low-Power mode only */ +enum inv_icm45600_odr { + INV_ICM45600_ODR_6400HZ_LN = 0x03, + INV_ICM45600_ODR_3200HZ_LN, + INV_ICM45600_ODR_1600HZ_LN, + INV_ICM45600_ODR_800HZ_LN, + INV_ICM45600_ODR_400HZ, + INV_ICM45600_ODR_200HZ, + INV_ICM45600_ODR_100HZ, + INV_ICM45600_ODR_50HZ, + INV_ICM45600_ODR_25HZ, + INV_ICM45600_ODR_12_5HZ, + INV_ICM45600_ODR_6_25HZ_LP, + INV_ICM45600_ODR_3_125HZ_LP, + INV_ICM45600_ODR_1_5625HZ_LP, + INV_ICM45600_ODR_MAX +}; + +struct inv_icm45600_sensor_conf { + u8 mode; + u8 fs; + u8 odr; + u8 filter; +}; + +struct inv_icm45600_conf { + struct inv_icm45600_sensor_conf gyro; + struct inv_icm45600_sensor_conf accel; +}; + +struct inv_icm45600_suspended { + enum inv_icm45600_sensor_mode gyro; + enum inv_icm45600_sensor_mode accel; +}; + +struct inv_icm45600_chip_info { + u8 whoami; + const char *name; + const struct inv_icm45600_conf *conf; +}; + +/** + * struct inv_icm45600_state - driver state variables + * @lock: lock for serializing multiple registers access. + * @map: regmap pointer. + * @vddio_supply: I/O voltage regulator for the chip. + * @orientation: sensor chip orientation relative to main hardware. + * @conf: chip sensors configurations. + * @suspended: suspended sensors configuration. + * @indio_gyro: gyroscope IIO device. + * @indio_accel: accelerometer IIO device. + * @chip_info: chip driver data. + * @timestamp: interrupt timestamps. + * @buffer: data transfer buffer aligned for DMA. + */ +struct inv_icm45600_state { + struct mutex lock; + struct regmap *map; + struct regulator *vddio_supply; + struct iio_mount_matrix orientation; + struct inv_icm45600_conf conf; + struct inv_icm45600_suspended suspended; + struct iio_dev *indio_gyro; + struct iio_dev *indio_accel; + const struct inv_icm45600_chip_info *chip_info; + struct { + s64 gyro; + s64 accel; + } timestamp; + union { + u8 buff[2]; + __le16 u16; + u8 ireg[3]; + } buffer __aligned(IIO_DMA_MINALIGN); +}; + +/** + * struct inv_icm45600_sensor_state - sensor state variables + * @scales: table of scales. + * @scales_len: length (nb of items) of the scales table. + * @power_mode: sensor requested power mode (for common frequencies) + * @ts: timestamp module states. + */ +struct inv_icm45600_sensor_state { + const int *scales; + size_t scales_len; + enum inv_icm45600_sensor_mode power_mode; + struct inv_sensors_timestamp ts; +}; + +/* Virtual register addresses: @bank on MSB (16 bits), @address on LSB */ + +/* Indirect register access */ +#define INV_ICM45600_REG_IREG_ADDR 0x7C +#define INV_ICM45600_REG_IREG_DATA 0x7E + +/* Direct acces registers */ +#define INV_ICM45600_REG_MISC2 0x007F +#define INV_ICM45600_MISC2_SOFT_RESET BIT(1) + +#define INV_ICM45600_REG_DRIVE_CONFIG0 0x0032 +#define INV_ICM45600_DRIVE_CONFIG0_SPI_MASK GENMASK(3, 1) +#define INV_ICM45600_SPI_SLEW_RATE_0_5NS 6 +#define INV_ICM45600_SPI_SLEW_RATE_4NS 5 +#define INV_ICM45600_SPI_SLEW_RATE_5NS 4 +#define INV_ICM45600_SPI_SLEW_RATE_7NS 3 +#define INV_ICM45600_SPI_SLEW_RATE_10NS 2 +#define INV_ICM45600_SPI_SLEW_RATE_14NS 1 +#define INV_ICM45600_SPI_SLEW_RATE_38NS 0 + +#define INV_ICM45600_REG_INT1_CONFIG2 0x0018 +#define INV_ICM45600_INT1_CONFIG2_PUSH_PULL BIT(2) +#define INV_ICM45600_INT1_CONFIG2_LATCHED BIT(1) +#define INV_ICM45600_INT1_CONFIG2_ACTIVE_HIGH BIT(0) +#define INV_ICM45600_INT1_CONFIG2_ACTIVE_LOW 0x00 + +#define INV_ICM45600_REG_FIFO_CONFIG0 0x001D +#define INV_ICM45600_FIFO_CONFIG0_MODE_MASK GENMASK(7, 6) +#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_MAX 0x1F + +#define INV_ICM45600_REG_FIFO_CONFIG2 0x0020 +#define INV_ICM45600_REG_FIFO_CONFIG2_FIFO_FLUSH BIT(7) +#define INV_ICM45600_REG_FIFO_CONFIG2_WM_GT_TH BIT(3) + +#define INV_ICM45600_REG_FIFO_CONFIG3 0x0021 +#define INV_ICM45600_FIFO_CONFIG3_ES1_EN BIT(5) +#define INV_ICM45600_FIFO_CONFIG3_ES0_EN BIT(4) +#define INV_ICM45600_FIFO_CONFIG3_HIRES_EN BIT(3) +#define INV_ICM45600_FIFO_CONFIG3_GYRO_EN BIT(2) +#define INV_ICM45600_FIFO_CONFIG3_ACCEL_EN BIT(1) +#define INV_ICM45600_FIFO_CONFIG3_IF_EN BIT(0) + +#define INV_ICM45600_REG_FIFO_CONFIG4 0x0022 +#define INV_ICM45600_FIFO_CONFIG4_COMP_EN BIT(2) +#define INV_ICM45600_FIFO_CONFIG4_TMST_FSYNC_EN BIT(1) +#define INV_ICM45600_FIFO_CONFIG4_ES0_9B BIT(0) + +/* all sensor data are 16 bits (2 registers wide) in big-endian */ +#define INV_ICM45600_REG_TEMP_DATA 0x000C +#define INV_ICM45600_REG_ACCEL_DATA_X 0x0000 +#define INV_ICM45600_REG_ACCEL_DATA_Y 0x0002 +#define INV_ICM45600_REG_ACCEL_DATA_Z 0x0004 +#define INV_ICM45600_REG_GYRO_DATA_X 0x0006 +#define INV_ICM45600_REG_GYRO_DATA_Y 0x0008 +#define INV_ICM45600_REG_GYRO_DATA_Z 0x000A + +#define INV_ICM45600_REG_INT_STATUS 0x0019 +#define INV_ICM45600_INT_STATUS_RESET_DONE BIT(7) +#define INV_ICM45600_INT_STATUS_AUX1_AGC_RDY BIT(6) +#define INV_ICM45600_INT_STATUS_AP_AGC_RDY BIT(5) +#define INV_ICM45600_INT_STATUS_AP_FSYNC BIT(4) +#define INV_ICM45600_INT_STATUS_AUX1_DRDY BIT(3) +#define INV_ICM45600_INT_STATUS_DATA_RDY BIT(2) +#define INV_ICM45600_INT_STATUS_FIFO_THS BIT(1) +#define INV_ICM45600_INT_STATUS_FIFO_FULL BIT(0) + +/* + * FIFO access registers + * FIFO count is 16 bits (2 registers) + * FIFO data is a continuous read register to read FIFO content + */ +#define INV_ICM45600_REG_FIFO_COUNT 0x0012 +#define INV_ICM45600_REG_FIFO_DATA 0x0014 + +#define INV_ICM45600_REG_PWR_MGMT0 0x0010 +#define INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK GENMASK(3, 2) +#define INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK GENMASK(1, 0) + +#define INV_ICM45600_REG_ACCEL_CONFIG0 0x001B +#define INV_ICM45600_ACCEL_CONFIG0_FS_MASK GENMASK(6, 4) +#define INV_ICM45600_ACCEL_CONFIG0_ODR_MASK GENMASK(3, 0) +#define INV_ICM45600_REG_GYRO_CONFIG0 0x001C +#define INV_ICM45600_GYRO_CONFIG0_FS_MASK GENMASK(7, 4) +#define INV_ICM45600_GYRO_CONFIG0_ODR_MASK GENMASK(3, 0) + +#define INV_ICM45600_REG_SMC_CONTROL_0 0xA258 +#define INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL BIT(4) +#define INV_ICM45600_SMC_CONTROL_0_TMST_EN BIT(0) + +/* FIFO watermark is 16 bits (2 registers wide) in little-endian */ +#define INV_ICM45600_REG_FIFO_WATERMARK 0x001E + +/* FIFO is configured for 8kb */ +#define INV_ICM45600_FIFO_SIZE_MAX SZ_8K + +#define INV_ICM45600_REG_INT1_CONFIG0 0x0016 +#define INV_ICM45600_INT1_CONFIG0_RESET_DONE_EN BIT(7) +#define INV_ICM45600_INT1_CONFIG0_AUX1_AGC_RDY_EN BIT(6) +#define INV_ICM45600_INT1_CONFIG0_AP_AGC_RDY_EN BIT(5) +#define INV_ICM45600_INT1_CONFIG0_AP_FSYNC_EN BIT(4) +#define INV_ICM45600_INT1_CONFIG0_AUX1_DRDY_EN BIT(3) +#define INV_ICM45600_INT1_CONFIG0_DRDY_EN BIT(2) +#define INV_ICM45600_INT1_CONFIG0_FIFO_THS_EN BIT(1) +#define INV_ICM45600_INT1_CONFIG0_FIFO_FULL_EN BIT(0) + +#define INV_ICM45600_REG_WHOAMI 0x0072 +#define INV_ICM45600_WHOAMI_ICM45605 0xE5 +#define INV_ICM45600_WHOAMI_ICM45686 0xE9 +#define INV_ICM45600_WHOAMI_ICM45688P 0xE7 +#define INV_ICM45600_WHOAMI_ICM45608 0x81 +#define INV_ICM45600_WHOAMI_ICM45634 0x82 +#define INV_ICM45600_WHOAMI_ICM45689 0x83 +#define INV_ICM45600_WHOAMI_ICM45606 0x84 +#define INV_ICM45600_WHOAMI_ICM45687 0x85 + +/* Gyro USER offset */ +#define INV_ICM45600_IPREG_SYS1_REG_42 0xA42A +#define INV_ICM45600_IPREG_SYS1_REG_56 0xA438 +#define INV_ICM45600_IPREG_SYS1_REG_70 0xA446 +#define INV_ICM45600_GYRO_OFFUSER_MASK GENMASK(13, 0) +/* Gyro Averaging filter */ +#define INV_ICM45600_IPREG_SYS1_REG_170 0xA4AA +#define INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK GENMASK(4, 1) +#define INV_ICM45600_GYRO_LP_AVG_SEL_8X 5 +#define INV_ICM45600_GYRO_LP_AVG_SEL_2X 1 +/* Accel USER offset */ +#define INV_ICM45600_IPREG_SYS2_REG_24 0xA518 +#define INV_ICM45600_IPREG_SYS2_REG_32 0xA520 +#define INV_ICM45600_IPREG_SYS2_REG_40 0xA528 +#define INV_ICM45600_ACCEL_OFFUSER_MASK GENMASK(13, 0) +/* Accel averaging filter */ +#define INV_ICM45600_IPREG_SYS2_REG_129 0xA581 +#define INV_ICM45600_ACCEL_LP_AVG_SEL_1X 0x0000 +#define INV_ICM45600_ACCEL_LP_AVG_SEL_4X 0x0002 + +/* Sleep times required by the driver */ +#define INV_ICM45600_ACCEL_STARTUP_TIME_MS 60 +#define INV_ICM45600_GYRO_STARTUP_TIME_MS 60 +#define INV_ICM45600_GYRO_STOP_TIME_MS 150 +#define INV_ICM45600_IREG_DELAY_US 4 + +typedef int (*inv_icm45600_bus_setup)(struct inv_icm45600_state *); + +extern const struct dev_pm_ops inv_icm45600_pm_ops; + +const struct iio_mount_matrix * +inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan); + +u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr); + +int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms); + +int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval); + +int inv_icm45600_core_probe(struct regmap *regmap, + const struct inv_icm45600_chip_info *chip_info, + bool reset, inv_icm45600_bus_setup bus_setup); + +#endif diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c new file mode 100644 index 000000000000..280cdd40f86b --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c @@ -0,0 +1,621 @@ +// 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 + +#include "inv_icm45600.h" + +static int inv_icm45600_ireg_read(struct regmap *map, unsigned int reg, + u8 *data, size_t count) +{ + const struct device *dev = regmap_get_device(map); + struct inv_icm45600_state *st = dev_get_drvdata(dev); + unsigned int d; + size_t i; + int ret; + + st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg); + st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg); + + /* Burst write address. */ + ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 2); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + + /* Read the data. */ + for (i = 0; i < count; i++) { + ret = regmap_read(map, INV_ICM45600_REG_IREG_DATA, &d); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + data[i] = d; + } + + return 0; +} + +static int inv_icm45600_ireg_write(struct regmap *map, unsigned int reg, + const u8 *data, size_t count) +{ + const struct device *dev = regmap_get_device(map); + struct inv_icm45600_state *st = dev_get_drvdata(dev); + size_t i; + int ret; + + st->buffer.ireg[0] = FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg); + st->buffer.ireg[1] = FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg); + st->buffer.ireg[2] = data[0]; + + /* Burst write address and first byte. */ + ret = regmap_bulk_write(map, INV_ICM45600_REG_IREG_ADDR, st->buffer.ireg, 3); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + + /* Write the remaining bytes. */ + for (i = 1; i < count; i++) { + ret = regmap_write(map, INV_ICM45600_REG_IREG_DATA, data[i]); + /* + * Wait while the device is busy processing the address. + * Datasheet: 13.3 MINIMUM WAIT TIME-GAP + */ + fsleep(INV_ICM45600_IREG_DELAY_US); + if (ret) + return ret; + } + + return 0; +} + +static int inv_icm45600_read(void *context, const void *reg_buf, size_t reg_size, + void *val_buf, size_t val_size) +{ + unsigned int reg = be16_to_cpup(reg_buf); + struct regmap *map = context; + + if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg)) + return inv_icm45600_ireg_read(map, reg, val_buf, val_size); + + return regmap_bulk_read(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg), + val_buf, val_size); +} + +static int inv_icm45600_write(void *context, const void *data, size_t count) +{ + const u8 *d = data; + unsigned int reg = be16_to_cpup(data); + struct regmap *map = context; + + if (FIELD_GET(INV_ICM45600_REG_BANK_MASK, reg)) + return inv_icm45600_ireg_write(map, reg, d + 2, count - 2); + + return regmap_bulk_write(map, FIELD_GET(INV_ICM45600_REG_ADDR_MASK, reg), + d + 2, count - 2); +} + +static const struct regmap_bus inv_icm45600_regmap_bus = { + .read = inv_icm45600_read, + .write = inv_icm45600_write, +}; + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 16, + .val_bits = 8, +}; + +const struct iio_mount_matrix * +inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + const struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + return &st->orientation; +} + +u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr) +{ + static const u32 odr_periods[INV_ICM45600_ODR_MAX] = { + /* 3 first values are reserved, left to 0 */ + [INV_ICM45600_ODR_6400HZ_LN] = 156250, + [INV_ICM45600_ODR_3200HZ_LN] = 312500, + [INV_ICM45600_ODR_1600HZ_LN] = 625000, + [INV_ICM45600_ODR_800HZ_LN] = 1250000, + [INV_ICM45600_ODR_400HZ] = 2500000, + [INV_ICM45600_ODR_200HZ] = 5000000, + [INV_ICM45600_ODR_100HZ] = 10000000, + [INV_ICM45600_ODR_50HZ] = 20000000, + [INV_ICM45600_ODR_25HZ] = 40000000, + [INV_ICM45600_ODR_12_5HZ] = 80000000, + [INV_ICM45600_ODR_6_25HZ_LP] = 160000000, + [INV_ICM45600_ODR_3_125HZ_LP] = 320000000, + [INV_ICM45600_ODR_1_5625HZ_LP] = 640000000, + }; + + return odr_periods[odr]; +} + +static int inv_icm45600_set_pwr_mgmt0(struct inv_icm45600_state *st, + enum inv_icm45600_sensor_mode gyro, + enum inv_icm45600_sensor_mode accel, + unsigned int *sleep_ms) +{ + enum inv_icm45600_sensor_mode oldgyro = st->conf.gyro.mode; + enum inv_icm45600_sensor_mode oldaccel = st->conf.accel.mode; + unsigned int sleepval; + unsigned int val; + int ret; + + /* if nothing changed, exit */ + if (gyro == oldgyro && accel == oldaccel) + return 0; + + val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, gyro) | + FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, accel); + ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + st->conf.gyro.mode = gyro; + st->conf.accel.mode = accel; + + /* Compute the required wait time for sensors to stabilize. */ + sleepval = 0; + if (accel != oldaccel && oldaccel == INV_ICM45600_SENSOR_MODE_OFF) + sleepval = max(sleepval, INV_ICM45600_ACCEL_STARTUP_TIME_MS); + + if (gyro != oldgyro) { + if (oldgyro == INV_ICM45600_SENSOR_MODE_OFF) + sleepval = max(sleepval, INV_ICM45600_GYRO_STARTUP_TIME_MS); + else if (gyro == INV_ICM45600_SENSOR_MODE_OFF) + sleepval = max(sleepval, INV_ICM45600_GYRO_STOP_TIME_MS); + } + + /* Deferred sleep value if sleep pointer is provided or direct sleep */ + if (sleep_ms) + *sleep_ms = sleepval; + else if (sleepval) + msleep(sleepval); + + return 0; +} + +static void inv_icm45600_set_default_conf(struct inv_icm45600_sensor_conf *conf, + struct inv_icm45600_sensor_conf *oldconf) +{ + /* Sanitize missing values with current values. */ + if (conf->mode == U8_MAX) + conf->mode = oldconf->mode; + if (conf->fs == U8_MAX) + conf->fs = oldconf->fs; + if (conf->odr == U8_MAX) + conf->odr = oldconf->odr; + if (conf->filter == U8_MAX) + conf->filter = oldconf->filter; +} + +int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm45600_sensor_conf *oldconf = &st->conf.accel; + unsigned int val; + int ret; + + inv_icm45600_set_default_conf(conf, oldconf); + + /* Force the power mode against the ODR when sensor is on. */ + if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) { + if (conf->odr <= INV_ICM45600_ODR_800HZ_LN) { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE; + } else { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + /* sanitize averaging value depending on ODR for low-power mode */ + /* maximum 1x @400Hz */ + if (conf->odr == INV_ICM45600_ODR_400HZ) + conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_1X; + else + conf->filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X; + } + } + + /* Set accel fullscale & odr. */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->fs) | + FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->odr); + ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* Set accel low-power average filter. */ + if (conf->filter != oldconf->filter) { + ret = regmap_write(st->map, INV_ICM45600_IPREG_SYS2_REG_129, + conf->filter); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* Update the sensor accel mode. */ + return inv_icm45600_set_pwr_mgmt0(st, st->conf.gyro.mode, conf->mode, + sleep_ms); +} + +int inv_icm45600_set_gyro_conf(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_conf *conf, + unsigned int *sleep_ms) +{ + struct inv_icm45600_sensor_conf *oldconf = &st->conf.gyro; + unsigned int val; + int ret; + + inv_icm45600_set_default_conf(conf, oldconf); + + /* Force the power mode against ODR when sensor is on. */ + if (conf->mode > INV_ICM45600_SENSOR_MODE_STANDBY) { + if (conf->odr >= INV_ICM45600_ODR_6_25HZ_LP) { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + conf->filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X; + } else { + conf->mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE; + } + } + + /* Set gyro fullscale & odr. */ + if (conf->fs != oldconf->fs || conf->odr != oldconf->odr) { + val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->fs) | + FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->odr); + ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + oldconf->fs = conf->fs; + oldconf->odr = conf->odr; + } + + /* Set gyro low-power average filter. */ + if (conf->filter != oldconf->filter) { + val = FIELD_PREP(INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, conf->filter); + ret = regmap_update_bits(st->map, INV_ICM45600_IPREG_SYS1_REG_170, + INV_ICM45600_IPREG_SYS1_170_GYRO_LP_AVG_MASK, val); + if (ret) + return ret; + oldconf->filter = conf->filter; + } + + /* Update the sensor gyro mode. */ + return inv_icm45600_set_pwr_mgmt0(st, conf->mode, st->conf.accel.mode, + sleep_ms); +} + +int inv_icm45600_debugfs_reg(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + if (readval) + return regmap_read(st->map, reg, readval); + else + return regmap_write(st->map, reg, writeval); +} + +static int inv_icm45600_set_conf(struct inv_icm45600_state *st, + const struct inv_icm45600_conf *conf) +{ + unsigned int val; + int ret; + + val = FIELD_PREP(INV_ICM45600_PWR_MGMT0_GYRO_MODE_MASK, conf->gyro.mode) | + FIELD_PREP(INV_ICM45600_PWR_MGMT0_ACCEL_MODE_MASK, conf->accel.mode); + ret = regmap_write(st->map, INV_ICM45600_REG_PWR_MGMT0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_FS_MASK, conf->gyro.fs) | + FIELD_PREP(INV_ICM45600_GYRO_CONFIG0_ODR_MASK, conf->gyro.odr); + ret = regmap_write(st->map, INV_ICM45600_REG_GYRO_CONFIG0, val); + if (ret) + return ret; + + val = FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_FS_MASK, conf->accel.fs) | + FIELD_PREP(INV_ICM45600_ACCEL_CONFIG0_ODR_MASK, conf->accel.odr); + ret = regmap_write(st->map, INV_ICM45600_REG_ACCEL_CONFIG0, val); + if (ret) + return ret; + + /* Save configuration. */ + st->conf = *conf; + + return 0; +} + +/** + * inv_icm45600_setup() - check and setup chip + * @st: driver internal state + * @chip_info: detected chip description + * @reset: define whether a reset is required or not + * @bus_setup: callback for setting up bus specific registers + * + * Returns: 0 on success, a negative error code otherwise. + */ +static int inv_icm45600_setup(struct inv_icm45600_state *st, + const struct inv_icm45600_chip_info *chip_info, + bool reset, inv_icm45600_bus_setup bus_setup) +{ + const struct device *dev = regmap_get_device(st->map); + unsigned int val; + int ret; + + /* Set chip bus configuration if specified. */ + if (bus_setup) { + ret = bus_setup(st); + if (ret) + return ret; + } + + /* Check chip self-identification value. */ + ret = regmap_read(st->map, INV_ICM45600_REG_WHOAMI, &val); + if (ret) + return ret; + if (val != chip_info->whoami) { + /* + * SPI interface has no ack mechanism. + * 0xFF or 0x00 whoami means no response from the device. + */ + if (val == U8_MAX || val == 0) + return dev_err_probe(dev, -ENODEV, + "Invalid whoami %#02x expected %#02x (%s)\n", + val, chip_info->whoami, chip_info->name); + + dev_warn(dev, "Unexpected whoami %#02x expected %#02x (%s)\n", + val, chip_info->whoami, chip_info->name); + } + + st->chip_info = chip_info; + + if (reset) { + /* Reset previous state. */ + ret = regmap_write(st->map, INV_ICM45600_REG_MISC2, + INV_ICM45600_MISC2_SOFT_RESET); + if (ret) + return ret; + /* + * IMU reset time. + * Datasheet: 16.84 REG_MISC2 + */ + fsleep(USEC_PER_MSEC); + + if (bus_setup) { + ret = bus_setup(st); + if (ret) + return ret; + } + + ret = regmap_read(st->map, INV_ICM45600_REG_INT_STATUS, &val); + if (ret) + return ret; + if (!(val & INV_ICM45600_INT_STATUS_RESET_DONE)) { + dev_err(dev, "reset error, reset done bit not set\n"); + return -ENODEV; + } + } + + return inv_icm45600_set_conf(st, chip_info->conf); +} + +static int inv_icm45600_timestamp_setup(struct inv_icm45600_state *st) +{ + /* Enable timestamps. */ + return regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0, + INV_ICM45600_SMC_CONTROL_0_TMST_EN); +} + +static int inv_icm45600_enable_regulator_vddio(struct inv_icm45600_state *st) +{ + int ret; + + ret = regulator_enable(st->vddio_supply); + if (ret) + return ret; + + /* + * Wait a little for supply ramp. + * Duration is empirically defined. + */ + fsleep(3 * USEC_PER_MSEC); + + return 0; +} + +static void inv_icm45600_disable_vddio_reg(void *_data) +{ + struct inv_icm45600_state *st = _data; + struct device *dev = regmap_get_device(st->map); + + if (pm_runtime_status_suspended(dev)) + return; + + regulator_disable(st->vddio_supply); +} + +int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chip_info *chip_info, + bool reset, inv_icm45600_bus_setup bus_setup) +{ + struct device *dev = regmap_get_device(regmap); + struct inv_icm45600_state *st; + struct regmap *regmap_custom; + int ret; + + regmap_custom = devm_regmap_init(dev, &inv_icm45600_regmap_bus, regmap, + &inv_icm45600_regmap_config); + if (IS_ERR(regmap_custom)) + return dev_err_probe(dev, PTR_ERR(regmap_custom), "Failed to register regmap\n"); + + st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); + if (!st) + return -ENOMEM; + + dev_set_drvdata(dev, st); + + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->map = regmap_custom; + + ret = iio_read_mount_matrix(dev, &st->orientation); + if (ret) + return dev_err_probe(dev, ret, "Failed to retrieve mounting matrix\n"); + + st->vddio_supply = devm_regulator_get(dev, "vddio"); + if (IS_ERR(st->vddio_supply)) + return PTR_ERR(st->vddio_supply); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get vdd regulator\n"); + + /* + * Supply ramp time + Start-up time. + * Datasheet: 3.3.2 A.C. Electrical Characteristics + */ + fsleep(5 * USEC_PER_MSEC); + + ret = inv_icm45600_enable_regulator_vddio(st); + if (ret) + return ret; + + ret = devm_add_action_or_reset(dev, inv_icm45600_disable_vddio_reg, st); + if (ret) + return ret; + + ret = inv_icm45600_setup(st, chip_info, reset, bus_setup); + if (ret) + return ret; + + ret = inv_icm45600_timestamp_setup(st); + if (ret) + return ret; + + ret = devm_pm_runtime_set_active_enabled(dev); + if (ret) + return ret; + + pm_runtime_get_noresume(dev); + pm_runtime_set_autosuspend_delay(dev, 2 * USEC_PER_MSEC); + pm_runtime_use_autosuspend(dev); + pm_runtime_put(dev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(inv_icm45600_core_probe, "IIO_ICM45600"); + +/* + * Suspend saves sensors state and turns everything off. + */ +static int inv_icm45600_suspend(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + + scoped_guard(mutex, &st->lock) { + /* Save sensors states */ + st->suspended.gyro = st->conf.gyro.mode; + st->suspended.accel = st->conf.accel.mode; + } + + return pm_runtime_force_suspend(dev); +} + +/* + * System resume gets the system back on and restores the sensors state. + * Manually put runtime power management in system active state. + */ +static int inv_icm45600_resume(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) { + /* Restore sensors state. */ + ret = inv_icm45600_set_pwr_mgmt0(st, st->suspended.gyro, + st->suspended.accel, NULL); + } + + return ret; +} + +/* Runtime suspend will turn off sensors that are enabled by iio devices. */ +static int inv_icm45600_runtime_suspend(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + int ret; + + guard(mutex)(&st->lock); + + /* disable all sensors */ + ret = inv_icm45600_set_pwr_mgmt0(st, INV_ICM45600_SENSOR_MODE_OFF, + INV_ICM45600_SENSOR_MODE_OFF, NULL); + if (ret) + return ret; + + regulator_disable(st->vddio_supply); + + return 0; +} + +/* Sensors are enabled by iio devices, no need to turn them back on here. */ +static int inv_icm45600_runtime_resume(struct device *dev) +{ + struct inv_icm45600_state *st = dev_get_drvdata(dev); + + guard(mutex)(&st->lock); + + return inv_icm45600_enable_regulator_vddio(st); +} + +EXPORT_NS_GPL_DEV_PM_OPS(inv_icm45600_pm_ops, IIO_ICM45600) = { + SYSTEM_SLEEP_PM_OPS(inv_icm45600_suspend, inv_icm45600_resume) + RUNTIME_PM_OPS(inv_icm45600_runtime_suspend, + inv_icm45600_runtime_resume, NULL) +}; + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-456xx device driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_INV_SENSORS_TIMESTAMP"); From 06674a72cf7afa540b4d8c5a4ee41d547988fd78 Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:04 +0000 Subject: [PATCH 064/150] 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 --- drivers/iio/imu/inv_icm45600/Makefile | 1 + drivers/iio/imu/inv_icm45600/inv_icm45600.h | 8 + .../imu/inv_icm45600/inv_icm45600_buffer.c | 483 ++++++++++++++++++ .../imu/inv_icm45600/inv_icm45600_buffer.h | 98 ++++ .../iio/imu/inv_icm45600/inv_icm45600_core.c | 158 ++++++ 5 files changed, 748 insertions(+) create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile index 4f442b61896e..72f95bc30d99 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 5f637e2f2ec8..aac8cd852c12 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 000000000000..5542ad8af2a6 --- /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 000000000000..b4c700fa4e9f --- /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 280cdd40f86b..118450b3c9f9 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; From 27e072bc34d12857cfd9f8a8424450c2aa6ecd47 Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:05 +0000 Subject: [PATCH 065/150] iio: imu: inv_icm45600: add IMU IIO gyroscope device Add IIO device for gyroscope sensor with data polling interface and FIFO parsing. Attributes: raw, scale, sampling_frequency, calibbias. Temperature is available as a processed channel. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm45600/Kconfig | 2 + drivers/iio/imu/inv_icm45600/Makefile | 1 + drivers/iio/imu/inv_icm45600/inv_icm45600.h | 38 + .../imu/inv_icm45600/inv_icm45600_buffer.c | 56 +- .../imu/inv_icm45600/inv_icm45600_buffer.h | 5 +- .../iio/imu/inv_icm45600/inv_icm45600_core.c | 179 +++- .../iio/imu/inv_icm45600/inv_icm45600_gyro.c | 791 ++++++++++++++++++ 7 files changed, 1069 insertions(+), 3 deletions(-) create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig index 8cb5543e0a58..ea0a8d20cba2 100644 --- a/drivers/iio/imu/inv_icm45600/Kconfig +++ b/drivers/iio/imu/inv_icm45600/Kconfig @@ -2,4 +2,6 @@ config INV_ICM45600 tristate + select IIO_BUFFER + select IIO_KFIFO_BUF select IIO_INV_SENSORS_TIMESTAMP diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile index 72f95bc30d99..b5954b4053c2 100644 --- a/drivers/iio/imu/inv_icm45600/Makefile +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o inv-icm45600-y += inv_icm45600_core.o inv-icm45600-y += inv_icm45600_buffer.o +inv-icm45600-y += inv_icm45600_gyro.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h index aac8cd852c12..674c2b4091ef 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600.h +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h @@ -113,8 +113,22 @@ struct inv_icm45600_chip_info { u8 whoami; const char *name; const struct inv_icm45600_conf *conf; + const int *gyro_scales; + const int gyro_scales_len; }; +extern const struct inv_icm45600_chip_info inv_icm45605_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45606_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45608_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45634_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45686_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45687_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45688p_chip_info; +extern const struct inv_icm45600_chip_info inv_icm45689_chip_info; + +extern const int inv_icm45600_gyro_scale[][2]; +extern const int inv_icm45686_gyro_scale[][2]; + /** * struct inv_icm45600_state - driver state variables * @lock: lock for serializing multiple registers access. @@ -319,6 +333,26 @@ const struct iio_mount_matrix * inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev, const struct iio_chan_spec *chan); +#define INV_ICM45600_TEMP_CHAN(_index) \ + { \ + .type = IIO_TEMP, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + } + +int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask); + u32 inv_icm45600_odr_to_period(enum inv_icm45600_odr odr); int inv_icm45600_set_accel_conf(struct inv_icm45600_state *st, @@ -336,4 +370,8 @@ int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chip_info *chip_info, bool reset, inv_icm45600_bus_setup bus_setup); +struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st); + +int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev); + #endif diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c index 5542ad8af2a6..2800995bfa32 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c @@ -389,7 +389,8 @@ const struct iio_buffer_setup_ops inv_icm45600_buffer_ops = { .postdisable = inv_icm45600_buffer_postdisable, }; -int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st) +int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st, + unsigned int max) { const ssize_t packet_size = sizeof(struct inv_icm45600_fifo_2sensors_packet); __le16 *raw_fifo_count; @@ -417,6 +418,8 @@ int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st) fifo_nb = le16_to_cpup(raw_fifo_count); if (fifo_nb == 0) return 0; + if (max > 0 && fifo_nb > max) + fifo_nb = max; /* Try to read all FIFO data in internal buffer. */ st->fifo.count = fifo_nb * packet_size; @@ -448,6 +451,57 @@ int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st) return 0; } +int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st) +{ + struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_sensors_timestamp *ts; + int ret; + + if (st->fifo.nb.total == 0) + return 0; + + /* Handle gyroscope timestamp and FIFO data parsing. */ + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_gyro, + st->timestamp.gyro); + ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + return 0; +} + +int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st, + unsigned int count) +{ + struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_sensors_timestamp *ts; + s64 gyro_ts, accel_ts; + int ret; + + gyro_ts = iio_get_time_ns(st->indio_gyro); + accel_ts = iio_get_time_ns(st->indio_accel); + + ret = inv_icm45600_buffer_fifo_read(st, count); + if (ret) + return ret; + + if (st->fifo.nb.total == 0) + return 0; + + if (st->fifo.nb.gyro > 0) { + ts = &gyro_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.gyro, gyro_ts); + ret = inv_icm45600_gyro_parse_fifo(st->indio_gyro); + if (ret) + return ret; + } + + return 0; +} + int inv_icm45600_buffer_init(struct inv_icm45600_state *st) { int ret; diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h index b4c700fa4e9f..e047871cdbe2 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h @@ -90,7 +90,10 @@ int inv_icm45600_buffer_set_fifo_en(struct inv_icm45600_state *st, 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_fifo_read(struct inv_icm45600_state *st, + unsigned int max); + +int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st); int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st, unsigned int count); diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c index 118450b3c9f9..0484e675069f 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c @@ -136,6 +136,97 @@ static const struct regmap_config inv_icm45600_regmap_config = { .val_bits = 8, }; +/* These are the chip initial default configurations (default FS value is based on icm45686) */ +static const struct inv_icm45600_conf inv_icm45600_default_conf = { + .gyro = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_GYRO_FS_2000DPS, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X, + }, +}; + +static const struct inv_icm45600_conf inv_icm45686_default_conf = { + .gyro = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_GYRO_FS_4000DPS, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X, + }, +}; + +const struct inv_icm45600_chip_info inv_icm45605_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45605, + .name = "icm45605", + .conf = &inv_icm45600_default_conf, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45605_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45606_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45606, + .name = "icm45606", + .conf = &inv_icm45600_default_conf, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45606_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45608_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45608, + .name = "icm45608", + .conf = &inv_icm45600_default_conf, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45608_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45634_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45634, + .name = "icm45634", + .conf = &inv_icm45600_default_conf, + .gyro_scales = (const int *)inv_icm45600_gyro_scale, + .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45634_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45686_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45686, + .name = "icm45686", + .conf = &inv_icm45686_default_conf, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45686_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45687_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45687, + .name = "icm45687", + .conf = &inv_icm45686_default_conf, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45687_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45688p_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45688P, + .name = "icm45688p", + .conf = &inv_icm45686_default_conf, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45688p_chip_info, "IIO_ICM45600"); + +const struct inv_icm45600_chip_info inv_icm45689_chip_info = { + .whoami = INV_ICM45600_WHOAMI_ICM45689, + .name = "icm45689", + .conf = &inv_icm45686_default_conf, + .gyro_scales = (const int *)inv_icm45686_gyro_scale, + .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, +}; +EXPORT_SYMBOL_NS_GPL(inv_icm45689_chip_info, "IIO_ICM45600"); + const struct iio_mount_matrix * inv_icm45600_get_mount_matrix(const struct iio_dev *indio_dev, const struct iio_chan_spec *chan) @@ -465,11 +556,14 @@ static irqreturn_t inv_icm45600_irq_handler(int irq, void *_data) /* 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); + ret = inv_icm45600_buffer_fifo_read(st, 0); if (ret) { dev_err(dev, "FIFO read error %d\n", ret); return IRQ_HANDLED; } + ret = inv_icm45600_buffer_fifo_parse(st); + if (ret) + dev_err(dev, "FIFO parsing error %d\n", ret); } /* FIFO full warning. */ @@ -642,6 +736,10 @@ int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chi if (ret) return ret; + st->indio_gyro = inv_icm45600_gyro_init(st); + if (IS_ERR(st->indio_gyro)) + return PTR_ERR(st->indio_gyro); + ret = inv_icm45600_irq_init(st, irq, irq_type, open_drain); if (ret) return ret; @@ -767,6 +865,85 @@ static int inv_icm45600_runtime_resume(struct device *dev) return inv_icm45600_enable_regulator_vddio(st); } +static int _inv_icm45600_temp_read(struct inv_icm45600_state *st, s16 *temp) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + /* Make sure a sensor is on. */ + if (st->conf.gyro.mode == INV_ICM45600_SENSOR_MODE_OFF && + st->conf.accel.mode == INV_ICM45600_SENSOR_MODE_OFF) { + conf.mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + } + + ret = regmap_bulk_read(st->map, INV_ICM45600_REG_TEMP_DATA, + &st->buffer.u16, sizeof(st->buffer.u16)); + if (ret) + return ret; + + *temp = (s16)le16_to_cpup(&st->buffer.u16); + if (*temp == INV_ICM45600_DATA_INVALID) + return -EINVAL; + + return 0; +} + +static int inv_icm45600_temp_read(struct inv_icm45600_state *st, s16 *temp) +{ + struct device *dev = regmap_get_device(st->map); + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_temp_read(st, temp); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +int inv_icm45600_temp_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + s16 temp; + int ret; + + if (chan->type != IIO_TEMP) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = inv_icm45600_temp_read(st, &temp); + if (ret) + return ret; + *val = temp; + return IIO_VAL_INT; + /* + * T°C = (temp / 128) + 25 + * Tm°C = 1000 * ((temp * 100 / 12800) + 25) + * scale: 100000 / 13248 = 7.8125 + * offset: 25000 + */ + case IIO_CHAN_INFO_SCALE: + *val = 7; + *val2 = 812500; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_OFFSET: + *val = 25000; + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + EXPORT_NS_GPL_DEV_PM_OPS(inv_icm45600_pm_ops, IIO_ICM45600) = { SYSTEM_SLEEP_PM_OPS(inv_icm45600_suspend, inv_icm45600_resume) RUNTIME_PM_OPS(inv_icm45600_runtime_suspend, diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c new file mode 100644 index 000000000000..1e85fd0e4ea9 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c @@ -0,0 +1,791 @@ +// 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 "inv_icm45600_buffer.h" +#include "inv_icm45600.h" + +enum inv_icm45600_gyro_scan { + INV_ICM45600_GYRO_SCAN_X, + INV_ICM45600_GYRO_SCAN_Y, + INV_ICM45600_GYRO_SCAN_Z, + INV_ICM45600_GYRO_SCAN_TEMP, + INV_ICM45600_GYRO_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm45600_gyro_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix), + { } +}; + +#define INV_ICM45600_GYRO_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = _ext_info, \ + } + +static const struct iio_chan_spec inv_icm45600_gyro_channels[] = { + INV_ICM45600_GYRO_CHAN(IIO_MOD_X, INV_ICM45600_GYRO_SCAN_X, + inv_icm45600_gyro_ext_infos), + INV_ICM45600_GYRO_CHAN(IIO_MOD_Y, INV_ICM45600_GYRO_SCAN_Y, + inv_icm45600_gyro_ext_infos), + INV_ICM45600_GYRO_CHAN(IIO_MOD_Z, INV_ICM45600_GYRO_SCAN_Z, + inv_icm45600_gyro_ext_infos), + INV_ICM45600_TEMP_CHAN(INV_ICM45600_GYRO_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_GYRO_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes angular velocity, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm45600_gyro_buffer { + struct inv_icm45600_fifo_sensor_data gyro; + s16 temp; + aligned_s64 timestamp; +}; + +static const unsigned long inv_icm45600_gyro_scan_masks[] = { + /* 3-axis gyro + temperature */ + BIT(INV_ICM45600_GYRO_SCAN_X) | + BIT(INV_ICM45600_GYRO_SCAN_Y) | + BIT(INV_ICM45600_GYRO_SCAN_Z) | + BIT(INV_ICM45600_GYRO_SCAN_TEMP), + 0 +}; + +/* enable gyroscope sensor and FIFO write */ +static int inv_icm45600_gyro_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + unsigned int fifo_en = 0; + unsigned int sleep = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + if (*scan_mask & BIT(INV_ICM45600_GYRO_SCAN_TEMP)) + fifo_en |= INV_ICM45600_SENSOR_TEMP; + + if (*scan_mask & (BIT(INV_ICM45600_GYRO_SCAN_X) | + BIT(INV_ICM45600_GYRO_SCAN_Y) | + BIT(INV_ICM45600_GYRO_SCAN_Z))) { + /* enable gyro sensor */ + conf.mode = gyro_st->power_mode; + ret = inv_icm45600_set_gyro_conf(st, &conf, &sleep); + if (ret) + return ret; + fifo_en |= INV_ICM45600_SENSOR_GYRO; + } + ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + } + if (sleep) + msleep(sleep); + + return ret; +} + +static int _inv_icm45600_gyro_read_sensor(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_state *gyro_st, + unsigned int reg, int *val) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + /* enable gyro sensor */ + conf.mode = gyro_st->power_mode; + ret = inv_icm45600_set_gyro_conf(st, &conf, NULL); + if (ret) + return ret; + + /* read gyro register data */ + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpup(&st->buffer.u16), 15); + if (*val == INV_ICM45600_DATA_INVALID) + return -ENODATA; + + return 0; +} + +static int inv_icm45600_gyro_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int reg; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_REG_GYRO_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_REG_GYRO_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_REG_GYRO_DATA_Z; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_gyro_read_sensor(st, gyro_st, reg, val); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + nano */ +const int inv_icm45600_gyro_scale[][2] = { + /* +/- 2000dps => 0.001065264 rad/s */ + [INV_ICM45600_GYRO_FS_2000DPS] = { 0, 1065264 }, + /* +/- 1000dps => 0.000532632 rad/s */ + [INV_ICM45600_GYRO_FS_1000DPS] = { 0, 532632 }, + /* +/- 500dps => 0.000266316 rad/s */ + [INV_ICM45600_GYRO_FS_500DPS] = { 0, 266316 }, + /* +/- 250dps => 0.000133158 rad/s */ + [INV_ICM45600_GYRO_FS_250DPS] = { 0, 133158 }, + /* +/- 125dps => 0.000066579 rad/s */ + [INV_ICM45600_GYRO_FS_125DPS] = { 0, 66579 }, + /* +/- 62.5dps => 0.000033290 rad/s */ + [INV_ICM45600_GYRO_FS_62_5DPS] = { 0, 33290 }, + /* +/- 31.25dps => 0.000016645 rad/s */ + [INV_ICM45600_GYRO_FS_31_25DPS] = { 0, 16645 }, + /* +/- 15.625dps => 0.000008322 rad/s */ + [INV_ICM45600_GYRO_FS_15_625DPS] = { 0, 8322 }, +}; + +/* IIO format int + nano */ +const int inv_icm45686_gyro_scale[][2] = { + /* +/- 4000dps => 0.002130529 rad/s */ + [INV_ICM45686_GYRO_FS_4000DPS] = { 0, 2130529 }, + /* +/- 2000dps => 0.001065264 rad/s */ + [INV_ICM45686_GYRO_FS_2000DPS] = { 0, 1065264 }, + /* +/- 1000dps => 0.000532632 rad/s */ + [INV_ICM45686_GYRO_FS_1000DPS] = { 0, 532632 }, + /* +/- 500dps => 0.000266316 rad/s */ + [INV_ICM45686_GYRO_FS_500DPS] = { 0, 266316 }, + /* +/- 250dps => 0.000133158 rad/s */ + [INV_ICM45686_GYRO_FS_250DPS] = { 0, 133158 }, + /* +/- 125dps => 0.000066579 rad/s */ + [INV_ICM45686_GYRO_FS_125DPS] = { 0, 66579 }, + /* +/- 62.5dps => 0.000033290 rad/s */ + [INV_ICM45686_GYRO_FS_62_5DPS] = { 0, 33290 }, + /* +/- 31.25dps => 0.000016645 rad/s */ + [INV_ICM45686_GYRO_FS_31_25DPS] = { 0, 16645 }, + /* +/- 15.625dps => 0.000008322 rad/s */ + [INV_ICM45686_GYRO_FS_15_625DPS] = { 0, 8322 }, +}; + +static int inv_icm45600_gyro_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + unsigned int idx; + + idx = st->conf.gyro.fs; + + /* Full scale register starts at 1 for not High FSR parts */ + if (gyro_st->scales == (const int *)&inv_icm45600_gyro_scale) + idx--; + + *val = gyro_st->scales[2 * idx]; + *val2 = gyro_st->scales[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm45600_gyro_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + for (idx = 0; idx < gyro_st->scales_len; idx += 2) { + if (val == gyro_st->scales[idx] && + val2 == gyro_st->scales[idx + 1]) + break; + } + if (idx == gyro_st->scales_len) + return -EINVAL; + + conf.fs = idx / 2; + + /* Full scale register starts at 1 for not High FSR parts */ + if (gyro_st->scales == (const int *)&inv_icm45600_gyro_scale) + conf.fs++; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = inv_icm45600_set_gyro_conf(st, &conf, NULL); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm45600_gyro_odr[] = { + 1, 562500, /* 1.5625Hz */ + 3, 125000, /* 3.125Hz */ + 6, 250000, /* 6.25Hz */ + 12, 500000, /* 12.5Hz */ + 25, 0, /* 25Hz */ + 50, 0, /* 50Hz */ + 100, 0, /* 100Hz */ + 200, 0, /* 200Hz */ + 400, 0, /* 400Hz */ + 800, 0, /* 800Hz */ + 1600, 0, /* 1.6kHz */ + 3200, 0, /* 3.2kHz */ + 6400, 0, /* 6.4kHz */ +}; + +static const int inv_icm45600_gyro_odr_conv[] = { + INV_ICM45600_ODR_1_5625HZ_LP, + INV_ICM45600_ODR_3_125HZ_LP, + INV_ICM45600_ODR_6_25HZ_LP, + INV_ICM45600_ODR_12_5HZ, + INV_ICM45600_ODR_25HZ, + INV_ICM45600_ODR_50HZ, + INV_ICM45600_ODR_100HZ, + INV_ICM45600_ODR_200HZ, + INV_ICM45600_ODR_400HZ, + INV_ICM45600_ODR_800HZ_LN, + INV_ICM45600_ODR_1600HZ_LN, + INV_ICM45600_ODR_3200HZ_LN, + INV_ICM45600_ODR_6400HZ_LN, +}; + +static int inv_icm45600_gyro_read_odr(struct inv_icm45600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.gyro.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm45600_gyro_odr_conv); ++i) { + if (inv_icm45600_gyro_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm45600_gyro_odr_conv)) + return -EINVAL; + + *val = inv_icm45600_gyro_odr[2 * i]; + *val2 = inv_icm45600_gyro_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int _inv_icm45600_gyro_write_odr(struct iio_dev *indio_dev, int odr) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + conf.odr = odr; + ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + return ret; + + if (st->conf.gyro.mode != INV_ICM45600_SENSOR_MODE_OFF) + conf.mode = gyro_st->power_mode; + + ret = inv_icm45600_set_gyro_conf(st, &conf, NULL); + if (ret) + return ret; + + inv_icm45600_buffer_update_fifo_period(st); + inv_icm45600_buffer_update_watermark(st); + + return 0; +} + +static int inv_icm45600_gyro_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + int odr; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_gyro_odr); idx += 2) { + if (val == inv_icm45600_gyro_odr[idx] && + val2 == inv_icm45600_gyro_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm45600_gyro_odr)) + return -EINVAL; + + odr = inv_icm45600_gyro_odr_conv[idx / 2]; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_gyro_write_odr(indio_dev, odr); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + nano. + * Value is limited to +/-62.5dps coded on 14 bits signed. Step is 7.5mdps. + */ +static int inv_icm45600_gyro_calibbias[] = { + -1, 90830336, /* min: -1.090830336 rad/s */ + 0, 133158, /* step: 0.000133158 rad/s */ + 1, 90697178, /* max: 1.090697178 rad/s */ +}; + +static int inv_icm45600_gyro_read_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 bias; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS1_REG_42; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS1_REG_56; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS1_REG_70; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + offset = le16_to_cpup(&st->buffer.u16) & INV_ICM45600_GYRO_OFFUSER_MASK; + /* 14 bits signed value */ + offset = sign_extend32(offset, 13); + + /* + * convert raw offset to dps then to rad/s + * 14 bits signed raw max 62.5 to dps: 625 / 81920 + * dps to rad: Pi / 180 + * result in nano (1000000000) + * (offset * 625 * Pi * 1000000000) / (81920 * 180) + */ + val64 = (s64)offset * 625LL * 3141592653LL; + /* for rounding, add + or - divisor (81920 * 180) divided by 2 */ + if (val64 >= 0) + val64 += 81920 * 180 / 2; + else + val64 -= 81920 * 180 / 2; + bias = div_s64(val64, 81920 * 180); + *val = bias / 1000000000L; + *val2 = bias % 1000000000L; + + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm45600_gyro_write_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64, min, max; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS1_REG_42; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS1_REG_56; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS1_REG_70; + break; + default: + return -EINVAL; + } + + /* inv_icm45600_gyro_calibbias: min - step - max in nano */ + min = (s64)inv_icm45600_gyro_calibbias[0] * 1000000000LL - + (s64)inv_icm45600_gyro_calibbias[1]; + max = (s64)inv_icm45600_gyro_calibbias[4] * 1000000000LL + + (s64)inv_icm45600_gyro_calibbias[5]; + val64 = (s64)val * 1000000000LL; + if (val >= 0) + val64 += (s64)val2; + else + val64 -= (s64)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert rad/s to dps then to raw value + * rad to dps: 180 / Pi + * dps to raw 14 bits signed, max 62.5: 8192 / 62.5 + * val in nano (1000000000) + * val * 180 * 8192 / (Pi * 1000000000 * 62.5) + */ + val64 = val64 * 180LL * 8192; + /* for rounding, add + or - divisor (314159265 * 625) divided by 2 */ + if (val64 >= 0) + val64 += 314159265LL * 625LL / 2LL; + else + val64 -= 314159265LL * 625LL / 2LL; + offset = div64_s64(val64, 314159265LL * 625LL); + + /* clamp value limited to 14 bits signed */ + offset = clamp(offset, -8192, 8191); + + st->buffer.u16 = cpu_to_le16(offset & INV_ICM45600_GYRO_OFFUSER_MASK); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_write(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm45600_gyro_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + switch (chan->type) { + case IIO_ANGL_VEL: + break; + case IIO_TEMP: + return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_gyro_read_sensor(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm45600_gyro_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_gyro_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm45600_gyro_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = gyro_st->scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = gyro_st->scales_len; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm45600_gyro_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm45600_gyro_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm45600_gyro_calibbias; + *type = IIO_VAL_INT_PLUS_NANO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_gyro_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_gyro_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_gyro_write_offset(st, chan, val, val2); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ANGL_VEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_NANO; + default: + return -EINVAL; + } +} + +static int inv_icm45600_gyro_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.gyro = val; + return inv_icm45600_buffer_update_watermark(st); +} + +static int inv_icm45600_gyro_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm45600_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.gyro; +} + +static const struct iio_info inv_icm45600_gyro_info = { + .read_raw = inv_icm45600_gyro_read_raw, + .read_avail = inv_icm45600_gyro_read_avail, + .write_raw = inv_icm45600_gyro_write_raw, + .write_raw_get_fmt = inv_icm45600_gyro_write_raw_get_fmt, + .debugfs_reg_access = inv_icm45600_debugfs_reg, + .update_scan_mode = inv_icm45600_gyro_update_scan_mode, + .hwfifo_set_watermark = inv_icm45600_gyro_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm45600_gyro_hwfifo_flush, +}; + +struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm45600_sensor_state *gyro_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + const char *name; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-gyro", st->chip_info->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*gyro_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + gyro_st = iio_priv(indio_dev); + + gyro_st->scales = st->chip_info->gyro_scales; + gyro_st->scales_len = st->chip_info->gyro_scales_len * 2; + + /* low-noise by default at init */ + gyro_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_NOISE; + + /* + * clock period is 32kHz (31250ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 31250; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.gyro.odr); + inv_sensors_timestamp_init(&gyro_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm45600_gyro_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm45600_gyro_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_gyro_channels); + indio_dev->available_scan_masks = inv_icm45600_gyro_scan_masks; + indio_dev->setup_ops = &inv_icm45600_buffer_ops; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &inv_icm45600_buffer_ops); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *gyro_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &gyro_st->ts; + ssize_t i, size; + unsigned int no; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + struct inv_icm45600_gyro_buffer buffer = { }; + const struct inv_icm45600_fifo_sensor_data *accel, *gyro; + const __le16 *timestamp; + const s8 *temp; + unsigned int odr; + s64 ts_val; + + size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no gyro data or data is invalid */ + if (gyro == NULL || !inv_icm45600_fifo_is_data_valid(gyro)) + continue; + + /* update odr */ + if (odr & INV_ICM45600_SENSOR_GYRO) + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + memcpy(&buffer.gyro, gyro, sizeof(buffer.gyro)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), ts_val); + } + + return 0; +} From 1fad7b491bb1eb77aad46b873f9d433cfb26b446 Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:06 +0000 Subject: [PATCH 066/150] iio: imu: inv_icm45600: add IMU IIO accelerometer device Add IIO device for accelerometer sensor with data polling interface and FIFO parsing. Attributes: raw, scale, sampling_frequency, calibbias. Temperature is available as a processed channel. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm45600/Makefile | 1 + drivers/iio/imu/inv_icm45600/inv_icm45600.h | 8 + .../iio/imu/inv_icm45600/inv_icm45600_accel.c | 782 ++++++++++++++++++ .../imu/inv_icm45600/inv_icm45600_buffer.c | 20 + .../iio/imu/inv_icm45600/inv_icm45600_core.c | 32 + 5 files changed, 843 insertions(+) create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile index b5954b4053c2..e34553d2b74d 100644 --- a/drivers/iio/imu/inv_icm45600/Makefile +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_INV_ICM45600) += inv-icm45600.o inv-icm45600-y += inv_icm45600_core.o inv-icm45600-y += inv_icm45600_buffer.o inv-icm45600-y += inv_icm45600_gyro.o +inv-icm45600-y += inv_icm45600_accel.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h index 674c2b4091ef..c5b5446f6c3b 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600.h +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h @@ -113,6 +113,8 @@ struct inv_icm45600_chip_info { u8 whoami; const char *name; const struct inv_icm45600_conf *conf; + const int *accel_scales; + const int accel_scales_len; const int *gyro_scales; const int gyro_scales_len; }; @@ -126,6 +128,8 @@ extern const struct inv_icm45600_chip_info inv_icm45687_chip_info; extern const struct inv_icm45600_chip_info inv_icm45688p_chip_info; extern const struct inv_icm45600_chip_info inv_icm45689_chip_info; +extern const int inv_icm45600_accel_scale[][2]; +extern const int inv_icm45686_accel_scale[][2]; extern const int inv_icm45600_gyro_scale[][2]; extern const int inv_icm45686_gyro_scale[][2]; @@ -374,4 +378,8 @@ struct iio_dev *inv_icm45600_gyro_init(struct inv_icm45600_state *st); int inv_icm45600_gyro_parse_fifo(struct iio_dev *indio_dev); +struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st); + +int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev); + #endif diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c new file mode 100644 index 000000000000..efa22e02657f --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c @@ -0,0 +1,782 @@ +// 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 "inv_icm45600_buffer.h" +#include "inv_icm45600.h" + +enum inv_icm45600_accel_scan { + INV_ICM45600_ACCEL_SCAN_X, + INV_ICM45600_ACCEL_SCAN_Y, + INV_ICM45600_ACCEL_SCAN_Z, + INV_ICM45600_ACCEL_SCAN_TEMP, + INV_ICM45600_ACCEL_SCAN_TIMESTAMP, +}; + +static const struct iio_chan_spec_ext_info inv_icm45600_accel_ext_infos[] = { + IIO_MOUNT_MATRIX(IIO_SHARED_BY_ALL, inv_icm45600_get_mount_matrix), + { } +}; + +#define INV_ICM45600_ACCEL_CHAN(_modifier, _index, _ext_info) \ + { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = _modifier, \ + .info_mask_separate = \ + BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_CALIBBIAS), \ + .info_mask_shared_by_all = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ + .ext_info = _ext_info, \ + } + +static const struct iio_chan_spec inv_icm45600_accel_channels[] = { + INV_ICM45600_ACCEL_CHAN(IIO_MOD_X, INV_ICM45600_ACCEL_SCAN_X, + inv_icm45600_accel_ext_infos), + INV_ICM45600_ACCEL_CHAN(IIO_MOD_Y, INV_ICM45600_ACCEL_SCAN_Y, + inv_icm45600_accel_ext_infos), + INV_ICM45600_ACCEL_CHAN(IIO_MOD_Z, INV_ICM45600_ACCEL_SCAN_Z, + inv_icm45600_accel_ext_infos), + INV_ICM45600_TEMP_CHAN(INV_ICM45600_ACCEL_SCAN_TEMP), + IIO_CHAN_SOFT_TIMESTAMP(INV_ICM45600_ACCEL_SCAN_TIMESTAMP), +}; + +/* + * IIO buffer data: size must be a power of 2 and timestamp aligned + * 16 bytes: 6 bytes acceleration, 2 bytes temperature, 8 bytes timestamp + */ +struct inv_icm45600_accel_buffer { + struct inv_icm45600_fifo_sensor_data accel; + s16 temp; + aligned_s64 timestamp; +}; + +static const unsigned long inv_icm45600_accel_scan_masks[] = { + /* 3-axis accel + temperature */ + BIT(INV_ICM45600_ACCEL_SCAN_X) | + BIT(INV_ICM45600_ACCEL_SCAN_Y) | + BIT(INV_ICM45600_ACCEL_SCAN_Z) | + BIT(INV_ICM45600_ACCEL_SCAN_TEMP), + 0 +}; + +/* enable accelerometer sensor and FIFO write */ +static int inv_icm45600_accel_update_scan_mode(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + unsigned int fifo_en = 0; + unsigned int sleep = 0; + int ret; + + scoped_guard(mutex, &st->lock) { + if (*scan_mask & BIT(INV_ICM45600_ACCEL_SCAN_TEMP)) + fifo_en |= INV_ICM45600_SENSOR_TEMP; + + if (*scan_mask & (BIT(INV_ICM45600_ACCEL_SCAN_X) | + BIT(INV_ICM45600_ACCEL_SCAN_Y) | + BIT(INV_ICM45600_ACCEL_SCAN_Z))) { + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + ret = inv_icm45600_set_accel_conf(st, &conf, &sleep); + if (ret) + return ret; + fifo_en |= INV_ICM45600_SENSOR_ACCEL; + } + + /* Update data FIFO write. */ + ret = inv_icm45600_buffer_set_fifo_en(st, fifo_en | st->fifo.en); + } + + /* Sleep required time. */ + if (sleep) + msleep(sleep); + + return ret; +} + +static int _inv_icm45600_accel_read_sensor(struct inv_icm45600_state *st, + struct inv_icm45600_sensor_state *accel_st, + unsigned int reg, int *val) +{ + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + /* enable accel sensor */ + conf.mode = accel_st->power_mode; + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + + /* read accel register data */ + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + if (ret) + return ret; + + *val = sign_extend32(le16_to_cpup(&st->buffer.u16), 15); + if (*val == INV_ICM45600_DATA_INVALID) + return -ENODATA; + + return 0; +} + +static int inv_icm45600_accel_read_sensor(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int reg; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_REG_ACCEL_DATA_X; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_REG_ACCEL_DATA_Y; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_REG_ACCEL_DATA_Z; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_accel_read_sensor(st, accel_st, reg, val); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + nano */ +const int inv_icm45600_accel_scale[][2] = { + /* +/- 16G => 0.004788403 m/s-2 */ + [INV_ICM45600_ACCEL_FS_16G] = { 0, 4788403 }, + /* +/- 8G => 0.002394202 m/s-2 */ + [INV_ICM45600_ACCEL_FS_8G] = { 0, 2394202 }, + /* +/- 4G => 0.001197101 m/s-2 */ + [INV_ICM45600_ACCEL_FS_4G] = { 0, 1197101 }, + /* +/- 2G => 0.000598550 m/s-2 */ + [INV_ICM45600_ACCEL_FS_2G] = { 0, 598550 }, +}; + +const int inv_icm45686_accel_scale[][2] = { + /* +/- 32G => 0.009576806 m/s-2 */ + [INV_ICM45686_ACCEL_FS_32G] = { 0, 9576806 }, + /* +/- 16G => 0.004788403 m/s-2 */ + [INV_ICM45686_ACCEL_FS_16G] = { 0, 4788403 }, + /* +/- 8G => 0.002394202 m/s-2 */ + [INV_ICM45686_ACCEL_FS_8G] = { 0, 2394202 }, + /* +/- 4G => 0.001197101 m/s-2 */ + [INV_ICM45686_ACCEL_FS_4G] = { 0, 1197101 }, + /* +/- 2G => 0.000598550 m/s-2 */ + [INV_ICM45686_ACCEL_FS_2G] = { 0, 598550 }, +}; + +static int inv_icm45600_accel_read_scale(struct iio_dev *indio_dev, + int *val, int *val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + unsigned int idx; + + idx = st->conf.accel.fs; + + /* Full scale register starts at 1 for not High FSR parts */ + if (accel_st->scales == (const int *)&inv_icm45600_accel_scale) + idx--; + + *val = accel_st->scales[2 * idx]; + *val2 = accel_st->scales[2 * idx + 1]; + return IIO_VAL_INT_PLUS_NANO; +} + +static int inv_icm45600_accel_write_scale(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + for (idx = 0; idx < accel_st->scales_len; idx += 2) { + if (val == accel_st->scales[idx] && + val2 == accel_st->scales[idx + 1]) + break; + } + if (idx == accel_st->scales_len) + return -EINVAL; + + conf.fs = idx / 2; + + /* Full scale register starts at 1 for not High FSR parts */ + if (accel_st->scales == (const int *)&inv_icm45600_accel_scale) + conf.fs++; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* IIO format int + micro */ +static const int inv_icm45600_accel_odr[] = { + 1, 562500, /* 1.5625Hz */ + 3, 125000, /* 3.125Hz */ + 6, 250000, /* 6.25Hz */ + 12, 500000, /* 12.5Hz */ + 25, 0, /* 25Hz */ + 50, 0, /* 50Hz */ + 100, 0, /* 100Hz */ + 200, 0, /* 200Hz */ + 400, 0, /* 400Hz */ + 800, 0, /* 800Hz */ + 1600, 0, /* 1.6kHz */ + 3200, 0, /* 3.2kHz */ + 6400, 0, /* 6.4kHz */ +}; + +static const int inv_icm45600_accel_odr_conv[] = { + INV_ICM45600_ODR_1_5625HZ_LP, + INV_ICM45600_ODR_3_125HZ_LP, + INV_ICM45600_ODR_6_25HZ_LP, + INV_ICM45600_ODR_12_5HZ, + INV_ICM45600_ODR_25HZ, + INV_ICM45600_ODR_50HZ, + INV_ICM45600_ODR_100HZ, + INV_ICM45600_ODR_200HZ, + INV_ICM45600_ODR_400HZ, + INV_ICM45600_ODR_800HZ_LN, + INV_ICM45600_ODR_1600HZ_LN, + INV_ICM45600_ODR_3200HZ_LN, + INV_ICM45600_ODR_6400HZ_LN, +}; + +static int inv_icm45600_accel_read_odr(struct inv_icm45600_state *st, + int *val, int *val2) +{ + unsigned int odr; + unsigned int i; + + odr = st->conf.accel.odr; + + for (i = 0; i < ARRAY_SIZE(inv_icm45600_accel_odr_conv); ++i) { + if (inv_icm45600_accel_odr_conv[i] == odr) + break; + } + if (i >= ARRAY_SIZE(inv_icm45600_accel_odr_conv)) + return -EINVAL; + + *val = inv_icm45600_accel_odr[2 * i]; + *val2 = inv_icm45600_accel_odr[2 * i + 1]; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int _inv_icm45600_accel_write_odr(struct iio_dev *indio_dev, int odr) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + struct inv_icm45600_sensor_conf conf = INV_ICM45600_SENSOR_CONF_KEEP_VALUES; + int ret; + + conf.odr = odr; + ret = inv_sensors_timestamp_update_odr(ts, inv_icm45600_odr_to_period(conf.odr), + iio_buffer_enabled(indio_dev)); + if (ret) + return ret; + + if (st->conf.accel.mode != INV_ICM45600_SENSOR_MODE_OFF) + conf.mode = accel_st->power_mode; + + ret = inv_icm45600_set_accel_conf(st, &conf, NULL); + if (ret) + return ret; + + inv_icm45600_buffer_update_fifo_period(st); + inv_icm45600_buffer_update_watermark(st); + + return 0; +} + +static int inv_icm45600_accel_write_odr(struct iio_dev *indio_dev, + int val, int val2) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct device *dev = regmap_get_device(st->map); + unsigned int idx; + int odr; + int ret; + + for (idx = 0; idx < ARRAY_SIZE(inv_icm45600_accel_odr); idx += 2) { + if (val == inv_icm45600_accel_odr[idx] && + val2 == inv_icm45600_accel_odr[idx + 1]) + break; + } + if (idx >= ARRAY_SIZE(inv_icm45600_accel_odr)) + return -EINVAL; + + odr = inv_icm45600_accel_odr_conv[idx / 2]; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = _inv_icm45600_accel_write_odr(indio_dev, odr); + + pm_runtime_put_autosuspend(dev); + + return ret; +} + +/* + * Calibration bias values, IIO range format int + micro. + * Value is limited to +/-1g coded on 14 bits signed. Step is 0.125mg. + */ +static int inv_icm45600_accel_calibbias[] = { + -9, 806650, /* min: -9.806650 m/s² */ + 0, 1197, /* step: 0.001197 m/s² */ + 9, 805453, /* max: 9.805453 m/s² */ +}; + +static int inv_icm45600_accel_read_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int *val, int *val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 bias; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS2_REG_24; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS2_REG_32; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS2_REG_40; + break; + default: + return -EINVAL; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_read(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + if (ret) + return ret; + + offset = le16_to_cpup(&st->buffer.u16) & INV_ICM45600_ACCEL_OFFUSER_MASK; + /* 14 bits signed value */ + offset = sign_extend32(offset, 13); + + /* + * convert raw offset to g then to m/s² + * 14 bits signed raw step 1/8192g + * g to m/s²: 9.806650 + * result in micro (* 1000000) + * (offset * 9806650) / 8192 + */ + val64 = (s64)offset * 9806650LL; + /* for rounding, add + or - divisor (8192) divided by 2 */ + if (val64 >= 0) + val64 += 8192LL / 2LL; + else + val64 -= 8192LL / 2LL; + bias = div_s64(val64, 8192L); + *val = bias / 1000000L; + *val2 = bias % 1000000L; + + return IIO_VAL_INT_PLUS_MICRO; +} + +static int inv_icm45600_accel_write_offset(struct inv_icm45600_state *st, + struct iio_chan_spec const *chan, + int val, int val2) +{ + struct device *dev = regmap_get_device(st->map); + s64 val64; + s32 min, max; + unsigned int reg; + s16 offset; + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (chan->channel2) { + case IIO_MOD_X: + reg = INV_ICM45600_IPREG_SYS2_REG_24; + break; + case IIO_MOD_Y: + reg = INV_ICM45600_IPREG_SYS2_REG_32; + break; + case IIO_MOD_Z: + reg = INV_ICM45600_IPREG_SYS2_REG_40; + break; + default: + return -EINVAL; + } + + /* inv_icm45600_accel_calibbias: min - step - max in micro */ + min = inv_icm45600_accel_calibbias[0] * 1000000L - + inv_icm45600_accel_calibbias[1]; + max = inv_icm45600_accel_calibbias[4] * 1000000L + + inv_icm45600_accel_calibbias[5]; + val64 = (s64)val * 1000000LL; + if (val >= 0) + val64 += (s64)val2; + else + val64 -= (s64)val2; + if (val64 < min || val64 > max) + return -EINVAL; + + /* + * convert m/s² to g then to raw value + * m/s² to g: 1 / 9.806650 + * g to raw 14 bits signed, step 1/8192g: * 8192 + * val in micro (1000000) + * val * 8192 / (9.806650 * 1000000) + */ + val64 = val64 * 8192LL; + /* for rounding, add + or - divisor (9806650) divided by 2 */ + if (val64 >= 0) + val64 += 9806650 / 2; + else + val64 -= 9806650 / 2; + offset = div_s64(val64, 9806650); + + /* clamp value limited to 14 bits signed */ + offset = clamp(offset, -8192, 8191); + + st->buffer.u16 = cpu_to_le16(offset & INV_ICM45600_ACCEL_OFFUSER_MASK); + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + scoped_guard(mutex, &st->lock) + ret = regmap_bulk_write(st->map, reg, &st->buffer.u16, sizeof(st->buffer.u16)); + + pm_runtime_put_autosuspend(dev); + return ret; +} + +static int inv_icm45600_accel_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + switch (chan->type) { + case IIO_ACCEL: + break; + case IIO_TEMP: + return inv_icm45600_temp_read_raw(indio_dev, chan, val, val2, mask); + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_accel_read_sensor(indio_dev, chan, val); + iio_device_release_direct(indio_dev); + if (ret) + return ret; + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + return inv_icm45600_accel_read_scale(indio_dev, val, val2); + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_accel_read_odr(st, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + return inv_icm45600_accel_read_offset(st, chan, val, val2); + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, + int *type, int *length, long mask) +{ + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *vals = accel_st->scales; + *type = IIO_VAL_INT_PLUS_NANO; + *length = accel_st->scales_len; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = inv_icm45600_accel_odr; + *type = IIO_VAL_INT_PLUS_MICRO; + *length = ARRAY_SIZE(inv_icm45600_accel_odr); + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBBIAS: + *vals = inv_icm45600_accel_calibbias; + *type = IIO_VAL_INT_PLUS_MICRO; + return IIO_AVAIL_RANGE; + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_accel_write_scale(indio_dev, val, val2); + iio_device_release_direct(indio_dev); + return ret; + case IIO_CHAN_INFO_SAMP_FREQ: + return inv_icm45600_accel_write_odr(indio_dev, val, val2); + case IIO_CHAN_INFO_CALIBBIAS: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = inv_icm45600_accel_write_offset(st, chan, val, val2); + iio_device_release_direct(indio_dev); + return ret; + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + if (chan->type != IIO_ACCEL) + return -EINVAL; + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_CALIBBIAS: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } +} + +static int inv_icm45600_accel_hwfifo_set_watermark(struct iio_dev *indio_dev, + unsigned int val) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + + guard(mutex)(&st->lock); + + st->fifo.watermark.accel = val; + return inv_icm45600_buffer_update_watermark(st); +} + +static int inv_icm45600_accel_hwfifo_flush(struct iio_dev *indio_dev, + unsigned int count) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + int ret; + + if (count == 0) + return 0; + + guard(mutex)(&st->lock); + + ret = inv_icm45600_buffer_hwfifo_flush(st, count); + if (ret) + return ret; + + return st->fifo.nb.accel; +} + +static const struct iio_info inv_icm45600_accel_info = { + .read_raw = inv_icm45600_accel_read_raw, + .read_avail = inv_icm45600_accel_read_avail, + .write_raw = inv_icm45600_accel_write_raw, + .write_raw_get_fmt = inv_icm45600_accel_write_raw_get_fmt, + .debugfs_reg_access = inv_icm45600_debugfs_reg, + .update_scan_mode = inv_icm45600_accel_update_scan_mode, + .hwfifo_set_watermark = inv_icm45600_accel_hwfifo_set_watermark, + .hwfifo_flush_to_buffer = inv_icm45600_accel_hwfifo_flush, +}; + +struct iio_dev *inv_icm45600_accel_init(struct inv_icm45600_state *st) +{ + struct device *dev = regmap_get_device(st->map); + struct inv_icm45600_sensor_state *accel_st; + struct inv_sensors_timestamp_chip ts_chip; + struct iio_dev *indio_dev; + const char *name; + int ret; + + name = devm_kasprintf(dev, GFP_KERNEL, "%s-accel", st->chip_info->name); + if (!name) + return ERR_PTR(-ENOMEM); + + indio_dev = devm_iio_device_alloc(dev, sizeof(*accel_st)); + if (!indio_dev) + return ERR_PTR(-ENOMEM); + accel_st = iio_priv(indio_dev); + + accel_st->scales = st->chip_info->accel_scales; + accel_st->scales_len = st->chip_info->accel_scales_len * 2; + + /* low-power (LP) mode by default at init, no ULP mode */ + accel_st->power_mode = INV_ICM45600_SENSOR_MODE_LOW_POWER; + ret = regmap_set_bits(st->map, INV_ICM45600_REG_SMC_CONTROL_0, + INV_ICM45600_SMC_CONTROL_0_ACCEL_LP_CLK_SEL); + if (ret) + return ERR_PTR(ret); + + /* + * clock period is 32kHz (31250ns) + * jitter is +/- 2% (20 per mille) + */ + ts_chip.clock_period = 31250; + ts_chip.jitter = 20; + ts_chip.init_period = inv_icm45600_odr_to_period(st->conf.accel.odr); + inv_sensors_timestamp_init(&accel_st->ts, &ts_chip); + + iio_device_set_drvdata(indio_dev, st); + indio_dev->name = name; + indio_dev->info = &inv_icm45600_accel_info; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->channels = inv_icm45600_accel_channels; + indio_dev->num_channels = ARRAY_SIZE(inv_icm45600_accel_channels); + indio_dev->available_scan_masks = inv_icm45600_accel_scan_masks; + + ret = devm_iio_kfifo_buffer_setup(dev, indio_dev, + &inv_icm45600_buffer_ops); + if (ret) + return ERR_PTR(ret); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return ERR_PTR(ret); + + return indio_dev; +} + +int inv_icm45600_accel_parse_fifo(struct iio_dev *indio_dev) +{ + struct inv_icm45600_state *st = iio_device_get_drvdata(indio_dev); + struct inv_icm45600_sensor_state *accel_st = iio_priv(indio_dev); + struct inv_sensors_timestamp *ts = &accel_st->ts; + ssize_t i, size; + unsigned int no; + + /* parse all fifo packets */ + for (i = 0, no = 0; i < st->fifo.count; i += size, ++no) { + struct inv_icm45600_accel_buffer buffer = { }; + const struct inv_icm45600_fifo_sensor_data *accel, *gyro; + const __le16 *timestamp; + const s8 *temp; + unsigned int odr; + s64 ts_val; + + size = inv_icm45600_fifo_decode_packet(&st->fifo.data[i], + &accel, &gyro, &temp, ×tamp, &odr); + /* quit if error or FIFO is empty */ + if (size <= 0) + return size; + + /* skip packet if no accel data or data is invalid */ + if (accel == NULL || !inv_icm45600_fifo_is_data_valid(accel)) + continue; + + /* update odr */ + if (odr & INV_ICM45600_SENSOR_ACCEL) + inv_sensors_timestamp_apply_odr(ts, st->fifo.period, + st->fifo.nb.total, no); + + memcpy(&buffer.accel, accel, sizeof(buffer.accel)); + /* convert 8 bits FIFO temperature in high resolution format */ + buffer.temp = temp ? (*temp * 64) : 0; + ts_val = inv_sensors_timestamp_pop(ts); + iio_push_to_buffers_with_ts(indio_dev, &buffer, sizeof(buffer), ts_val); + } + + return 0; +} diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c index 2800995bfa32..2efcc177f9d6 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c @@ -454,6 +454,7 @@ int inv_icm45600_buffer_fifo_read(struct inv_icm45600_state *st, int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st) { struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel); struct inv_sensors_timestamp *ts; int ret; @@ -470,6 +471,16 @@ int inv_icm45600_buffer_fifo_parse(struct inv_icm45600_state *st) return ret; } + /* Handle accelerometer timestamp and FIFO data parsing. */ + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.watermark.eff_accel, + st->timestamp.accel); + ret = inv_icm45600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + return 0; } @@ -477,6 +488,7 @@ int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st, unsigned int count) { struct inv_icm45600_sensor_state *gyro_st = iio_priv(st->indio_gyro); + struct inv_icm45600_sensor_state *accel_st = iio_priv(st->indio_accel); struct inv_sensors_timestamp *ts; s64 gyro_ts, accel_ts; int ret; @@ -499,6 +511,14 @@ int inv_icm45600_buffer_hwfifo_flush(struct inv_icm45600_state *st, return ret; } + if (st->fifo.nb.accel > 0) { + ts = &accel_st->ts; + inv_sensors_timestamp_interrupt(ts, st->fifo.nb.accel, accel_ts); + ret = inv_icm45600_accel_parse_fifo(st->indio_accel); + if (ret) + return ret; + } + return 0; } diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c index 0484e675069f..ab1cb7b9dba4 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c @@ -144,6 +144,12 @@ static const struct inv_icm45600_conf inv_icm45600_default_conf = { .odr = INV_ICM45600_ODR_800HZ_LN, .filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X, }, + .accel = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_ACCEL_FS_16G, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X, + }, }; static const struct inv_icm45600_conf inv_icm45686_default_conf = { @@ -153,12 +159,20 @@ static const struct inv_icm45600_conf inv_icm45686_default_conf = { .odr = INV_ICM45600_ODR_800HZ_LN, .filter = INV_ICM45600_GYRO_LP_AVG_SEL_8X, }, + .accel = { + .mode = INV_ICM45600_SENSOR_MODE_OFF, + .fs = INV_ICM45686_ACCEL_FS_32G, + .odr = INV_ICM45600_ODR_800HZ_LN, + .filter = INV_ICM45600_ACCEL_LP_AVG_SEL_4X, + }, }; const struct inv_icm45600_chip_info inv_icm45605_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45605, .name = "icm45605", .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45600_gyro_scale, .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, }; @@ -168,6 +182,8 @@ const struct inv_icm45600_chip_info inv_icm45606_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45606, .name = "icm45606", .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45600_gyro_scale, .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, }; @@ -177,6 +193,8 @@ const struct inv_icm45600_chip_info inv_icm45608_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45608, .name = "icm45608", .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45600_gyro_scale, .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, }; @@ -186,6 +204,8 @@ const struct inv_icm45600_chip_info inv_icm45634_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45634, .name = "icm45634", .conf = &inv_icm45600_default_conf, + .accel_scales = (const int *)inv_icm45600_accel_scale, + .accel_scales_len = INV_ICM45600_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45600_gyro_scale, .gyro_scales_len = INV_ICM45600_GYRO_FS_MAX, }; @@ -195,6 +215,8 @@ const struct inv_icm45600_chip_info inv_icm45686_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45686, .name = "icm45686", .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45686_gyro_scale, .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, }; @@ -204,6 +226,8 @@ const struct inv_icm45600_chip_info inv_icm45687_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45687, .name = "icm45687", .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45686_gyro_scale, .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, }; @@ -213,6 +237,8 @@ const struct inv_icm45600_chip_info inv_icm45688p_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45688P, .name = "icm45688p", .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45686_gyro_scale, .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, }; @@ -222,6 +248,8 @@ const struct inv_icm45600_chip_info inv_icm45689_chip_info = { .whoami = INV_ICM45600_WHOAMI_ICM45689, .name = "icm45689", .conf = &inv_icm45686_default_conf, + .accel_scales = (const int *)inv_icm45686_accel_scale, + .accel_scales_len = INV_ICM45686_ACCEL_FS_MAX, .gyro_scales = (const int *)inv_icm45686_gyro_scale, .gyro_scales_len = INV_ICM45686_GYRO_FS_MAX, }; @@ -740,6 +768,10 @@ int inv_icm45600_core_probe(struct regmap *regmap, const struct inv_icm45600_chi if (IS_ERR(st->indio_gyro)) return PTR_ERR(st->indio_gyro); + st->indio_accel = inv_icm45600_accel_init(st); + if (IS_ERR(st->indio_accel)) + return PTR_ERR(st->indio_accel); + ret = inv_icm45600_irq_init(st, irq, irq_type, open_drain); if (ret) return ret; From d4684c4363f947d7b10ba8bcbf2c2941a616656d Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:07 +0000 Subject: [PATCH 067/150] iio: imu: inv_icm45600: add I2C driver for inv_icm45600 driver Add I2C driver for InvenSense ICM-456000 devices. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm45600/Kconfig | 21 ++++ drivers/iio/imu/inv_icm45600/Makefile | 3 + .../iio/imu/inv_icm45600/inv_icm45600_i2c.c | 98 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig index ea0a8d20cba2..5b044a954e95 100644 --- a/drivers/iio/imu/inv_icm45600/Kconfig +++ b/drivers/iio/imu/inv_icm45600/Kconfig @@ -5,3 +5,24 @@ config INV_ICM45600 select IIO_BUFFER select IIO_KFIFO_BUF select IIO_INV_SENSORS_TIMESTAMP + +config INV_ICM45600_I2C + tristate "InvenSense ICM-456xx I2C driver" + depends on I2C + select INV_ICM45600 + select REGMAP_I2C + help + This driver supports the InvenSense ICM-456xx motion tracking + devices over I2C. + Supported devices: + - ICM-45605 + - ICM-45606 + - ICM-45608 + - ICM-45634 + - ICM-45686 + - ICM-45687 + - ICM-45688-P + - ICM-45689 + + This driver can be built as a module. The module will be called + inv-icm45600-i2c. diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile index e34553d2b74d..c43e5d6ad3a2 100644 --- a/drivers/iio/imu/inv_icm45600/Makefile +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -5,3 +5,6 @@ inv-icm45600-y += inv_icm45600_core.o inv-icm45600-y += inv_icm45600_buffer.o inv-icm45600-y += inv_icm45600_gyro.o inv-icm45600-y += inv_icm45600_accel.o + +obj-$(CONFIG_INV_ICM45600_I2C) += inv-icm45600-i2c.o +inv-icm45600-i2c-y += inv_icm45600_i2c.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c new file mode 100644 index 000000000000..5ebc18121a11 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 InvenSense, Inc. */ + +#include +#include +#include +#include +#include +#include + +#include "inv_icm45600.h" + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_icm45600_probe(struct i2c_client *client) +{ + const struct inv_icm45600_chip_info *chip_info; + struct regmap *regmap; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return -ENODEV; + + chip_info = device_get_match_data(&client->dev); + if (!chip_info) + return -ENODEV; + + regmap = devm_regmap_init_i2c(client, &inv_icm45600_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm45600_core_probe(regmap, chip_info, true, NULL); +} + +/* + * The device id table is used to identify which device is + * supported by this driver. + */ +static const struct i2c_device_id inv_icm45600_id[] = { + { "icm45605", (kernel_ulong_t)&inv_icm45605_chip_info }, + { "icm45606", (kernel_ulong_t)&inv_icm45606_chip_info }, + { "icm45608", (kernel_ulong_t)&inv_icm45608_chip_info }, + { "icm45634", (kernel_ulong_t)&inv_icm45634_chip_info }, + { "icm45686", (kernel_ulong_t)&inv_icm45686_chip_info }, + { "icm45687", (kernel_ulong_t)&inv_icm45687_chip_info }, + { "icm45688p", (kernel_ulong_t)&inv_icm45688p_chip_info }, + { "icm45689", (kernel_ulong_t)&inv_icm45689_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(i2c, inv_icm45600_id); + +static const struct of_device_id inv_icm45600_of_matches[] = { + { + .compatible = "invensense,icm45605", + .data = &inv_icm45605_chip_info, + }, { + .compatible = "invensense,icm45606", + .data = &inv_icm45606_chip_info, + }, { + .compatible = "invensense,icm45608", + .data = &inv_icm45608_chip_info, + }, { + .compatible = "invensense,icm45634", + .data = &inv_icm45634_chip_info, + }, { + .compatible = "invensense,icm45686", + .data = &inv_icm45686_chip_info, + }, { + .compatible = "invensense,icm45687", + .data = &inv_icm45687_chip_info, + }, { + .compatible = "invensense,icm45688p", + .data = &inv_icm45688p_chip_info, + }, { + .compatible = "invensense,icm45689", + .data = &inv_icm45689_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches); + +static struct i2c_driver inv_icm45600_driver = { + .driver = { + .name = "inv-icm45600-i2c", + .of_match_table = inv_icm45600_of_matches, + .pm = pm_ptr(&inv_icm45600_pm_ops), + }, + .id_table = inv_icm45600_id, + .probe = inv_icm45600_probe, +}; +module_i2c_driver(inv_icm45600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-456xx I2C driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM45600"); From b27492d50d2c75ca48c893fd65e49f855300f0ac Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:08 +0000 Subject: [PATCH 068/150] iio: imu: inv_icm45600: add SPI driver for inv_icm45600 driver Add SPI driver for InvenSense ICM-456000 devices. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm45600/Kconfig | 21 ++++ drivers/iio/imu/inv_icm45600/Makefile | 3 + .../iio/imu/inv_icm45600/inv_icm45600_spi.c | 108 ++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig index 5b044a954e95..01399d136a7e 100644 --- a/drivers/iio/imu/inv_icm45600/Kconfig +++ b/drivers/iio/imu/inv_icm45600/Kconfig @@ -26,3 +26,24 @@ config INV_ICM45600_I2C This driver can be built as a module. The module will be called inv-icm45600-i2c. + +config INV_ICM45600_SPI + tristate "InvenSense ICM-456xx SPI driver" + depends on SPI_MASTER + select INV_ICM45600 + select REGMAP_SPI + help + This driver supports the InvenSense ICM-456xx motion tracking + devices over SPI. + Supported devices: + - ICM-45605 + - ICM-45606 + - ICM-45608 + - ICM-45634 + - ICM-45686 + - ICM-45687 + - ICM-45688-P + - ICM-45689 + + This driver can be built as a module. The module will be called + inv-icm45600-spi. diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile index c43e5d6ad3a2..3692636d393a 100644 --- a/drivers/iio/imu/inv_icm45600/Makefile +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -8,3 +8,6 @@ inv-icm45600-y += inv_icm45600_accel.o obj-$(CONFIG_INV_ICM45600_I2C) += inv-icm45600-i2c.o inv-icm45600-i2c-y += inv_icm45600_i2c.o + +obj-$(CONFIG_INV_ICM45600_SPI) += inv-icm45600-spi.o +inv-icm45600-spi-y += inv_icm45600_spi.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c new file mode 100644 index 000000000000..6288113a6d7c --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 InvenSense, Inc. */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "inv_icm45600.h" + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int inv_icm45600_spi_bus_setup(struct inv_icm45600_state *st) +{ + /* Set slew rates for SPI. */ + return regmap_update_bits(st->map, INV_ICM45600_REG_DRIVE_CONFIG0, + INV_ICM45600_DRIVE_CONFIG0_SPI_MASK, + FIELD_PREP(INV_ICM45600_DRIVE_CONFIG0_SPI_MASK, + INV_ICM45600_SPI_SLEW_RATE_5NS)); +} + +static int inv_icm45600_probe(struct spi_device *spi) +{ + const struct inv_icm45600_chip_info *chip_info; + struct regmap *regmap; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODEV; + + /* Use SPI specific regmap. */ + regmap = devm_regmap_init_spi(spi, &inv_icm45600_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return inv_icm45600_core_probe(regmap, chip_info, true, + inv_icm45600_spi_bus_setup); +} + +/* + * The device id table is used to identify which device is + * supported by this driver. + */ +static const struct spi_device_id inv_icm45600_id[] = { + { "icm45605", (kernel_ulong_t)&inv_icm45605_chip_info }, + { "icm45606", (kernel_ulong_t)&inv_icm45606_chip_info }, + { "icm45608", (kernel_ulong_t)&inv_icm45608_chip_info }, + { "icm45634", (kernel_ulong_t)&inv_icm45634_chip_info }, + { "icm45686", (kernel_ulong_t)&inv_icm45686_chip_info }, + { "icm45687", (kernel_ulong_t)&inv_icm45687_chip_info }, + { "icm45688p", (kernel_ulong_t)&inv_icm45688p_chip_info }, + { "icm45689", (kernel_ulong_t)&inv_icm45689_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, inv_icm45600_id); + +static const struct of_device_id inv_icm45600_of_matches[] = { + { + .compatible = "invensense,icm45605", + .data = &inv_icm45605_chip_info, + }, { + .compatible = "invensense,icm45606", + .data = &inv_icm45606_chip_info, + }, { + .compatible = "invensense,icm45608", + .data = &inv_icm45608_chip_info, + }, { + .compatible = "invensense,icm45634", + .data = &inv_icm45634_chip_info, + }, { + .compatible = "invensense,icm45686", + .data = &inv_icm45686_chip_info, + }, { + .compatible = "invensense,icm45687", + .data = &inv_icm45687_chip_info, + }, { + .compatible = "invensense,icm45688p", + .data = &inv_icm45688p_chip_info, + }, { + .compatible = "invensense,icm45689", + .data = &inv_icm45689_chip_info, + }, + { } +}; +MODULE_DEVICE_TABLE(of, inv_icm45600_of_matches); + +static struct spi_driver inv_icm45600_driver = { + .driver = { + .name = "inv-icm45600-spi", + .of_match_table = inv_icm45600_of_matches, + .pm = pm_ptr(&inv_icm45600_pm_ops), + }, + .id_table = inv_icm45600_id, + .probe = inv_icm45600_probe, +}; +module_spi_driver(inv_icm45600_driver); + +MODULE_AUTHOR("InvenSense, Inc."); +MODULE_DESCRIPTION("InvenSense ICM-456xx SPI driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM45600"); From 1bef24e9007e4bc5fcaf99df1c256969ba58d5e6 Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:09 +0000 Subject: [PATCH 069/150] iio: imu: inv_icm45600: add I3C driver for inv_icm45600 driver Add I3C driver for InvenSense ICM-45600 devices. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm45600/Kconfig | 21 +++++ drivers/iio/imu/inv_icm45600/Makefile | 3 + .../iio/imu/inv_icm45600/inv_icm45600_i3c.c | 78 +++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig index 01399d136a7e..dc133402f6d7 100644 --- a/drivers/iio/imu/inv_icm45600/Kconfig +++ b/drivers/iio/imu/inv_icm45600/Kconfig @@ -47,3 +47,24 @@ config INV_ICM45600_SPI This driver can be built as a module. The module will be called inv-icm45600-spi. + +config INV_ICM45600_I3C + tristate "InvenSense ICM-456xx I3C driver" + depends on I3C + select INV_ICM45600 + select REGMAP_I3C + help + This driver supports the InvenSense ICM-456xx motion tracking + devices over I3C. + Supported devices: + - ICM-45605 + - ICM-45606 + - ICM-45608 + - ICM-45634 + - ICM-45686 + - ICM-45687 + - ICM-45688-P + - ICM-45689 + + This driver can be built as a module. The module will be called + inv-icm45600-i3c. diff --git a/drivers/iio/imu/inv_icm45600/Makefile b/drivers/iio/imu/inv_icm45600/Makefile index 3692636d393a..c98b8365b467 100644 --- a/drivers/iio/imu/inv_icm45600/Makefile +++ b/drivers/iio/imu/inv_icm45600/Makefile @@ -11,3 +11,6 @@ inv-icm45600-i2c-y += inv_icm45600_i2c.o obj-$(CONFIG_INV_ICM45600_SPI) += inv-icm45600-spi.o inv-icm45600-spi-y += inv_icm45600_spi.o + +obj-$(CONFIG_INV_ICM45600_I3C) += inv-icm45600-i3c.o +inv-icm45600-i3c-y += inv_icm45600_i3c.o diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c new file mode 100644 index 000000000000..b5df06b97d44 --- /dev/null +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Copyright (C) 2025 InvenSense, Inc. */ + +#include +#include +#include +#include + +#include +#include + +#include "inv_icm45600.h" + +static const struct regmap_config inv_icm45600_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static const struct i3c_device_id inv_icm45600_i3c_ids[] = { + I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0011, (void *)NULL), + I3C_DEVICE_EXTRA_INFO(0x0235, 0x0000, 0x0084, (void *)NULL), + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i3c, inv_icm45600_i3c_ids); + +static const struct inv_icm45600_chip_info *i3c_chip_info[] = { + &inv_icm45605_chip_info, + &inv_icm45606_chip_info, + &inv_icm45608_chip_info, + &inv_icm45634_chip_info, + &inv_icm45686_chip_info, + &inv_icm45687_chip_info, + &inv_icm45688p_chip_info, + &inv_icm45689_chip_info, +}; + +static int inv_icm45600_i3c_probe(struct i3c_device *i3cdev) +{ + int ret; + unsigned int whoami; + struct regmap *regmap; + const int nb_chip = ARRAY_SIZE(i3c_chip_info); + int chip; + + regmap = devm_regmap_init_i3c(i3cdev, &inv_icm45600_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&i3cdev->dev, PTR_ERR(regmap), + "Failed to register i3c regmap %ld\n", PTR_ERR(regmap)); + + ret = regmap_read(regmap, INV_ICM45600_REG_WHOAMI, &whoami); + if (ret) + return dev_err_probe(&i3cdev->dev, ret, "Failed to read part id %d\n", whoami); + + for (chip = 0; chip < nb_chip; chip++) { + if (whoami == i3c_chip_info[chip]->whoami) + break; + } + + if (chip == nb_chip) + dev_err_probe(&i3cdev->dev, -ENODEV, "Failed to match part id %d\n", whoami); + + return inv_icm45600_core_probe(regmap, i3c_chip_info[chip], false, NULL); +} + +static struct i3c_driver inv_icm45600_driver = { + .driver = { + .name = "inv_icm45600_i3c", + .pm = pm_sleep_ptr(&inv_icm45600_pm_ops), + }, + .probe = inv_icm45600_i3c_probe, + .id_table = inv_icm45600_i3c_ids, +}; +module_i3c_driver(inv_icm45600_driver); + +MODULE_AUTHOR("Remi Buisson "); +MODULE_DESCRIPTION("InvenSense ICM-456xx i3c driver"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_ICM45600"); From b73e812a82508b8ba81b7ac9f64a75ddd0be74c7 Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Tue, 7 Oct 2025 07:20:10 +0000 Subject: [PATCH 070/150] MAINTAINERS: add entry for inv_icm45600 6-axis imu sensor Add MAINTAINERS entry for InvenSense ICM-45600 IMU device. Signed-off-by: Remi Buisson Signed-off-by: Jonathan Cameron --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 371ac55d0edc..8082081ea742 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13040,6 +13040,14 @@ F: Documentation/ABI/testing/sysfs-bus-iio-inv_icm42600 F: Documentation/devicetree/bindings/iio/imu/invensense,icm42600.yaml F: drivers/iio/imu/inv_icm42600/ +INVENSENSE ICM-456xx IMU DRIVER +M: Remi Buisson +L: linux-iio@vger.kernel.org +S: Maintained +W: https://invensense.tdk.com/ +F: Documentation/devicetree/bindings/iio/imu/invensense,icm45600.yaml +F: drivers/iio/imu/inv_icm45600/ + INVENSENSE MPU-3050 GYROSCOPE DRIVER M: Linus Walleij L: linux-iio@vger.kernel.org From b66cddc8be7278fd14650ff9182f3794397f8b31 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 7 Oct 2025 11:15:20 +0000 Subject: [PATCH 071/150] iio: adc: ad4080: fix chip identification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix AD4080 chip identification by using the correct 16-bit product ID (0x0050) instead of GENMASK(2, 0). Update the chip reading logic to use regmap_bulk_read to read both PRODUCT_ID_L and PRODUCT_ID_H registers and combine them into a 16-bit value. The original implementation was incorrectly reading only 3 bits, which would not correctly identify the AD4080 chip. Fixes: 6b31ba1811b6 ("iio: adc: ad4080: add driver support") Signed-off-by: Antoniu Miclaus Reviewed-by: Nuno Sá Cc: Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 6e61787ed321..e15310fcd21a 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -125,7 +125,7 @@ /* Miscellaneous Definitions */ #define AD4080_SPI_READ BIT(7) -#define AD4080_CHIP_ID GENMASK(2, 0) +#define AD4080_CHIP_ID 0x0050 #define AD4080_LVDS_CNV_CLK_CNT_MAX 7 @@ -445,7 +445,8 @@ static int ad4080_setup(struct iio_dev *indio_dev) { struct ad4080_state *st = iio_priv(indio_dev); struct device *dev = regmap_get_device(st->regmap); - unsigned int id; + __le16 id_le; + u16 id; int ret; ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A, @@ -458,10 +459,12 @@ static int ad4080_setup(struct iio_dev *indio_dev) if (ret) return ret; - ret = regmap_read(st->regmap, AD4080_REG_CHIP_TYPE, &id); + ret = regmap_bulk_read(st->regmap, AD4080_REG_PRODUCT_ID_L, &id_le, + sizeof(id_le)); if (ret) return ret; + id = le16_to_cpu(id_le); if (id != AD4080_CHIP_ID) dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id); From 69ca4df3491a9475fa2f92ce711f5a21cd26a4a9 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 7 Oct 2025 11:15:21 +0000 Subject: [PATCH 072/150] iio: adc: ad4080: prepare driver for multi-part support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the ad4080 driver to support multiple ADC variants with different resolution bits and LVDS CNV clock count maximums. Changes: - Add lvds_cnv_clk_cnt_max field to chip_info structure - Create AD4080_CHANNEL_DEFINE macro for variable resolution/storage bits - Make LVDS CNV clock count configurable per chip variant - Use chip_info->product_id for chip identification comparison This prepares the infrastructure for adding support for additional ADC parts with different specifications while maintaining backward compatibility with existing AD4080 functionality. Reviewed-by: Nuno Sá Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 42 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index e15310fcd21a..646a4de01ca7 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -167,6 +167,7 @@ struct ad4080_chip_info { const unsigned int (*scale_table)[2]; const struct iio_chan_spec *channels; unsigned int num_channels; + unsigned int lvds_cnv_clk_cnt_max; }; struct ad4080_state { @@ -414,23 +415,25 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = { { } }; -static const struct iio_chan_spec ad4080_channel = { - .type = IIO_VOLTAGE, - .indexed = 1, - .channel = 0, - .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), - .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | - BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), - .info_mask_shared_by_all_available = - BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), - .ext_info = ad4080_ext_info, - .scan_index = 0, - .scan_type = { - .sign = 's', - .realbits = 20, - .storagebits = 32, - }, -}; +#define AD4080_CHANNEL_DEFINE(bits, storage) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \ + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .info_mask_shared_by_all_available = \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \ + .ext_info = ad4080_ext_info, \ + .scan_index = 0, \ + .scan_type = { \ + .sign = 's', \ + .realbits = (bits), \ + .storagebits = (storage), \ + }, \ +} + +static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32); static const struct ad4080_chip_info ad4080_chip_info = { .name = "ad4080", @@ -439,6 +442,7 @@ static const struct ad4080_chip_info ad4080_chip_info = { .num_scales = ARRAY_SIZE(ad4080_scale_table), .num_channels = 1, .channels = &ad4080_channel, + .lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX, }; static int ad4080_setup(struct iio_dev *indio_dev) @@ -465,7 +469,7 @@ static int ad4080_setup(struct iio_dev *indio_dev) return ret; id = le16_to_cpu(id_le); - if (id != AD4080_CHIP_ID) + if (id != st->info->product_id) dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id); ret = regmap_set_bits(st->regmap, AD4080_REG_GPIO_CONFIG_A, @@ -491,7 +495,7 @@ static int ad4080_setup(struct iio_dev *indio_dev) AD4080_REG_ADC_DATA_INTF_CONFIG_B, AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK, FIELD_PREP(AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK, - AD4080_LVDS_CNV_CLK_CNT_MAX)); + st->info->lvds_cnv_clk_cnt_max)); if (ret) return ret; From 1b86053ba6f818624efffa8d1b890b4c74886903 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 7 Oct 2025 11:15:22 +0000 Subject: [PATCH 073/150] dt-bindings: iio: adc: adi,ad4080: add support for AD4084 Add device tree binding support for the AD4084 16-bit SAR ADC. Add adi,ad4084 to the compatible enum. A fallback compatible string to adi,ad4080 is not appropriate as the AD4084 has different resolution (16-bit vs 20-bit) and LVDS CNV clock count maximum (2 vs 7), requiring different driver configuration. Acked-by: Conor Dooley Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml index ed849ba1b77b..c4c5d208f502 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml @@ -26,6 +26,7 @@ properties: compatible: enum: - adi,ad4080 + - adi,ad4084 reg: maxItems: 1 From 6c3e7265734bbde7347d44fa8e5720bcc5ad5cea Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 7 Oct 2025 11:15:23 +0000 Subject: [PATCH 074/150] iio: adc: ad4080: add support for AD4084 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for AD4084 16-bit SAR ADC. The AD4084 differs from AD4080 in resolution (16-bit vs 20-bit) and LVDS CNV clock count maximum (2 vs 7). Changes: - Add AD4084_CHIP_ID definition (0x0054) - Create ad4084_channel with 16-bit resolution and storage - Add ad4084_chip_info with appropriate configuration - Register AD4084 in device ID and OF match tables Reviewed-by: Nuno Sá Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 646a4de01ca7..d243b4336ac6 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -126,6 +126,7 @@ /* Miscellaneous Definitions */ #define AD4080_SPI_READ BIT(7) #define AD4080_CHIP_ID 0x0050 +#define AD4084_CHIP_ID 0x0054 #define AD4080_LVDS_CNV_CLK_CNT_MAX 7 @@ -435,6 +436,8 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = { static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32); +static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16); + static const struct ad4080_chip_info ad4080_chip_info = { .name = "ad4080", .product_id = AD4080_CHIP_ID, @@ -445,6 +448,16 @@ static const struct ad4080_chip_info ad4080_chip_info = { .lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX, }; +static const struct ad4080_chip_info ad4084_chip_info = { + .name = "ad4084", + .product_id = AD4084_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4084_channel, + .lvds_cnv_clk_cnt_max = 2, +}; + static int ad4080_setup(struct iio_dev *indio_dev) { struct ad4080_state *st = iio_priv(indio_dev); @@ -600,12 +613,14 @@ static int ad4080_probe(struct spi_device *spi) static const struct spi_device_id ad4080_id[] = { { "ad4080", (kernel_ulong_t)&ad4080_chip_info }, + { "ad4084", (kernel_ulong_t)&ad4084_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad4080_id); static const struct of_device_id ad4080_of_match[] = { { .compatible = "adi,ad4080", &ad4080_chip_info }, + { .compatible = "adi,ad4084", &ad4084_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad4080_of_match); From 83185903b4eca86e0c7145d62e55de768eeb9e7b Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 7 Oct 2025 11:15:24 +0000 Subject: [PATCH 075/150] dt-bindings: iio: adc: adi,ad4080: add support for AD4081 Add device tree binding support for the AD4081 20-bit SAR ADC. Add adi,ad4081 to the compatible enum. A fallback compatible string to adi,ad4080 is not appropriate as the AD4081 has a different LVDS CNV clock count maximum (2 vs 7), requiring different driver configuration. Acked-by: Conor Dooley Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml index c4c5d208f502..a9fa068189ea 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml @@ -26,6 +26,7 @@ properties: compatible: enum: - adi,ad4080 + - adi,ad4081 - adi,ad4084 reg: From d34ad6467200380c8f8337ceefee9f3d8cee5492 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 7 Oct 2025 11:15:25 +0000 Subject: [PATCH 076/150] iio: adc: ad4080: add support for AD4081 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for AD4081 20-bit SAR ADC. The AD4081 has the same resolution as AD4080 (20-bit) but differs in LVDS CNV clock count maximum (2 vs 7). Changes: - Add AD4081_CHIP_ID definition (0x0051) - Create ad4081_channel with 20-bit resolution and 32-bit storage - Add ad4081_chip_info with lvds_cnv_clk_cnt_max = 2 - Register AD4081 in device ID and OF match tables Reviewed-by: Nuno Sá Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index d243b4336ac6..5940651655df 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -126,6 +126,7 @@ /* Miscellaneous Definitions */ #define AD4080_SPI_READ BIT(7) #define AD4080_CHIP_ID 0x0050 +#define AD4081_CHIP_ID 0x0051 #define AD4084_CHIP_ID 0x0054 #define AD4080_LVDS_CNV_CLK_CNT_MAX 7 @@ -436,6 +437,8 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = { static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32); +static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32); + static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16); static const struct ad4080_chip_info ad4080_chip_info = { @@ -448,6 +451,16 @@ static const struct ad4080_chip_info ad4080_chip_info = { .lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX, }; +static const struct ad4080_chip_info ad4081_chip_info = { + .name = "ad4081", + .product_id = AD4081_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4081_channel, + .lvds_cnv_clk_cnt_max = 2, +}; + static const struct ad4080_chip_info ad4084_chip_info = { .name = "ad4084", .product_id = AD4084_CHIP_ID, @@ -613,6 +626,7 @@ static int ad4080_probe(struct spi_device *spi) static const struct spi_device_id ad4080_id[] = { { "ad4080", (kernel_ulong_t)&ad4080_chip_info }, + { "ad4081", (kernel_ulong_t)&ad4081_chip_info }, { "ad4084", (kernel_ulong_t)&ad4084_chip_info }, { } }; @@ -620,6 +634,7 @@ MODULE_DEVICE_TABLE(spi, ad4080_id); static const struct of_device_id ad4080_of_match[] = { { .compatible = "adi,ad4080", &ad4080_chip_info }, + { .compatible = "adi,ad4081", &ad4081_chip_info }, { .compatible = "adi,ad4084", &ad4084_chip_info }, { } }; From f277fe2ff54aeea3b5c31aa045f03ccea612a299 Mon Sep 17 00:00:00 2001 From: Dixit Parmar Date: Sat, 11 Oct 2025 13:41:49 +0530 Subject: [PATCH 077/150] iio: adc: ti-ads131e08: return correct error code The error code returned from devm_iio_trigger_register() inturn iio_trigger_register() can be other than -ENOMEM. Hence return the same value as it was returned from the function call. This change makes devm_iio_trigger_register() handling uniform with other iio drivers. Signed-off-by: Dixit Parmar Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti-ads131e08.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ti-ads131e08.c b/drivers/iio/adc/ti-ads131e08.c index 742acc6d8cf9..c9a20024d6b1 100644 --- a/drivers/iio/adc/ti-ads131e08.c +++ b/drivers/iio/adc/ti-ads131e08.c @@ -848,7 +848,7 @@ static int ads131e08_probe(struct spi_device *spi) ret = devm_iio_trigger_register(&spi->dev, st->trig); if (ret) { dev_err(&spi->dev, "failed to register IIO trigger\n"); - return -ENOMEM; + return ret; } indio_dev->trig = iio_trigger_get(st->trig); From 8f0072c742e37474de058a41256fb91934dd4d5d Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 11 Oct 2025 22:59:09 +0200 Subject: [PATCH 078/150] dt-bindings: iio: accel: adxl345: document second interrupt The pinout of all the supported chips in this binding have two interrupt pins. Document the second one, too, even though the Linux driver currently does not support the second interrupt. Boards may have it wired nonetheless. While here, drop the dependency of interrupt-names which is already described in the core. Reviewed-by: Geert Uytterhoeven Signed-off-by: Wolfram Sang Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/accel/adi,adxl345.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml index a23a626bfab6..61d7ba89adc2 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl345.yaml @@ -35,15 +35,17 @@ properties: spi-3wire: true interrupts: - maxItems: 1 + minItems: 1 + maxItems: 2 interrupt-names: + minItems: 1 items: - enum: [INT1, INT2] + - const: INT2 dependencies: interrupts: [ interrupt-names ] - interrupt-names: [ interrupts ] required: - compatible @@ -84,7 +86,8 @@ examples: spi-cpol; spi-cpha; interrupt-parent = <&gpio0>; - interrupts = <0 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "INT2"; + interrupts = <0 IRQ_TYPE_LEVEL_HIGH>, + <1 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "INT1", "INT2"; }; }; From e7966a4953a3c9e40382693f578523da2b384919 Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Sun, 12 Oct 2025 23:00:34 +0530 Subject: [PATCH 079/150] dt-bindings: iio: health: max30100: Add LED pulse-width property The LED pulse width on the MAX30100 sensor determines how long the IR/Red LEDs are driven during each sample, directly affecting the emitted optical energy and hence the received signal amplitude. This parameter is highly dependent on the mechanical and optical integration of the sensor, such as: - The type and thickness of the optical window or lens covering the sensor. - The distance between the LED and photodiode. - The reflectivity of the target surface. For example: - A smartwatch or wearable ring with a thin glass window can operate with shorter pulses (200-400 us) to save power. - A medical-grade pulse oximeter or sensor mounted behind a thicker protective layer may require longer pulses (800-1600 us) for reliable signal amplitude. Because this configuration is determined by hardware design rather than by runtime conditions, it is appropriate to describe it in the DT. If not specified, the driver defaults to 1600 us to maintain existing behavior. Tested on: Raspberry Pi 3B + MAX30100 breakout board. Signed-off-by: Shrikant Raskar Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/health/maxim,max30100.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/health/maxim,max30100.yaml b/Documentation/devicetree/bindings/iio/health/maxim,max30100.yaml index 967778fb0ce8..d4753c85ecc3 100644 --- a/Documentation/devicetree/bindings/iio/health/maxim,max30100.yaml +++ b/Documentation/devicetree/bindings/iio/health/maxim,max30100.yaml @@ -27,6 +27,14 @@ properties: LED current whilst the engine is running. First indexed value is the configuration for the RED LED, and second value is for the IR LED. + maxim,pulse-width-us: + description: | + LED pulse width in microseconds. Appropriate pulse width depends on + factors such as optical window absorption, LED-to-sensor distance, + and expected reflectivity of the skin or contact surface. + enum: [200, 400, 800, 1600] + default: 1600 + additionalProperties: false required: From 6365d2b988aa6515983b498333d832297418563f Mon Sep 17 00:00:00 2001 From: Shrikant Raskar Date: Sun, 12 Oct 2025 23:00:35 +0530 Subject: [PATCH 080/150] iio: health: max30100: Make LED pulse-width configurable via DT The required LED pulse width depends on board-specific optical and mechanical design, which affects measurement accuracy and power use. Making it configurable via Device Tree allows each platform to define an appropriate value instead of relying on a hardcoded default. If unspecified, the driver defaults to 1600 us for backward compatibility. Tested on: Raspberry Pi 3B + MAX30100 breakout board. Reviewed-by: Nuno Sa Signed-off-by: Shrikant Raskar Signed-off-by: Jonathan Cameron --- drivers/iio/health/max30100.c | 38 ++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/iio/health/max30100.c b/drivers/iio/health/max30100.c index 814f521e47ae..3d441013893c 100644 --- a/drivers/iio/health/max30100.c +++ b/drivers/iio/health/max30100.c @@ -5,7 +5,6 @@ * Copyright (C) 2015, 2018 * Author: Matt Ranostay * - * TODO: enable pulse length controls via device tree properties */ #include @@ -18,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -52,9 +52,13 @@ #define MAX30100_REG_MODE_CONFIG_PWR BIT(7) #define MAX30100_REG_SPO2_CONFIG 0x07 +#define MAX30100_REG_SPO2_CONFIG_PW_MASK GENMASK(1, 0) +#define MAX30100_REG_SPO2_CONFIG_200US 0x0 +#define MAX30100_REG_SPO2_CONFIG_400US 0x1 +#define MAX30100_REG_SPO2_CONFIG_800US 0x2 +#define MAX30100_REG_SPO2_CONFIG_1600US 0x3 #define MAX30100_REG_SPO2_CONFIG_100HZ BIT(2) #define MAX30100_REG_SPO2_CONFIG_HI_RES_EN BIT(6) -#define MAX30100_REG_SPO2_CONFIG_1600US 0x3 #define MAX30100_REG_LED_CONFIG 0x09 #define MAX30100_REG_LED_CONFIG_LED_MASK 0x0f @@ -306,19 +310,47 @@ static int max30100_led_init(struct max30100_data *data) MAX30100_REG_LED_CONFIG_LED_MASK, reg); } +static int max30100_get_pulse_width(unsigned int pwidth_us) +{ + switch (pwidth_us) { + case 200: + return MAX30100_REG_SPO2_CONFIG_200US; + case 400: + return MAX30100_REG_SPO2_CONFIG_400US; + case 800: + return MAX30100_REG_SPO2_CONFIG_800US; + case 1600: + return MAX30100_REG_SPO2_CONFIG_1600US; + default: + return -EINVAL; + } +} + static int max30100_chip_init(struct max30100_data *data) { int ret; + int pulse_width; + /* set default LED pulse-width to 1600 us */ + unsigned int pulse_us = 1600; + struct device *dev = &data->client->dev; /* setup LED current settings */ ret = max30100_led_init(data); if (ret) return ret; + /* Read LED pulse-width-us from DT */ + device_property_read_u32(dev, "maxim,pulse-width-us", &pulse_us); + + pulse_width = max30100_get_pulse_width(pulse_us); + if (pulse_width < 0) + return dev_err_probe(dev, pulse_width, "invalid LED pulse-width %uus\n", pulse_us); + /* enable hi-res SPO2 readings at 100Hz */ ret = regmap_write(data->regmap, MAX30100_REG_SPO2_CONFIG, MAX30100_REG_SPO2_CONFIG_HI_RES_EN | - MAX30100_REG_SPO2_CONFIG_100HZ); + MAX30100_REG_SPO2_CONFIG_100HZ | + FIELD_PREP(MAX30100_REG_SPO2_CONFIG_PW_MASK, pulse_width)); if (ret) return ret; From 36bf0de9d6ec8f5ce50bdb9f2bd90150c9026d9a Mon Sep 17 00:00:00 2001 From: Akshay Jindal Date: Sun, 12 Oct 2025 23:36:08 +0530 Subject: [PATCH 081/150] iio: accel: bma400: Reorganize and rename register and field macros Reorganize register and field macros to improve consistency with the datasheet and naming style: - Move field macros next to their corresponding register macros - Reorder register macros to follow address order from the datasheet - Rename field macros to include the register name in the macro name - Add a _REG suffix to register macros where missing - Add INT_STAT register fields corresponding to used INT_CONFIG fields No functional changes are intended. Signed-off-by: Akshay Jindal Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma400.h | 117 ++++++++++-------- drivers/iio/accel/bma400_core.c | 212 ++++++++++++++++---------------- 2 files changed, 170 insertions(+), 159 deletions(-) diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h index 932358b45f17..fcafd1fba57a 100644 --- a/drivers/iio/accel/bma400.h +++ b/drivers/iio/accel/bma400.h @@ -16,31 +16,44 @@ * Read-Only Registers */ +/* Chip ID of BMA 400 devices found in the chip ID register. */ +#define BMA400_ID_REG_VAL 0x90 + /* Status and ID registers */ #define BMA400_CHIP_ID_REG 0x00 #define BMA400_ERR_REG 0x02 #define BMA400_STATUS_REG 0x03 /* Acceleration registers */ -#define BMA400_X_AXIS_LSB_REG 0x04 -#define BMA400_X_AXIS_MSB_REG 0x05 -#define BMA400_Y_AXIS_LSB_REG 0x06 -#define BMA400_Y_AXIS_MSB_REG 0x07 -#define BMA400_Z_AXIS_LSB_REG 0x08 -#define BMA400_Z_AXIS_MSB_REG 0x09 +#define BMA400_ACC_X_LSB_REG 0x04 +#define BMA400_ACC_X_MSB_REG 0x05 +#define BMA400_ACC_Y_LSB_REG 0x06 +#define BMA400_ACC_Y_MSB_REG 0x07 +#define BMA400_ACC_Z_LSB_REG 0x08 +#define BMA400_ACC_Z_MSB_REG 0x09 /* Sensor time registers */ -#define BMA400_SENSOR_TIME0 0x0a -#define BMA400_SENSOR_TIME1 0x0b -#define BMA400_SENSOR_TIME2 0x0c +#define BMA400_SENSOR_TIME0_REG 0x0a +#define BMA400_SENSOR_TIME1_REG 0x0b +#define BMA400_SENSOR_TIME2_REG 0x0c /* Event and interrupt registers */ #define BMA400_EVENT_REG 0x0d + #define BMA400_INT_STAT0_REG 0x0e +#define BMA400_INT_STAT0_GEN1_MASK BIT(2) +#define BMA400_INT_STAT0_GEN2_MASK BIT(3) +#define BMA400_INT_STAT0_DRDY_MASK BIT(7) + #define BMA400_INT_STAT1_REG 0x0f +#define BMA400_INT_STAT1_STEP_INT_MASK GENMASK(9, 8) +#define BMA400_INT_STAT1_S_TAP_MASK BIT(10) +#define BMA400_INT_STAT1_D_TAP_MASK BIT(11) + #define BMA400_INT_STAT2_REG 0x10 -#define BMA400_INT12_MAP_REG 0x23 -#define BMA400_INT_ENG_OVRUN_MSK BIT(4) + +/* Bit present in all INT_STAT registers */ +#define BMA400_INT_STAT_ENG_OVRRUN_MASK BIT(4) /* Temperature register */ #define BMA400_TEMP_DATA_REG 0x11 @@ -55,70 +68,68 @@ #define BMA400_STEP_CNT1_REG 0x16 #define BMA400_STEP_CNT3_REG 0x17 #define BMA400_STEP_STAT_REG 0x18 -#define BMA400_STEP_INT_MSK BIT(0) #define BMA400_STEP_RAW_LEN 0x03 -#define BMA400_STEP_STAT_MASK GENMASK(9, 8) /* * Read-write configuration registers */ -#define BMA400_ACC_CONFIG0_REG 0x19 -#define BMA400_ACC_CONFIG1_REG 0x1a +#define BMA400_ACC_CONFIG0_REG 0x19 +#define BMA400_ACC_CONFIG0_LP_OSR_MASK GENMASK(6, 5) +#define BMA400_LP_OSR_SHIFT 5 + +#define BMA400_ACC_CONFIG1_REG 0x1a +#define BMA400_ACC_CONFIG1_ODR_MASK GENMASK(3, 0) +#define BMA400_ACC_CONFIG1_ODR_MIN_RAW 0x05 +#define BMA400_ACC_CONFIG1_ODR_LP_RAW 0x06 +#define BMA400_ACC_CONFIG1_ODR_MAX_RAW 0x0b +#define BMA400_ACC_CONFIG1_ODR_MAX_HZ 800 +#define BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ 25 +#define BMA400_ACC_CONFIG1_ODR_MIN_HZ 12 +#define BMA400_ACC_CONFIG1_NP_OSR_MASK GENMASK(5, 4) +#define BMA400_NP_OSR_SHIFT 4 +#define BMA400_ACC_CONFIG1_ACC_RANGE_MASK GENMASK(7, 6) +#define BMA400_ACC_RANGE_SHIFT 6 + #define BMA400_ACC_CONFIG2_REG 0x1b -#define BMA400_CMD_REG 0x7e /* Interrupt registers */ #define BMA400_INT_CONFIG0_REG 0x1f +#define BMA400_INT_CONFIG0_GEN1_MASK BIT(2) +#define BMA400_INT_CONFIG0_GEN2_MASK BIT(3) +#define BMA400_INT_CONFIG0_DRDY_MASK BIT(7) + #define BMA400_INT_CONFIG1_REG 0x20 +#define BMA400_INT_CONFIG1_STEP_INT_MASK BIT(0) +#define BMA400_INT_CONFIG1_S_TAP_MASK BIT(2) +#define BMA400_INT_CONFIG1_D_TAP_MASK BIT(3) + #define BMA400_INT1_MAP_REG 0x21 +#define BMA400_INT12_MAP_REG 0x23 #define BMA400_INT_IO_CTRL_REG 0x24 -#define BMA400_INT_DRDY_MSK BIT(7) - -/* Chip ID of BMA 400 devices found in the chip ID register. */ -#define BMA400_ID_REG_VAL 0x90 - -#define BMA400_LP_OSR_SHIFT 5 -#define BMA400_NP_OSR_SHIFT 4 -#define BMA400_SCALE_SHIFT 6 #define BMA400_TWO_BITS_MASK GENMASK(1, 0) -#define BMA400_LP_OSR_MASK GENMASK(6, 5) -#define BMA400_NP_OSR_MASK GENMASK(5, 4) -#define BMA400_ACC_ODR_MASK GENMASK(3, 0) -#define BMA400_ACC_SCALE_MASK GENMASK(7, 6) - -#define BMA400_ACC_ODR_MIN_RAW 0x05 -#define BMA400_ACC_ODR_LP_RAW 0x06 -#define BMA400_ACC_ODR_MAX_RAW 0x0b - -#define BMA400_ACC_ODR_MAX_HZ 800 -#define BMA400_ACC_ODR_MIN_WHOLE_HZ 25 -#define BMA400_ACC_ODR_MIN_HZ 12 /* Generic interrupts register */ -#define BMA400_GEN1INT_CONFIG0 0x3f -#define BMA400_GEN2INT_CONFIG0 0x4A +#define BMA400_GEN1INT_CONFIG0_REG 0x3f +#define BMA400_GEN2INT_CONFIG0_REG 0x4A +#define BMA400_GENINT_CONFIG0_HYST_MASK GENMASK(1, 0) + #define BMA400_GEN_CONFIG1_OFF 0x01 #define BMA400_GEN_CONFIG2_OFF 0x02 #define BMA400_GEN_CONFIG3_OFF 0x03 #define BMA400_GEN_CONFIG31_OFF 0x04 -#define BMA400_INT_GEN1_MSK BIT(2) -#define BMA400_INT_GEN2_MSK BIT(3) -#define BMA400_GEN_HYST_MSK GENMASK(1, 0) /* TAP config registers */ -#define BMA400_TAP_CONFIG 0x57 -#define BMA400_TAP_CONFIG1 0x58 -#define BMA400_S_TAP_MSK BIT(2) -#define BMA400_D_TAP_MSK BIT(3) -#define BMA400_INT_S_TAP_MSK BIT(10) -#define BMA400_INT_D_TAP_MSK BIT(11) -#define BMA400_TAP_SEN_MSK GENMASK(2, 0) -#define BMA400_TAP_TICSTH_MSK GENMASK(1, 0) -#define BMA400_TAP_QUIET_MSK GENMASK(3, 2) -#define BMA400_TAP_QUIETDT_MSK GENMASK(5, 4) +#define BMA400_TAP_CONFIG_REG 0x57 +#define BMA400_TAP_CONFIG_SEN_MASK GENMASK(2, 0) + +#define BMA400_TAP_CONFIG1_REG 0x58 +#define BMA400_TAP_CONFIG1_TICSTH_MASK GENMASK(1, 0) +#define BMA400_TAP_CONFIG1_QUIET_MASK GENMASK(3, 2) +#define BMA400_TAP_CONFIG1_QUIETDT_MASK GENMASK(5, 4) #define BMA400_TAP_TIM_LIST_LEN 4 +#define BMA400_CMD_REG 0x7e /* * BMA400_SCALE_MIN macro value represents m/s^2 for 1 LSB before * converting to micro values for +-2g range. @@ -138,8 +149,8 @@ * To select +-8g = 9577 << 2 = raw value to write is 2. * To select +-16g = 9577 << 3 = raw value to write is 3. */ -#define BMA400_SCALE_MIN 9577 -#define BMA400_SCALE_MAX 76617 +#define BMA400_ACC_SCALE_MIN 9577 +#define BMA400_ACC_SCALE_MAX 76617 extern const struct regmap_config bma400_regmap_config; diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 85e23badf733..2324c4ef645c 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -127,15 +127,15 @@ static bool bma400_is_writable_reg(struct device *dev, unsigned int reg) case BMA400_CHIP_ID_REG: case BMA400_ERR_REG: case BMA400_STATUS_REG: - case BMA400_X_AXIS_LSB_REG: - case BMA400_X_AXIS_MSB_REG: - case BMA400_Y_AXIS_LSB_REG: - case BMA400_Y_AXIS_MSB_REG: - case BMA400_Z_AXIS_LSB_REG: - case BMA400_Z_AXIS_MSB_REG: - case BMA400_SENSOR_TIME0: - case BMA400_SENSOR_TIME1: - case BMA400_SENSOR_TIME2: + case BMA400_ACC_X_LSB_REG: + case BMA400_ACC_X_MSB_REG: + case BMA400_ACC_Y_LSB_REG: + case BMA400_ACC_Y_MSB_REG: + case BMA400_ACC_Z_LSB_REG: + case BMA400_ACC_Z_MSB_REG: + case BMA400_SENSOR_TIME0_REG: + case BMA400_SENSOR_TIME1_REG: + case BMA400_SENSOR_TIME2_REG: case BMA400_EVENT_REG: case BMA400_INT_STAT0_REG: case BMA400_INT_STAT1_REG: @@ -159,15 +159,15 @@ static bool bma400_is_volatile_reg(struct device *dev, unsigned int reg) switch (reg) { case BMA400_ERR_REG: case BMA400_STATUS_REG: - case BMA400_X_AXIS_LSB_REG: - case BMA400_X_AXIS_MSB_REG: - case BMA400_Y_AXIS_LSB_REG: - case BMA400_Y_AXIS_MSB_REG: - case BMA400_Z_AXIS_LSB_REG: - case BMA400_Z_AXIS_MSB_REG: - case BMA400_SENSOR_TIME0: - case BMA400_SENSOR_TIME1: - case BMA400_SENSOR_TIME2: + case BMA400_ACC_X_LSB_REG: + case BMA400_ACC_X_MSB_REG: + case BMA400_ACC_Y_LSB_REG: + case BMA400_ACC_Y_MSB_REG: + case BMA400_ACC_Z_LSB_REG: + case BMA400_ACC_Z_MSB_REG: + case BMA400_SENSOR_TIME0_REG: + case BMA400_SENSOR_TIME1_REG: + case BMA400_SENSOR_TIME2_REG: case BMA400_EVENT_REG: case BMA400_INT_STAT0_REG: case BMA400_INT_STAT1_REG: @@ -275,11 +275,11 @@ static ssize_t in_accel_gesture_tap_maxtomin_time_show(struct device *dev, struct bma400_data *data = iio_priv(indio_dev); int ret, reg_val, raw, vals[2]; - ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1, ®_val); + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, ®_val); if (ret) return ret; - raw = FIELD_GET(BMA400_TAP_TICSTH_MSK, reg_val); + raw = FIELD_GET(BMA400_TAP_CONFIG1_TICSTH_MASK, reg_val); vals[0] = 0; vals[1] = tap_max2min_time[raw]; @@ -302,9 +302,9 @@ static ssize_t in_accel_gesture_tap_maxtomin_time_store(struct device *dev, if (raw < 0) return -EINVAL; - ret = regmap_update_bits(data->regmap, BMA400_TAP_CONFIG1, - BMA400_TAP_TICSTH_MSK, - FIELD_PREP(BMA400_TAP_TICSTH_MSK, raw)); + ret = regmap_update_bits(data->regmap, BMA400_TAP_CONFIG1_REG, + BMA400_TAP_CONFIG1_TICSTH_MASK, + FIELD_PREP(BMA400_TAP_CONFIG1_TICSTH_MASK, raw)); if (ret) return ret; @@ -449,13 +449,13 @@ static int bma400_get_accel_reg(struct bma400_data *data, switch (chan->channel2) { case IIO_MOD_X: - lsb_reg = BMA400_X_AXIS_LSB_REG; + lsb_reg = BMA400_ACC_X_LSB_REG; break; case IIO_MOD_Y: - lsb_reg = BMA400_Y_AXIS_LSB_REG; + lsb_reg = BMA400_ACC_Y_LSB_REG; break; case IIO_MOD_Z: - lsb_reg = BMA400_Z_AXIS_LSB_REG; + lsb_reg = BMA400_ACC_Z_LSB_REG; break; default: dev_err(data->dev, "invalid axis channel modifier\n"); @@ -475,8 +475,8 @@ static int bma400_get_accel_reg(struct bma400_data *data, static void bma400_output_data_rate_from_raw(int raw, unsigned int *val, unsigned int *val2) { - *val = BMA400_ACC_ODR_MAX_HZ >> (BMA400_ACC_ODR_MAX_RAW - raw); - if (raw > BMA400_ACC_ODR_MIN_RAW) + *val = BMA400_ACC_CONFIG1_ODR_MAX_HZ >> (BMA400_ACC_CONFIG1_ODR_MAX_RAW - raw); + if (raw > BMA400_ACC_CONFIG1_ODR_MIN_RAW) *val2 = 0; else *val2 = 500000; @@ -494,7 +494,7 @@ static int bma400_get_accel_output_data_rate(struct bma400_data *data) * Runs at a fixed rate in low-power mode. See section 4.3 * in the datasheet. */ - bma400_output_data_rate_from_raw(BMA400_ACC_ODR_LP_RAW, + bma400_output_data_rate_from_raw(BMA400_ACC_CONFIG1_ODR_LP_RAW, &data->sample_freq.hz, &data->sample_freq.uhz); return 0; @@ -507,9 +507,9 @@ static int bma400_get_accel_output_data_rate(struct bma400_data *data) if (ret) goto error; - odr = val & BMA400_ACC_ODR_MASK; - if (odr < BMA400_ACC_ODR_MIN_RAW || - odr > BMA400_ACC_ODR_MAX_RAW) { + odr = val & BMA400_ACC_CONFIG1_ODR_MASK; + if (odr < BMA400_ACC_CONFIG1_ODR_MIN_RAW || + odr > BMA400_ACC_CONFIG1_ODR_MAX_RAW) { ret = -EINVAL; goto error; } @@ -539,19 +539,19 @@ static int bma400_set_accel_output_data_rate(struct bma400_data *data, unsigned int val; int ret; - if (hz >= BMA400_ACC_ODR_MIN_WHOLE_HZ) { - if (uhz || hz > BMA400_ACC_ODR_MAX_HZ) + if (hz >= BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ) { + if (uhz || hz > BMA400_ACC_CONFIG1_ODR_MAX_HZ) return -EINVAL; /* Note this works because MIN_WHOLE_HZ is odd */ idx = __ffs(hz); - if (hz >> idx != BMA400_ACC_ODR_MIN_WHOLE_HZ) + if (hz >> idx != BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ) return -EINVAL; - idx += BMA400_ACC_ODR_MIN_RAW + 1; - } else if (hz == BMA400_ACC_ODR_MIN_HZ && uhz == 500000) { - idx = BMA400_ACC_ODR_MIN_RAW; + idx += BMA400_ACC_CONFIG1_ODR_MIN_RAW + 1; + } else if (hz == BMA400_ACC_CONFIG1_ODR_MIN_HZ && uhz == 500000) { + idx = BMA400_ACC_CONFIG1_ODR_MIN_RAW; } else { return -EINVAL; } @@ -561,7 +561,7 @@ static int bma400_set_accel_output_data_rate(struct bma400_data *data, return ret; /* preserve the range and normal mode osr */ - odr = (~BMA400_ACC_ODR_MASK & val) | idx; + odr = (~BMA400_ACC_CONFIG1_ODR_MASK & val) | idx; ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, odr); if (ret) @@ -592,7 +592,7 @@ static int bma400_get_accel_oversampling_ratio(struct bma400_data *data) return ret; } - osr = (val & BMA400_LP_OSR_MASK) >> BMA400_LP_OSR_SHIFT; + osr = (val & BMA400_ACC_CONFIG0_LP_OSR_MASK) >> BMA400_LP_OSR_SHIFT; data->oversampling_ratio = osr; return 0; @@ -603,7 +603,7 @@ static int bma400_get_accel_oversampling_ratio(struct bma400_data *data) return ret; } - osr = (val & BMA400_NP_OSR_MASK) >> BMA400_NP_OSR_SHIFT; + osr = (val & BMA400_ACC_CONFIG1_NP_OSR_MASK) >> BMA400_NP_OSR_SHIFT; data->oversampling_ratio = osr; return 0; @@ -637,7 +637,7 @@ static int bma400_set_accel_oversampling_ratio(struct bma400_data *data, return ret; ret = regmap_write(data->regmap, BMA400_ACC_CONFIG0_REG, - (acc_config & ~BMA400_LP_OSR_MASK) | + (acc_config & ~BMA400_ACC_CONFIG0_LP_OSR_MASK) | (val << BMA400_LP_OSR_SHIFT)); if (ret) { dev_err(data->dev, "Failed to write out OSR\n"); @@ -653,7 +653,7 @@ static int bma400_set_accel_oversampling_ratio(struct bma400_data *data, return ret; ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, - (acc_config & ~BMA400_NP_OSR_MASK) | + (acc_config & ~BMA400_ACC_CONFIG1_NP_OSR_MASK) | (val << BMA400_NP_OSR_SHIFT)); if (ret) { dev_err(data->dev, "Failed to write out OSR\n"); @@ -679,7 +679,7 @@ static int bma400_accel_scale_to_raw(struct bma400_data *data, /* Note this works because BMA400_SCALE_MIN is odd */ raw = __ffs(val); - if (val >> raw != BMA400_SCALE_MIN) + if (val >> raw != BMA400_ACC_SCALE_MIN) return -EINVAL; return raw; @@ -695,11 +695,11 @@ static int bma400_get_accel_scale(struct bma400_data *data) if (ret) return ret; - raw_scale = (val & BMA400_ACC_SCALE_MASK) >> BMA400_SCALE_SHIFT; + raw_scale = (val & BMA400_ACC_CONFIG1_ACC_RANGE_MASK) >> BMA400_ACC_RANGE_SHIFT; if (raw_scale > BMA400_TWO_BITS_MASK) return -EINVAL; - data->scale = BMA400_SCALE_MIN << raw_scale; + data->scale = BMA400_ACC_SCALE_MIN << raw_scale; return 0; } @@ -719,8 +719,8 @@ static int bma400_set_accel_scale(struct bma400_data *data, unsigned int val) return raw; ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, - (acc_config & ~BMA400_ACC_SCALE_MASK) | - (raw << BMA400_SCALE_SHIFT)); + (acc_config & ~BMA400_ACC_CONFIG1_ACC_RANGE_MASK) | + (raw << BMA400_ACC_RANGE_SHIFT)); if (ret) return ret; @@ -786,8 +786,8 @@ static int bma400_enable_steps(struct bma400_data *data, int val) return 0; ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG1_REG, - BMA400_STEP_INT_MSK, - FIELD_PREP(BMA400_STEP_INT_MSK, val ? 1 : 0)); + BMA400_INT_CONFIG1_STEP_INT_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK, val ? 1 : 0)); if (ret) return ret; data->steps_enabled = val; @@ -826,7 +826,7 @@ static void bma400_init_tables(void) for (i = 0; i + 1 < ARRAY_SIZE(bma400_scales); i += 2) { raw = i / 2; bma400_scales[i] = 0; - bma400_scales[i + 1] = BMA400_SCALE_MIN << raw; + bma400_scales[i + 1] = BMA400_ACC_SCALE_MIN << raw; } } @@ -1063,7 +1063,7 @@ static int bma400_write_raw(struct iio_dev *indio_dev, return ret; case IIO_CHAN_INFO_SCALE: if (val != 0 || - val2 < BMA400_SCALE_MIN || val2 > BMA400_SCALE_MAX) + val2 < BMA400_ACC_SCALE_MIN || val2 > BMA400_ACC_SCALE_MAX) return -EINVAL; mutex_lock(&data->mutex); @@ -1114,16 +1114,16 @@ static int bma400_read_event_config(struct iio_dev *indio_dev, case IIO_ACCEL: switch (dir) { case IIO_EV_DIR_RISING: - return FIELD_GET(BMA400_INT_GEN1_MSK, + return FIELD_GET(BMA400_INT_CONFIG0_GEN1_MASK, data->generic_event_en); case IIO_EV_DIR_FALLING: - return FIELD_GET(BMA400_INT_GEN2_MSK, + return FIELD_GET(BMA400_INT_CONFIG0_GEN2_MASK, data->generic_event_en); case IIO_EV_DIR_SINGLETAP: - return FIELD_GET(BMA400_S_TAP_MSK, + return FIELD_GET(BMA400_INT_CONFIG1_S_TAP_MASK, data->tap_event_en_bitmask); case IIO_EV_DIR_DOUBLETAP: - return FIELD_GET(BMA400_D_TAP_MSK, + return FIELD_GET(BMA400_INT_CONFIG1_D_TAP_MASK, data->tap_event_en_bitmask); default: return -EINVAL; @@ -1146,8 +1146,8 @@ static int bma400_steps_event_enable(struct bma400_data *data, int state) return ret; ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG, - BMA400_STEP_INT_MSK, - FIELD_PREP(BMA400_STEP_INT_MSK, + BMA400_INT_CONFIG1_STEP_INT_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_STEP_INT_MASK, state)); if (ret) return ret; @@ -1164,18 +1164,18 @@ static int bma400_activity_event_en(struct bma400_data *data, switch (dir) { case IIO_EV_DIR_RISING: - reg = BMA400_GEN1INT_CONFIG0; - msk = BMA400_INT_GEN1_MSK; + reg = BMA400_GEN1INT_CONFIG0_REG; + msk = BMA400_INT_CONFIG0_GEN1_MASK; value = 2; - set_mask_bits(&field_value, BMA400_INT_GEN1_MSK, - FIELD_PREP(BMA400_INT_GEN1_MSK, state)); + set_mask_bits(&field_value, BMA400_INT_CONFIG0_GEN1_MASK, + FIELD_PREP(BMA400_INT_CONFIG0_GEN1_MASK, state)); break; case IIO_EV_DIR_FALLING: - reg = BMA400_GEN2INT_CONFIG0; - msk = BMA400_INT_GEN2_MSK; + reg = BMA400_GEN2INT_CONFIG0_REG; + msk = BMA400_INT_CONFIG0_GEN2_MASK; value = 0; - set_mask_bits(&field_value, BMA400_INT_GEN2_MSK, - FIELD_PREP(BMA400_INT_GEN2_MSK, state)); + set_mask_bits(&field_value, BMA400_INT_CONFIG0_GEN2_MASK, + FIELD_PREP(BMA400_INT_CONFIG0_GEN2_MASK, state)); break; default: return -EINVAL; @@ -1240,21 +1240,21 @@ static int bma400_tap_event_en(struct bma400_data *data, } ret = regmap_update_bits(data->regmap, BMA400_INT12_MAP_REG, - BMA400_S_TAP_MSK, - FIELD_PREP(BMA400_S_TAP_MSK, state)); + BMA400_INT_CONFIG1_S_TAP_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state)); if (ret) return ret; switch (dir) { case IIO_EV_DIR_SINGLETAP: - mask = BMA400_S_TAP_MSK; - set_mask_bits(&field_value, BMA400_S_TAP_MSK, - FIELD_PREP(BMA400_S_TAP_MSK, state)); + mask = BMA400_INT_CONFIG1_S_TAP_MASK; + set_mask_bits(&field_value, BMA400_INT_CONFIG1_S_TAP_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_S_TAP_MASK, state)); break; case IIO_EV_DIR_DOUBLETAP: - mask = BMA400_D_TAP_MSK; - set_mask_bits(&field_value, BMA400_D_TAP_MSK, - FIELD_PREP(BMA400_D_TAP_MSK, state)); + mask = BMA400_INT_CONFIG1_D_TAP_MASK; + set_mask_bits(&field_value, BMA400_INT_CONFIG1_D_TAP_MASK, + FIELD_PREP(BMA400_INT_CONFIG1_D_TAP_MASK, state)); break; default: return -EINVAL; @@ -1340,9 +1340,9 @@ static int get_gen_config_reg(enum iio_event_direction dir) { switch (dir) { case IIO_EV_DIR_FALLING: - return BMA400_GEN2INT_CONFIG0; + return BMA400_GEN2INT_CONFIG0_REG; case IIO_EV_DIR_RISING: - return BMA400_GEN1INT_CONFIG0; + return BMA400_GEN1INT_CONFIG0_REG; default: return -EINVAL; } @@ -1393,7 +1393,7 @@ static int bma400_read_event_value(struct iio_dev *indio_dev, ret = regmap_read(data->regmap, reg, val); if (ret) return ret; - *val = FIELD_GET(BMA400_GEN_HYST_MSK, *val); + *val = FIELD_GET(BMA400_GENINT_CONFIG0_HYST_MASK, *val); return IIO_VAL_INT; default: return -EINVAL; @@ -1401,30 +1401,30 @@ static int bma400_read_event_value(struct iio_dev *indio_dev, case IIO_EV_TYPE_GESTURE: switch (info) { case IIO_EV_INFO_VALUE: - ret = regmap_read(data->regmap, BMA400_TAP_CONFIG, + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG_REG, ®_val); if (ret) return ret; - *val = FIELD_GET(BMA400_TAP_SEN_MSK, reg_val); + *val = FIELD_GET(BMA400_TAP_CONFIG_SEN_MASK, reg_val); return IIO_VAL_INT; case IIO_EV_INFO_RESET_TIMEOUT: - ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1, + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, ®_val); if (ret) return ret; - raw = FIELD_GET(BMA400_TAP_QUIET_MSK, reg_val); + raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIET_MASK, reg_val); *val = 0; *val2 = tap_reset_timeout[raw]; return IIO_VAL_INT_PLUS_MICRO; case IIO_EV_INFO_TAP2_MIN_DELAY: - ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1, + ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, ®_val); if (ret) return ret; - raw = FIELD_GET(BMA400_TAP_QUIETDT_MSK, reg_val); + raw = FIELD_GET(BMA400_TAP_CONFIG1_QUIETDT_MASK, reg_val); *val = 0; *val2 = double_tap2_min_delay[raw]; return IIO_VAL_INT_PLUS_MICRO; @@ -1480,8 +1480,8 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, return -EINVAL; return regmap_update_bits(data->regmap, reg, - BMA400_GEN_HYST_MSK, - FIELD_PREP(BMA400_GEN_HYST_MSK, + BMA400_GENINT_CONFIG0_HYST_MASK, + FIELD_PREP(BMA400_GENINT_CONFIG0_HYST_MASK, val)); default: return -EINVAL; @@ -1493,9 +1493,9 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, return -EINVAL; return regmap_update_bits(data->regmap, - BMA400_TAP_CONFIG, - BMA400_TAP_SEN_MSK, - FIELD_PREP(BMA400_TAP_SEN_MSK, + BMA400_TAP_CONFIG_REG, + BMA400_TAP_CONFIG_SEN_MASK, + FIELD_PREP(BMA400_TAP_CONFIG_SEN_MASK, val)); case IIO_EV_INFO_RESET_TIMEOUT: raw = usec_to_tapreg_raw(val2, tap_reset_timeout); @@ -1503,9 +1503,9 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, return -EINVAL; return regmap_update_bits(data->regmap, - BMA400_TAP_CONFIG1, - BMA400_TAP_QUIET_MSK, - FIELD_PREP(BMA400_TAP_QUIET_MSK, + BMA400_TAP_CONFIG1_REG, + BMA400_TAP_CONFIG1_QUIET_MASK, + FIELD_PREP(BMA400_TAP_CONFIG1_QUIET_MASK, raw)); case IIO_EV_INFO_TAP2_MIN_DELAY: raw = usec_to_tapreg_raw(val2, double_tap2_min_delay); @@ -1513,9 +1513,9 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, return -EINVAL; return regmap_update_bits(data->regmap, - BMA400_TAP_CONFIG1, - BMA400_TAP_QUIETDT_MSK, - FIELD_PREP(BMA400_TAP_QUIETDT_MSK, + BMA400_TAP_CONFIG1_REG, + BMA400_TAP_CONFIG1_QUIETDT_MASK, + FIELD_PREP(BMA400_TAP_CONFIG1_QUIETDT_MASK, raw)); default: return -EINVAL; @@ -1533,14 +1533,14 @@ static int bma400_data_rdy_trigger_set_state(struct iio_trigger *trig, int ret; ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, - BMA400_INT_DRDY_MSK, - FIELD_PREP(BMA400_INT_DRDY_MSK, state)); + BMA400_INT_CONFIG0_DRDY_MASK, + FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state)); if (ret) return ret; return regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, - BMA400_INT_DRDY_MSK, - FIELD_PREP(BMA400_INT_DRDY_MSK, state)); + BMA400_INT_CONFIG0_DRDY_MASK, + FIELD_PREP(BMA400_INT_CONFIG0_DRDY_MASK, state)); } static const unsigned long bma400_avail_scan_masks[] = { @@ -1578,7 +1578,7 @@ static irqreturn_t bma400_trigger_handler(int irq, void *p) mutex_lock(&data->mutex); /* bulk read six registers, with the base being the LSB register */ - ret = regmap_bulk_read(data->regmap, BMA400_X_AXIS_LSB_REG, + ret = regmap_bulk_read(data->regmap, BMA400_ACC_X_LSB_REG, &data->buffer.buff, sizeof(data->buffer.buff)); if (ret) goto unlock_err; @@ -1628,13 +1628,13 @@ static irqreturn_t bma400_interrupt(int irq, void *private) * Disable all advance interrupts if interrupt engine overrun occurs. * See section 4.7 "Interrupt engine overrun" in datasheet v1.2. */ - if (FIELD_GET(BMA400_INT_ENG_OVRUN_MSK, le16_to_cpu(data->status))) { + if (FIELD_GET(BMA400_INT_STAT_ENG_OVRRUN_MASK, le16_to_cpu(data->status))) { bma400_disable_adv_interrupt(data); dev_err(data->dev, "Interrupt engine overrun\n"); goto unlock_err; } - if (FIELD_GET(BMA400_INT_S_TAP_MSK, le16_to_cpu(data->status))) + if (FIELD_GET(BMA400_INT_STAT1_S_TAP_MASK, le16_to_cpu(data->status))) iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, @@ -1642,7 +1642,7 @@ static irqreturn_t bma400_interrupt(int irq, void *private) IIO_EV_DIR_SINGLETAP), timestamp); - if (FIELD_GET(BMA400_INT_D_TAP_MSK, le16_to_cpu(data->status))) + if (FIELD_GET(BMA400_INT_STAT1_D_TAP_MASK, le16_to_cpu(data->status))) iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X_OR_Y_OR_Z, @@ -1650,10 +1650,10 @@ static irqreturn_t bma400_interrupt(int irq, void *private) IIO_EV_DIR_DOUBLETAP), timestamp); - if (FIELD_GET(BMA400_INT_GEN1_MSK, le16_to_cpu(data->status))) + if (FIELD_GET(BMA400_INT_STAT0_GEN1_MASK, le16_to_cpu(data->status))) ev_dir = IIO_EV_DIR_RISING; - if (FIELD_GET(BMA400_INT_GEN2_MSK, le16_to_cpu(data->status))) + if (FIELD_GET(BMA400_INT_STAT0_GEN2_MASK, le16_to_cpu(data->status))) ev_dir = IIO_EV_DIR_FALLING; if (ev_dir != IIO_EV_DIR_NONE) { @@ -1664,7 +1664,7 @@ static irqreturn_t bma400_interrupt(int irq, void *private) timestamp); } - if (FIELD_GET(BMA400_STEP_STAT_MASK, le16_to_cpu(data->status))) { + if (FIELD_GET(BMA400_INT_STAT1_STEP_INT_MASK, le16_to_cpu(data->status))) { iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_STEPS, 0, IIO_NO_MOD, IIO_EV_TYPE_CHANGE, @@ -1686,7 +1686,7 @@ static irqreturn_t bma400_interrupt(int irq, void *private) } } - if (FIELD_GET(BMA400_INT_DRDY_MSK, le16_to_cpu(data->status))) { + if (FIELD_GET(BMA400_INT_STAT0_DRDY_MASK, le16_to_cpu(data->status))) { mutex_unlock(&data->mutex); iio_trigger_poll_nested(data->trig); return IRQ_HANDLED; From a2ef0af1923ba3fa0b46adfd8fb01d5920906980 Mon Sep 17 00:00:00 2001 From: Akshay Jindal Date: Sun, 12 Oct 2025 23:36:09 +0530 Subject: [PATCH 082/150] iio: accel: bma400: Use macros for generic event configuration values Add macros and enums for configuration values used in generic event handling for activity and inactivity detection. Replace hard-coded values in activity_event_en() with the new definitions to make the configuration explicit. No functional changes are intended. Signed-off-by: Akshay Jindal Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma400.h | 30 ++++++++++++++++++++++++++++++ drivers/iio/accel/bma400_core.c | 14 +++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h index fcafd1fba57a..12e7bf5fe647 100644 --- a/drivers/iio/accel/bma400.h +++ b/drivers/iio/accel/bma400.h @@ -113,8 +113,38 @@ #define BMA400_GEN1INT_CONFIG0_REG 0x3f #define BMA400_GEN2INT_CONFIG0_REG 0x4A #define BMA400_GENINT_CONFIG0_HYST_MASK GENMASK(1, 0) +#define BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK GENMASK(3, 2) +#define BMA400_GENINT_CONFIG0_DATA_SRC_MASK BIT(4) +#define BMA400_GENINT_CONFIG0_X_EN_MASK BIT(5) +#define BMA400_GENINT_CONFIG0_Y_EN_MASK BIT(6) +#define BMA400_GENINT_CONFIG0_Z_EN_MASK BIT(7) + +enum bma400_accel_data_src { + ACCEL_FILT1 = 0x0, + ACCEL_FILT2 = 0x1, +}; + +enum bma400_ref_updt_mode { + BMA400_REF_MANUAL_UPDT_MODE = 0x0, + BMA400_REF_ONETIME_UPDT_MODE = 0x1, + BMA400_REF_EVERYTIME_UPDT_MODE = 0x2, + BMA400_REF_EVERYTIME_LP_UPDT_MODE = 0x3, +}; #define BMA400_GEN_CONFIG1_OFF 0x01 +#define BMA400_GENINT_CONFIG1_AXES_COMB_MASK BIT(0) +#define BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK BIT(1) + +enum bma400_genintr_acceleval_axescomb { + BMA400_EVAL_X_OR_Y_OR_Z = 0x0, + BMA400_EVAL_X_AND_Y_AND_Z = 0x1, +}; + +enum bma400_detect_criterion { + BMA400_DETECT_INACTIVITY = 0x0, + BMA400_DETECT_ACTIVITY = 0x1, +}; + #define BMA400_GEN_CONFIG2_OFF 0x02 #define BMA400_GEN_CONFIG3_OFF 0x03 #define BMA400_GEN_CONFIG31_OFF 0x04 diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 2324c4ef645c..46855073ce3a 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -1166,14 +1166,16 @@ static int bma400_activity_event_en(struct bma400_data *data, case IIO_EV_DIR_RISING: reg = BMA400_GEN1INT_CONFIG0_REG; msk = BMA400_INT_CONFIG0_GEN1_MASK; - value = 2; + value = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) | + FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, BMA400_DETECT_ACTIVITY); set_mask_bits(&field_value, BMA400_INT_CONFIG0_GEN1_MASK, FIELD_PREP(BMA400_INT_CONFIG0_GEN1_MASK, state)); break; case IIO_EV_DIR_FALLING: reg = BMA400_GEN2INT_CONFIG0_REG; msk = BMA400_INT_CONFIG0_GEN2_MASK; - value = 0; + value = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) | + FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, BMA400_DETECT_INACTIVITY); set_mask_bits(&field_value, BMA400_INT_CONFIG0_GEN2_MASK, FIELD_PREP(BMA400_INT_CONFIG0_GEN2_MASK, state)); break; @@ -1182,7 +1184,13 @@ static int bma400_activity_event_en(struct bma400_data *data, } /* Enabling all axis for interrupt evaluation */ - ret = regmap_write(data->regmap, reg, 0xF8); + ret = regmap_write(data->regmap, reg, + BMA400_GENINT_CONFIG0_X_EN_MASK | + BMA400_GENINT_CONFIG0_Y_EN_MASK | + BMA400_GENINT_CONFIG0_Z_EN_MASK| + FIELD_PREP(BMA400_GENINT_CONFIG0_DATA_SRC_MASK, ACCEL_FILT2)| + FIELD_PREP(BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK, + BMA400_REF_EVERYTIME_UPDT_MODE)); if (ret) return ret; From e03d213848b045aa370af57da559b49b5ab3c966 Mon Sep 17 00:00:00 2001 From: Akshay Jindal Date: Sun, 12 Oct 2025 23:36:10 +0530 Subject: [PATCH 083/150] iio: accel: bma400: Use index-based register addressing and lookup Introduce formula-based macros to compute GEN INTR configuration register addresses from the interrupt number and register index. This reduces the need for 22 explicit register macros to three base definitions. Add a centralized lookup table keyed by IIO event direction and replace get_gen_config_reg() with a helper integrated with this table. Apply these changes across the affected callbacks to ensure consistent access to generic interrupt registers. No functional changes are intended. Signed-off-by: Akshay Jindal Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma400.h | 17 +++-- drivers/iio/accel/bma400_core.c | 131 +++++++++++++++++++------------- 2 files changed, 88 insertions(+), 60 deletions(-) diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h index 12e7bf5fe647..e2832d33862d 100644 --- a/drivers/iio/accel/bma400.h +++ b/drivers/iio/accel/bma400.h @@ -98,6 +98,11 @@ #define BMA400_INT_CONFIG0_GEN2_MASK BIT(3) #define BMA400_INT_CONFIG0_DRDY_MASK BIT(7) +enum bma400_generic_intr { + BMA400_GEN1_INTR = 0x1, + BMA400_GEN2_INTR = 0x2, +}; + #define BMA400_INT_CONFIG1_REG 0x20 #define BMA400_INT_CONFIG1_STEP_INT_MASK BIT(0) #define BMA400_INT_CONFIG1_S_TAP_MASK BIT(2) @@ -110,8 +115,12 @@ #define BMA400_TWO_BITS_MASK GENMASK(1, 0) /* Generic interrupts register */ -#define BMA400_GEN1INT_CONFIG0_REG 0x3f -#define BMA400_GEN2INT_CONFIG0_REG 0x4A +#define BMA400_GENINT_CONFIG_REG_BASE 0x3f +#define BMA400_NUM_GENINT_CONFIG_REGS 11 +#define BMA400_GENINT_CONFIG_REG(gen_intr, config_idx) \ + (BMA400_GENINT_CONFIG_REG_BASE + \ + (gen_intr - 1) * BMA400_NUM_GENINT_CONFIG_REGS + \ + (config_idx)) #define BMA400_GENINT_CONFIG0_HYST_MASK GENMASK(1, 0) #define BMA400_GENINT_CONFIG0_REF_UPD_MODE_MASK GENMASK(3, 2) #define BMA400_GENINT_CONFIG0_DATA_SRC_MASK BIT(4) @@ -145,10 +154,6 @@ enum bma400_detect_criterion { BMA400_DETECT_ACTIVITY = 0x1, }; -#define BMA400_GEN_CONFIG2_OFF 0x02 -#define BMA400_GEN_CONFIG3_OFF 0x03 -#define BMA400_GEN_CONFIG31_OFF 0x04 - /* TAP config registers */ #define BMA400_TAP_CONFIG_REG 0x57 #define BMA400_TAP_CONFIG_SEN_MASK GENMASK(2, 0) diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 46855073ce3a..42cd55fa1609 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -121,6 +121,41 @@ struct bma400_data { __be16 duration; }; +struct bma400_genintr_info { + enum bma400_generic_intr genintr; + unsigned int intrmask; + enum iio_event_direction dir; + enum bma400_detect_criterion detect_mode; +}; + +/* Lookup struct for determining GEN1/GEN2 based on dir */ +static const struct bma400_genintr_info bma400_genintrs[] = { + [IIO_EV_DIR_RISING] = { + .genintr = BMA400_GEN1_INTR, + .intrmask = BMA400_INT_CONFIG0_GEN1_MASK, + .dir = IIO_EV_DIR_RISING, + .detect_mode = BMA400_DETECT_ACTIVITY, + }, + [IIO_EV_DIR_FALLING] = { + .genintr = BMA400_GEN2_INTR, + .intrmask = BMA400_INT_CONFIG0_GEN2_MASK, + .dir = IIO_EV_DIR_FALLING, + .detect_mode = BMA400_DETECT_INACTIVITY, + } +}; + +static inline const struct bma400_genintr_info * +get_bma400_genintr_info(enum iio_event_direction dir) +{ + switch (dir) { + case IIO_EV_DIR_RISING: + case IIO_EV_DIR_FALLING: + return &bma400_genintrs[dir]; + default: + return NULL; + }; +} + static bool bma400_is_writable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -1159,32 +1194,22 @@ static int bma400_activity_event_en(struct bma400_data *data, enum iio_event_direction dir, int state) { - int ret, reg, msk, value; - int field_value = 0; + int ret; + unsigned int intrmask, regval; + enum bma400_generic_intr genintr; + enum bma400_detect_criterion detect_criterion; + const struct bma400_genintr_info *bma400_genintr; - switch (dir) { - case IIO_EV_DIR_RISING: - reg = BMA400_GEN1INT_CONFIG0_REG; - msk = BMA400_INT_CONFIG0_GEN1_MASK; - value = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) | - FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, BMA400_DETECT_ACTIVITY); - set_mask_bits(&field_value, BMA400_INT_CONFIG0_GEN1_MASK, - FIELD_PREP(BMA400_INT_CONFIG0_GEN1_MASK, state)); - break; - case IIO_EV_DIR_FALLING: - reg = BMA400_GEN2INT_CONFIG0_REG; - msk = BMA400_INT_CONFIG0_GEN2_MASK; - value = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) | - FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, BMA400_DETECT_INACTIVITY); - set_mask_bits(&field_value, BMA400_INT_CONFIG0_GEN2_MASK, - FIELD_PREP(BMA400_INT_CONFIG0_GEN2_MASK, state)); - break; - default: + bma400_genintr = get_bma400_genintr_info(dir); + if (!bma400_genintr) return -EINVAL; - } + + genintr = bma400_genintr->genintr; + detect_criterion = bma400_genintr->detect_mode; + intrmask = bma400_genintr->intrmask; /* Enabling all axis for interrupt evaluation */ - ret = regmap_write(data->regmap, reg, + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 0), BMA400_GENINT_CONFIG0_X_EN_MASK | BMA400_GENINT_CONFIG0_Y_EN_MASK | BMA400_GENINT_CONFIG0_Z_EN_MASK| @@ -1195,31 +1220,32 @@ static int bma400_activity_event_en(struct bma400_data *data, return ret; /* OR combination of all axis for interrupt evaluation */ - ret = regmap_write(data->regmap, reg + BMA400_GEN_CONFIG1_OFF, value); + regval = FIELD_PREP(BMA400_GENINT_CONFIG1_AXES_COMB_MASK, BMA400_EVAL_X_OR_Y_OR_Z) | + FIELD_PREP(BMA400_GENINT_CONFIG1_DETCT_CRIT_MASK, detect_criterion); + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 1), regval); if (ret) return ret; /* Initial value to avoid interrupts while enabling*/ - ret = regmap_write(data->regmap, reg + BMA400_GEN_CONFIG2_OFF, 0x0A); + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 2), 0x0A); if (ret) return ret; /* Initial duration value to avoid interrupts while enabling*/ - ret = regmap_write(data->regmap, reg + BMA400_GEN_CONFIG31_OFF, 0x0F); + ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 4), 0x0F); if (ret) return ret; - ret = regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, msk, - field_value); + regval = state ? intrmask : 0; + ret = regmap_update_bits(data->regmap, BMA400_INT1_MAP_REG, intrmask, regval); if (ret) return ret; - ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, msk, - field_value); + ret = regmap_update_bits(data->regmap, BMA400_INT_CONFIG0_REG, intrmask, regval); if (ret) return ret; - set_mask_bits(&data->generic_event_en, msk, field_value); + set_mask_bits(&data->generic_event_en, intrmask, regval); return 0; } @@ -1344,18 +1370,6 @@ static int bma400_write_event_config(struct iio_dev *indio_dev, } } -static int get_gen_config_reg(enum iio_event_direction dir) -{ - switch (dir) { - case IIO_EV_DIR_FALLING: - return BMA400_GEN2INT_CONFIG0_REG; - case IIO_EV_DIR_RISING: - return BMA400_GEN1INT_CONFIG0_REG; - default: - return -EINVAL; - } -} - static int bma400_read_event_value(struct iio_dev *indio_dev, const struct iio_chan_spec *chan, enum iio_event_type type, @@ -1364,22 +1378,25 @@ static int bma400_read_event_value(struct iio_dev *indio_dev, int *val, int *val2) { struct bma400_data *data = iio_priv(indio_dev); - int ret, reg, reg_val, raw; + int ret, reg_val, raw; + enum bma400_generic_intr genintr; + const struct bma400_genintr_info *bma400_genintr; if (chan->type != IIO_ACCEL) return -EINVAL; switch (type) { case IIO_EV_TYPE_MAG: - reg = get_gen_config_reg(dir); - if (reg < 0) + bma400_genintr = get_bma400_genintr_info(dir); + if (!bma400_genintr) return -EINVAL; + genintr = bma400_genintr->genintr; *val2 = 0; switch (info) { case IIO_EV_INFO_VALUE: ret = regmap_read(data->regmap, - reg + BMA400_GEN_CONFIG2_OFF, + BMA400_GENINT_CONFIG_REG(genintr, 2), val); if (ret) return ret; @@ -1387,7 +1404,7 @@ static int bma400_read_event_value(struct iio_dev *indio_dev, case IIO_EV_INFO_PERIOD: mutex_lock(&data->mutex); ret = regmap_bulk_read(data->regmap, - reg + BMA400_GEN_CONFIG3_OFF, + BMA400_GENINT_CONFIG_REG(genintr, 3), &data->duration, sizeof(data->duration)); if (ret) { @@ -1398,7 +1415,9 @@ static int bma400_read_event_value(struct iio_dev *indio_dev, mutex_unlock(&data->mutex); return IIO_VAL_INT; case IIO_EV_INFO_HYSTERESIS: - ret = regmap_read(data->regmap, reg, val); + ret = regmap_read(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 0), + val); if (ret) return ret; *val = FIELD_GET(BMA400_GENINT_CONFIG0_HYST_MASK, *val); @@ -1452,16 +1471,19 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, int val, int val2) { struct bma400_data *data = iio_priv(indio_dev); - int reg, ret, raw; + int ret, raw; + enum bma400_generic_intr genintr; + const struct bma400_genintr_info *bma400_genintr; if (chan->type != IIO_ACCEL) return -EINVAL; switch (type) { case IIO_EV_TYPE_MAG: - reg = get_gen_config_reg(dir); - if (reg < 0) + bma400_genintr = get_bma400_genintr_info(dir); + if (!bma400_genintr) return -EINVAL; + genintr = bma400_genintr->genintr; switch (info) { case IIO_EV_INFO_VALUE: @@ -1469,7 +1491,7 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, return -EINVAL; return regmap_write(data->regmap, - reg + BMA400_GEN_CONFIG2_OFF, + BMA400_GENINT_CONFIG_REG(genintr, 2), val); case IIO_EV_INFO_PERIOD: if (val < 1 || val > 65535) @@ -1478,7 +1500,7 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, mutex_lock(&data->mutex); put_unaligned_be16(val, &data->duration); ret = regmap_bulk_write(data->regmap, - reg + BMA400_GEN_CONFIG3_OFF, + BMA400_GENINT_CONFIG_REG(genintr, 3), &data->duration, sizeof(data->duration)); mutex_unlock(&data->mutex); @@ -1487,7 +1509,8 @@ static int bma400_write_event_value(struct iio_dev *indio_dev, if (val < 0 || val > 3) return -EINVAL; - return regmap_update_bits(data->regmap, reg, + return regmap_update_bits(data->regmap, + BMA400_GENINT_CONFIG_REG(genintr, 0), BMA400_GENINT_CONFIG0_HYST_MASK, FIELD_PREP(BMA400_GENINT_CONFIG0_HYST_MASK, val)); From 1a7a6c5db6366c29f687796c75ef5bd00e4921a2 Mon Sep 17 00:00:00 2001 From: Akshay Jindal Date: Sun, 12 Oct 2025 23:36:11 +0530 Subject: [PATCH 084/150] iio: accel: bma400: Replace bit shifts with FIELD_PREP() and FIELD_GET() set_* functions involve left shift of param values into respective register fields before writing to register. Similarly get_* functions involve right shift to extract values from the respective bit fields. Replace these explicit shifting statements with standard kernel style macros FIELD_GET() and FIELD_PREP(). Signed-off-by: Akshay Jindal Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma400.h | 3 --- drivers/iio/accel/bma400_core.c | 12 ++++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h index e2832d33862d..b5f3cac51610 100644 --- a/drivers/iio/accel/bma400.h +++ b/drivers/iio/accel/bma400.h @@ -75,7 +75,6 @@ */ #define BMA400_ACC_CONFIG0_REG 0x19 #define BMA400_ACC_CONFIG0_LP_OSR_MASK GENMASK(6, 5) -#define BMA400_LP_OSR_SHIFT 5 #define BMA400_ACC_CONFIG1_REG 0x1a #define BMA400_ACC_CONFIG1_ODR_MASK GENMASK(3, 0) @@ -86,9 +85,7 @@ #define BMA400_ACC_CONFIG1_ODR_MIN_WHOLE_HZ 25 #define BMA400_ACC_CONFIG1_ODR_MIN_HZ 12 #define BMA400_ACC_CONFIG1_NP_OSR_MASK GENMASK(5, 4) -#define BMA400_NP_OSR_SHIFT 4 #define BMA400_ACC_CONFIG1_ACC_RANGE_MASK GENMASK(7, 6) -#define BMA400_ACC_RANGE_SHIFT 6 #define BMA400_ACC_CONFIG2_REG 0x1b diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 42cd55fa1609..cec59d409531 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -627,7 +627,7 @@ static int bma400_get_accel_oversampling_ratio(struct bma400_data *data) return ret; } - osr = (val & BMA400_ACC_CONFIG0_LP_OSR_MASK) >> BMA400_LP_OSR_SHIFT; + osr = FIELD_GET(BMA400_ACC_CONFIG0_LP_OSR_MASK, val); data->oversampling_ratio = osr; return 0; @@ -638,7 +638,7 @@ static int bma400_get_accel_oversampling_ratio(struct bma400_data *data) return ret; } - osr = (val & BMA400_ACC_CONFIG1_NP_OSR_MASK) >> BMA400_NP_OSR_SHIFT; + osr = FIELD_GET(BMA400_ACC_CONFIG1_NP_OSR_MASK, val); data->oversampling_ratio = osr; return 0; @@ -673,7 +673,7 @@ static int bma400_set_accel_oversampling_ratio(struct bma400_data *data, ret = regmap_write(data->regmap, BMA400_ACC_CONFIG0_REG, (acc_config & ~BMA400_ACC_CONFIG0_LP_OSR_MASK) | - (val << BMA400_LP_OSR_SHIFT)); + FIELD_PREP(BMA400_ACC_CONFIG0_LP_OSR_MASK, val)); if (ret) { dev_err(data->dev, "Failed to write out OSR\n"); return ret; @@ -689,7 +689,7 @@ static int bma400_set_accel_oversampling_ratio(struct bma400_data *data, ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, (acc_config & ~BMA400_ACC_CONFIG1_NP_OSR_MASK) | - (val << BMA400_NP_OSR_SHIFT)); + FIELD_PREP(BMA400_ACC_CONFIG1_NP_OSR_MASK, val)); if (ret) { dev_err(data->dev, "Failed to write out OSR\n"); return ret; @@ -730,7 +730,7 @@ static int bma400_get_accel_scale(struct bma400_data *data) if (ret) return ret; - raw_scale = (val & BMA400_ACC_CONFIG1_ACC_RANGE_MASK) >> BMA400_ACC_RANGE_SHIFT; + raw_scale = FIELD_GET(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, val); if (raw_scale > BMA400_TWO_BITS_MASK) return -EINVAL; @@ -755,7 +755,7 @@ static int bma400_set_accel_scale(struct bma400_data *data, unsigned int val) ret = regmap_write(data->regmap, BMA400_ACC_CONFIG1_REG, (acc_config & ~BMA400_ACC_CONFIG1_ACC_RANGE_MASK) | - (raw << BMA400_ACC_RANGE_SHIFT)); + FIELD_PREP(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, raw)); if (ret) return ret; From 31f3af6283a0810c1a51d56eaf3c5936df718f84 Mon Sep 17 00:00:00 2001 From: Akshay Jindal Date: Sun, 12 Oct 2025 23:36:12 +0530 Subject: [PATCH 085/150] iio: accel: bma400: Rename activity_event_en() to generic_event_en() The function activity_event_en() configures the generic interrupts GEN1 and GEN2, which are used for activity and inactivity detection as per the datasheet. The existing name is misleading, since the device also provides activity change and activity recognition interrupts. Activity change interrupt is not supported yet whereas Activity recognition interrupt is configured in a different function. Rename activity_event_en() to generic_event_en() to better reflect its actual purpose. No functional changes intended. Signed-off-by: Akshay Jindal Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma400_core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index cec59d409531..840c4156ba60 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -1190,9 +1190,9 @@ static int bma400_steps_event_enable(struct bma400_data *data, int state) return 0; } -static int bma400_activity_event_en(struct bma400_data *data, - enum iio_event_direction dir, - int state) +static int bma400_generic_event_en(struct bma400_data *data, + enum iio_event_direction dir, + int state) { int ret; unsigned int intrmask, regval; @@ -1337,7 +1337,7 @@ static int bma400_write_event_config(struct iio_dev *indio_dev, switch (type) { case IIO_EV_TYPE_MAG: mutex_lock(&data->mutex); - ret = bma400_activity_event_en(data, dir, state); + ret = bma400_generic_event_en(data, dir, state); mutex_unlock(&data->mutex); return ret; case IIO_EV_TYPE_GESTURE: From 6ea3b542646f45a4b279228989348a8dc229f53c Mon Sep 17 00:00:00 2001 From: Akshay Jindal Date: Sun, 12 Oct 2025 23:36:13 +0530 Subject: [PATCH 086/150] iio: accel: bma400: Add detail to comments in GEN INTR configuration Append additional information to existing comments in the generic interrupt configuration code to provide more context. Signed-off-by: Akshay Jindal Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma400_core.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/iio/accel/bma400_core.c b/drivers/iio/accel/bma400_core.c index 840c4156ba60..05f72707f830 100644 --- a/drivers/iio/accel/bma400_core.c +++ b/drivers/iio/accel/bma400_core.c @@ -1208,7 +1208,10 @@ static int bma400_generic_event_en(struct bma400_data *data, detect_criterion = bma400_genintr->detect_mode; intrmask = bma400_genintr->intrmask; - /* Enabling all axis for interrupt evaluation */ + /* + * Enabling all axis for interrupt evaluation + * Acc_filt2 is recommended as data source in datasheet (Section 4.7) + */ ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 0), BMA400_GENINT_CONFIG0_X_EN_MASK | BMA400_GENINT_CONFIG0_Y_EN_MASK | @@ -1226,7 +1229,10 @@ static int bma400_generic_event_en(struct bma400_data *data, if (ret) return ret; - /* Initial value to avoid interrupts while enabling*/ + /* + * Initial value to avoid interrupts while enabling + * Value is in units of 8mg/lsb, i.e. effective val is val * 8mg/lsb + */ ret = regmap_write(data->regmap, BMA400_GENINT_CONFIG_REG(genintr, 2), 0x0A); if (ret) return ret; From c9fb952360d0c78bbe98239bd6b702f05c2dbb31 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Tue, 14 Oct 2025 17:12:50 +0800 Subject: [PATCH 087/150] iio: adc: ti_am335x_adc: Limit step_avg to valid range for gcc complains FIELD_PREP() checks that a value fits into the available bitfield, add a check for step_avg to fix gcc complains. which gcc complains about: drivers/iio/adc/ti_am335x_adc.c: In function 'tiadc_step_config': include/linux/compiler_types.h:572:38: error: call to '__compiletime_assert_491' declared with attribute error: FIELD_PREP: value too large for the field include/linux/mfd/ti_am335x_tscadc.h:58:29: note: in expansion of macro 'FIELD_PREP' #define STEPCONFIG_AVG(val) FIELD_PREP(GENMASK(4, 2), (val)) ^~~~~~~~~~ drivers/iio/adc/ti_am335x_adc.c:127:17: note: in expansion of macro 'STEPCONFIG_AVG' stepconfig = STEPCONFIG_AVG(ffs(adc_dev->step_avg[i]) - 1) Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510102117.Jqxrw1vF-lkp@intel.com/ Signed-off-by: Pei Xiao Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ti_am335x_adc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ti_am335x_adc.c b/drivers/iio/adc/ti_am335x_adc.c index 99f274adc870..a1a28584de93 100644 --- a/drivers/iio/adc/ti_am335x_adc.c +++ b/drivers/iio/adc/ti_am335x_adc.c @@ -123,7 +123,7 @@ static void tiadc_step_config(struct iio_dev *indio_dev) chan = adc_dev->channel_line[i]; - if (adc_dev->step_avg[i]) + if (adc_dev->step_avg[i] && adc_dev->step_avg[i] <= STEPCONFIG_AVG_16) stepconfig = STEPCONFIG_AVG(ffs(adc_dev->step_avg[i]) - 1) | STEPCONFIG_FIFO1; else From d8cfb1c6494d0dc07cc960dd6511dbda524698d0 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Tue, 14 Oct 2025 19:42:57 +0300 Subject: [PATCH 088/150] iio: accel: bma220: white space cleanup Clean up white space inconsistencies from the last patch series as requested by Jonathan. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_core.c | 2 +- drivers/iio/accel/bma220_spi.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 2531d6a54ff0..871342d21456 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -128,7 +128,7 @@ enum bma220_axis { }; static const int bma220_scale_table[][2] = { - {0, 623000}, {1, 248000}, {2, 491000}, {4, 983000}, + { 0, 623000 }, { 1, 248000 }, { 2, 491000 }, { 4, 983000 }, }; struct bma220_data { diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c index 7aced4017373..383ee8a135ee 100644 --- a/drivers/iio/accel/bma220_spi.c +++ b/drivers/iio/accel/bma220_spi.c @@ -26,12 +26,12 @@ static int bma220_spi_probe(struct spi_device *spi) } static const struct spi_device_id bma220_spi_id[] = { - {"bma220", 0}, + { "bma220", 0 }, { } }; static const struct acpi_device_id bma220_acpi_id[] = { - {"BMA0220", 0}, + { "BMA0220", 0 }, { } }; MODULE_DEVICE_TABLE(spi, bma220_spi_id); From 003930040784b30902870a945ed9503f9cdc2327 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Tue, 14 Oct 2025 19:42:58 +0300 Subject: [PATCH 089/150] iio: accel: bma220: remove useless include Remove errno.h include from bma220_i2c.c since error codes are generated within bma220_core.c instead. Signed-off-by: Petre Rodan Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220_i2c.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/iio/accel/bma220_i2c.c b/drivers/iio/accel/bma220_i2c.c index 5dc7c38f53b3..2b85d4921768 100644 --- a/drivers/iio/accel/bma220_i2c.c +++ b/drivers/iio/accel/bma220_i2c.c @@ -8,7 +8,6 @@ * I2C address is either 0x0b or 0x0a depending on CSB (pin 10) */ -#include #include #include #include From c6d702f2b77194b62fb2098c63bb7f2a87da142d Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 17 Oct 2025 18:42:54 +0200 Subject: [PATCH 090/150] iio: imu: st_lsm6dsx: Fix measurement unit for odr struct member The `odr` field in struct st_lsm6dsx_sensor contains a data rate value expressed in mHz, not in Hz. Fixes: f8710f0357bc3 ("iio: imu: st_lsm6dsx: express odr in mHZ") Signed-off-by: Francesco Lavra Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index c225b246c8a5..bd366c6e282a 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -365,7 +365,7 @@ enum st_lsm6dsx_fifo_mode { * @id: Sensor identifier. * @hw: Pointer to instance of struct st_lsm6dsx_hw. * @gain: Configured sensor sensitivity. - * @odr: Output data rate of the sensor [Hz]. + * @odr: Output data rate of the sensor [mHz]. * @samples_to_discard: Number of samples to discard for filters settling time. * @watermark: Sensor watermark level. * @decimator: Sensor decimation factor. From 6b648a36c200dfd3e776e657a4c8a8ce63fb52d3 Mon Sep 17 00:00:00 2001 From: Francesco Lavra Date: Fri, 17 Oct 2025 18:42:55 +0200 Subject: [PATCH 091/150] iio: imu: st_lsm6dsx: Decouple sensor ODR from FIFO batch data rate The rate at which accelerometer or gyroscope sensor samples are fed to the hardware FIFO (batch data rate, or BDR) does not have to coincide with the sensor sampling frequency (output data rate, or ODR); the only requirement is for the BDR to not be greater than the ODR. Having a BDR lower than the ODR is useful in cases where an application requires a high sampling rate for accurate detection of motion events (e.g. wakeup events), but wants to read sensor sample values from the hardware FIFO at a lower data rate (e.g. to minimize the amount of I2C or SPI traffic and the rate of periodic interrupts). To support the above use case, add a sampling_frequency sysfs attribute to the buffer directory of st_lsm6dsx IIO devices, which controls the BDR for a given sensor independently from the "main" sampling_frequency attribute (which controls the ODR); introduce a new `hwfifo_odr_mHz` field in struct st_lsm6dsx_sensor to keep track of the current BDR value, and use this field instead of the `odr` field in the code that deals with the FIFO data rate. In the sensor hub driver, make the hwfifo_odr_mHz value always mirror the odr value, since there is no separate configuration setting to control the BDR for data produced by the sensor hub functionality. For backwards compatibility, set the buffer frequency equal to the main frequency whenever the latter is updated via sysfs; if userspace wants a different buffer frequency, it has to write to the relevant sysfs attribute after any writes to the main frequency attribute. Signed-off-by: Francesco Lavra Acked-by: Lorenzo Bianconi Signed-off-by: Jonathan Cameron --- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h | 2 + .../iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c | 71 ++++++++++++++++--- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c | 7 +- drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c | 2 + 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h index bd366c6e282a..3cd520bdec46 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h @@ -366,6 +366,7 @@ enum st_lsm6dsx_fifo_mode { * @hw: Pointer to instance of struct st_lsm6dsx_hw. * @gain: Configured sensor sensitivity. * @odr: Output data rate of the sensor [mHz]. + * hwfifo_odr_mHz: Batch data rate for hardware FIFO [mHz] * @samples_to_discard: Number of samples to discard for filters settling time. * @watermark: Sensor watermark level. * @decimator: Sensor decimation factor. @@ -380,6 +381,7 @@ struct st_lsm6dsx_sensor { u32 gain; u32 odr; + u32 hwfifo_odr_mHz; u16 samples_to_discard; u16 watermark; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c index 8a9d2593576a..55d877745575 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -105,7 +106,7 @@ static int st_lsm6dsx_get_decimator_val(struct st_lsm6dsx_sensor *sensor, u32 max_odr) { const int max_size = ARRAY_SIZE(st_lsm6dsx_decimator_table); - u32 decimator = max_odr / sensor->odr; + u32 decimator = max_odr / sensor->hwfifo_odr_mHz; int i; if (decimator > 1) @@ -136,14 +137,14 @@ static void st_lsm6dsx_get_max_min_odr(struct st_lsm6dsx_hw *hw, if (!(hw->enable_mask & BIT(sensor->id))) continue; - *max_odr = max_t(u32, *max_odr, sensor->odr); - *min_odr = min_t(u32, *min_odr, sensor->odr); + *max_odr = max(*max_odr, sensor->hwfifo_odr_mHz); + *min_odr = min(*min_odr, sensor->hwfifo_odr_mHz); } } static u8 st_lsm6dsx_get_sip(struct st_lsm6dsx_sensor *sensor, u32 min_odr) { - u8 sip = sensor->odr / min_odr; + u8 sip = sensor->hwfifo_odr_mHz / min_odr; return sip > 1 ? round_down(sip, 2) : sip; } @@ -231,7 +232,7 @@ static int st_lsm6dsx_set_fifo_odr(struct st_lsm6dsx_sensor *sensor, if (enable) { int err; - err = st_lsm6dsx_check_odr(sensor, sensor->odr, + err = st_lsm6dsx_check_odr(sensor, sensor->hwfifo_odr_mHz, &data); if (err < 0) return err; @@ -713,7 +714,7 @@ st_lsm6dsx_update_samples_to_discard(struct st_lsm6dsx_sensor *sensor) data = &hw->settings->samples_to_discard[sensor->id]; for (i = 0; i < ST_LSM6DSX_ODR_LIST_SIZE; i++) { - if (data->val[i].milli_hz == sensor->odr) { + if (data->val[i].milli_hz == sensor->hwfifo_odr_mHz) { sensor->samples_to_discard = data->val[i].samples; return; } @@ -799,6 +800,59 @@ static const struct iio_buffer_setup_ops st_lsm6dsx_buffer_ops = { .postdisable = st_lsm6dsx_buffer_postdisable, }; +static ssize_t st_lsm6dsx_hwfifo_odr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct st_lsm6dsx_sensor *sensor = iio_priv(dev_to_iio_dev(dev)); + + return sysfs_emit(buf, "%d.%03d\n", sensor->hwfifo_odr_mHz / 1000, + sensor->hwfifo_odr_mHz % 1000); +} + +static ssize_t st_lsm6dsx_hwfifo_odr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *iio_dev = dev_to_iio_dev(dev); + struct st_lsm6dsx_sensor *sensor = iio_priv(iio_dev); + int integer, milli; + int ret; + u32 hwfifo_odr; + u8 data; + + if (!iio_device_claim_direct(iio_dev)) + return -EBUSY; + + ret = iio_str_to_fixpoint(buf, 100, &integer, &milli); + if (ret) + goto out; + + hwfifo_odr = integer * 1000 + milli; + ret = st_lsm6dsx_check_odr(sensor, hwfifo_odr, &data); + if (ret < 0) + goto out; + + hwfifo_odr = ret; + + /* the batch data rate must not exceed the sensor output data rate */ + if (hwfifo_odr <= sensor->odr) + sensor->hwfifo_odr_mHz = hwfifo_odr; + else + ret = -EINVAL; + +out: + iio_device_release_direct(iio_dev); + + return ret < 0 ? ret : len; +} + +static IIO_DEV_ATTR_SAMP_FREQ(0664, st_lsm6dsx_hwfifo_odr_show, st_lsm6dsx_hwfifo_odr_store); + +static const struct iio_dev_attr *st_lsm6dsx_buffer_attrs[] = { + &iio_dev_attr_sampling_frequency, + NULL +}; + int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw) { int i, ret; @@ -807,8 +861,9 @@ int st_lsm6dsx_fifo_setup(struct st_lsm6dsx_hw *hw) if (!hw->iio_devs[i]) continue; - ret = devm_iio_kfifo_buffer_setup(hw->dev, hw->iio_devs[i], - &st_lsm6dsx_buffer_ops); + ret = devm_iio_kfifo_buffer_setup_ext(hw->dev, hw->iio_devs[i], + &st_lsm6dsx_buffer_ops, + st_lsm6dsx_buffer_attrs); if (ret) return ret; } diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c index d8cb4b0218d5..88939625ace4 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c @@ -1847,10 +1847,12 @@ static int st_lsm6dsx_write_raw(struct iio_dev *iio_dev, val = val * 1000 + val2 / 1000; val = st_lsm6dsx_check_odr(sensor, val, &data); - if (val < 0) + if (val < 0) { err = val; - else + } else { sensor->odr = val; + sensor->hwfifo_odr_mHz = val; + } break; } default: @@ -2384,6 +2386,7 @@ 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->hwfifo_odr_mHz = sensor->odr; sensor->gain = hw->settings->fs_table[id].fs_avl[0].gain; sensor->watermark = 1; diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c index 3c5e65dc0f97..d6a1eeb151ca 100644 --- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c +++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c @@ -640,6 +640,7 @@ __st_lsm6dsx_shub_write_raw(struct iio_dev *iio_dev, sensor->ext_info.slv_odr = val; sensor->odr = odr; + sensor->hwfifo_odr_mHz = odr; return 0; } case IIO_CHAN_INFO_SCALE: @@ -746,6 +747,7 @@ st_lsm6dsx_shub_alloc_iiodev(struct st_lsm6dsx_hw *hw, sensor->id = id; sensor->hw = hw; sensor->odr = hw->settings->odr_table[ref_id].odr_avl[0].milli_hz; + sensor->hwfifo_odr_mHz = sensor->odr; sensor->ext_info.slv_odr = info->odr_table.odr_avl[0].milli_hz; sensor->gain = info->fs_table.fs_avl[0].gain; sensor->ext_info.settings = info; From 192e5bbf0a8d7a629e6f9fa9e2fae54c3268bb7f Mon Sep 17 00:00:00 2001 From: Marilene Andrade Garcia Date: Wed, 15 Oct 2025 02:11:08 -0300 Subject: [PATCH 092/150] dt-bindings: iio: adc: add max14001 Add device-tree documentation for MAX14001/MAX14002 ADCs. The MAX14001/MAX14002 are isolated, single-channel analog-to-digital converters with programmable voltage comparators and inrush current control optimized for configurable binary input applications. They share the same features, but in the MAX14001 the inrush trigger threshold, current magnitude, and current duration are all programmable, whereas in the MAX14002 these parameters are fixed. Co-developed-by: Kim Seer Paller Signed-off-by: Kim Seer Paller Signed-off-by: Marilene Andrade Garcia Reviewed-by: Conor Dooley Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/adi,max14001.yaml | 89 +++++++++++++++++++ MAINTAINERS | 8 ++ 2 files changed, 97 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml b/Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml new file mode 100644 index 000000000000..a2dc59c9dcd8 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml @@ -0,0 +1,89 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +# Copyright 2023-2025 Analog Devices Inc. +# Copyright 2023 Kim Seer Paller +# Copyright 2025 Marilene Andrade Garcia +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/adi,max14001.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices MAX14001-MAX14002 ADC + +maintainers: + - Kim Seer Paller + - Marilene Andrade Garcia + +description: | + Single channel 10 bit ADC with SPI interface. + Datasheet can be found here + https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + oneOf: + - const: adi,max14002 + - items: + - const: adi,max14001 + - const: adi,max14002 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 5000000 + + vdd-supply: + description: + Isolated DC-DC power supply input voltage. + + vddl-supply: + description: + Logic power supply. + + refin-supply: + description: + ADC voltage reference supply. + + interrupts: + minItems: 1 + items: + - description: | + cout: comparator output signal that asserts high on the COUT pin + when ADC readings exceed the upper threshold and low when readings + fall below the lower threshold. + - description: | + fault: when fault reporting is enabled, the FAULT pin is asserted + low whenever one of the monitored fault conditions occurs. + + interrupt-names: + minItems: 1 + items: + - const: cout + - const: fault + +required: + - compatible + - reg + - vdd-supply + - vddl-supply + +unevaluatedProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "adi,max14001", "adi,max14002"; + reg = <0>; + spi-max-frequency = <5000000>; + spi-lsb-first; + vdd-supply = <&vdd>; + vddl-supply = <&vddl>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 8082081ea742..f584196d3260 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15174,6 +15174,14 @@ S: Orphan F: drivers/video/fbdev/matrox/matroxfb_* F: include/uapi/linux/matroxfb.h +MAX14001/MAX14002 IIO ADC DRIVER +M: Kim Seer Paller +M: Marilene Andrade Garcia +L: linux-iio@vger.kernel.org +S: Maintained +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml + MAX15301 DRIVER M: Daniel Nilsson L: linux-hwmon@vger.kernel.org From 59795109fa67d2bee7c8c2847a487d4dddb428c1 Mon Sep 17 00:00:00 2001 From: Marilene Andrade Garcia Date: Wed, 15 Oct 2025 02:12:08 -0300 Subject: [PATCH 093/150] iio: adc: max14001: New driver The MAX14001/MAX14002 is configurable, isolated 10-bit ADCs for multi-range binary inputs. In addition to ADC readings, the MAX14001/MAX14002 offers more features, like a binary comparator, a filtered reading that can provide the average of the last 2, 4, or 8 ADC readings, and an inrush comparator that triggers the inrush current. There is also a fault feature that can diagnose seven possible fault conditions. And an option to select an external or internal ADC voltage reference. MAX14001/MAX14002 features implemented so far: - Raw ADC reading. - MV fault disable. - Selection of external or internal ADC voltage reference, depending on whether it is declared in the device tree. Co-developed-by: Kim Seer Paller Signed-off-by: Kim Seer Paller Signed-off-by: Marilene Andrade Garcia Tested-by: Marcelo Schmitt Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/max14001.c | 391 +++++++++++++++++++++++++++++++++++++ 4 files changed, 403 insertions(+) create mode 100644 drivers/iio/adc/max14001.c diff --git a/MAINTAINERS b/MAINTAINERS index f584196d3260..940889b158eb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15181,6 +15181,7 @@ L: linux-iio@vger.kernel.org S: Maintained W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,max14001.yaml +F: drivers/iio/adc/max14001.c MAX15301 DRIVER M: Daniel Nilsson diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index b0580fcefef5..31335af6b2f1 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1020,6 +1020,16 @@ config MAX1363 To compile this driver as a module, choose M here: the module will be called max1363. +config MAX14001 + tristate "Analog Devices MAX14001/MAX14002 ADC driver" + depends on SPI + help + Say yes here to build support for Analog Devices MAX14001/MAX14002 + Configurable, Isolated 10-bit ADCs for Multi-Range Binary Inputs. + + To compile this driver as a module, choose M here: the module will be + called max14001. + config MAX34408 tristate "Maxim max34408/max344089 ADC driver" depends on I2C diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index ed647a734c51..e5349b01e4d9 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -89,6 +89,7 @@ obj-$(CONFIG_MAX11205) += max11205.o obj-$(CONFIG_MAX11410) += max11410.o obj-$(CONFIG_MAX1241) += max1241.o obj-$(CONFIG_MAX1363) += max1363.o +obj-$(CONFIG_MAX14001) += max14001.o obj-$(CONFIG_MAX34408) += max34408.o obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o obj-$(CONFIG_MAX9611) += max9611.o diff --git a/drivers/iio/adc/max14001.c b/drivers/iio/adc/max14001.c new file mode 100644 index 000000000000..90ad4cb5868d --- /dev/null +++ b/drivers/iio/adc/max14001.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +/* + * Analog Devices MAX14001/MAX14002 ADC driver + * + * Copyright (C) 2023-2025 Analog Devices Inc. + * Copyright (C) 2023 Kim Seer Paller + * Copyright (c) 2025 Marilene Andrade Garcia + * + * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* MAX14001 Registers Address */ +#define MAX14001_REG_ADC 0x00 +#define MAX14001_REG_FADC 0x01 +#define MAX14001_REG_FLAGS 0x02 +#define MAX14001_REG_FLTEN 0x03 +#define MAX14001_REG_THL 0x04 +#define MAX14001_REG_THU 0x05 +#define MAX14001_REG_INRR 0x06 +#define MAX14001_REG_INRT 0x07 +#define MAX14001_REG_INRP 0x08 +#define MAX14001_REG_CFG 0x09 +#define MAX14001_REG_ENBL 0x0A +#define MAX14001_REG_ACT 0x0B +#define MAX14001_REG_WEN 0x0C + +#define MAX14001_REG_VERIFICATION(x) ((x) + 0x10) + +#define MAX14001_REG_CFG_BIT_EXRF BIT(5) + +#define MAX14001_REG_WEN_VALUE_WRITE 0x294 + +#define MAX14001_MASK_ADDR GENMASK(15, 11) +#define MAX14001_MASK_WR BIT(10) +#define MAX14001_MASK_DATA GENMASK(9, 0) + +struct max14001_state { + const struct max14001_chip_info *chip_info; + struct spi_device *spi; + struct regmap *regmap; + int vref_mV; + bool spi_hw_has_lsb_first; + + /* + * The following buffers will be bit-reversed during device + * communication, because the device transmits and receives data + * LSB-first. + * DMA (thus cache coherency maintenance) requires the transfer + * buffers to live in their own cache lines. + */ + union { + __be16 be; + __le16 le; + } spi_tx_buffer __aligned(IIO_DMA_MINALIGN); + + union { + __be16 be; + __le16 le; + } spi_rx_buffer; +}; + +struct max14001_chip_info { + const char *name; +}; + +static int max14001_read(void *context, unsigned int reg, unsigned int *val) +{ + struct max14001_state *st = context; + struct spi_transfer xfers[] = { + { + .tx_buf = &st->spi_tx_buffer, + .len = sizeof(st->spi_tx_buffer), + .cs_change = 1, + }, { + .rx_buf = &st->spi_rx_buffer, + .len = sizeof(st->spi_rx_buffer), + }, + }; + int ret; + unsigned int addr, data; + + /* + * Prepare SPI transmit buffer 16 bit-value and reverse bit order + * to align with the LSB-first input on SDI port in order to meet + * the device communication requirements. If the controller supports + * SPI_LSB_FIRST, this step will be handled by the SPI controller. + */ + addr = FIELD_PREP(MAX14001_MASK_ADDR, reg); + + if (st->spi_hw_has_lsb_first) + st->spi_tx_buffer.le = cpu_to_le16(addr); + else + st->spi_tx_buffer.be = cpu_to_be16(bitrev16(addr)); + + ret = spi_sync_transfer(st->spi, xfers, ARRAY_SIZE(xfers)); + if (ret) + return ret; + + /* + * Convert received 16-bit value to cpu-endian format and reverse + * bit order. If the controller supports SPI_LSB_FIRST, this step + * will be handled by the SPI controller. + */ + if (st->spi_hw_has_lsb_first) + data = le16_to_cpu(st->spi_rx_buffer.le); + else + data = bitrev16(be16_to_cpu(st->spi_rx_buffer.be)); + + *val = FIELD_GET(MAX14001_MASK_DATA, data); + + return 0; +} + +static int max14001_write(struct max14001_state *st, unsigned int reg, unsigned int val) +{ + unsigned int addr; + + /* + * Prepare SPI transmit buffer 16 bit-value and reverse bit order + * to align with the LSB-first input on SDI port in order to meet + * the device communication requirements. If the controller supports + * SPI_LSB_FIRST, this step will be handled by the SPI controller. + */ + addr = FIELD_PREP(MAX14001_MASK_ADDR, reg) | + FIELD_PREP(MAX14001_MASK_WR, 1) | + FIELD_PREP(MAX14001_MASK_DATA, val); + + if (st->spi_hw_has_lsb_first) + st->spi_tx_buffer.le = cpu_to_le16(addr); + else + st->spi_tx_buffer.be = cpu_to_be16(bitrev16(addr)); + + return spi_write(st->spi, &st->spi_tx_buffer, sizeof(st->spi_tx_buffer)); +} + +static int max14001_write_single_reg(void *context, unsigned int reg, unsigned int val) +{ + struct max14001_state *st = context; + int ret; + + /* Enable writing to the SPI register. */ + ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_REG_WEN_VALUE_WRITE); + if (ret) + return ret; + + /* Writing data into SPI register. */ + ret = max14001_write(st, reg, val); + if (ret) + return ret; + + /* Disable writing to the SPI register. */ + return max14001_write(st, MAX14001_REG_WEN, 0); +} + +static int max14001_write_verification_reg(struct max14001_state *st, unsigned int reg) +{ + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, reg, &val); + if (ret) + return ret; + + return max14001_write(st, MAX14001_REG_VERIFICATION(reg), val); +} + +static int max14001_disable_mv_fault(struct max14001_state *st) +{ + unsigned int reg; + int ret; + + /* Enable writing to the SPI registers. */ + ret = max14001_write(st, MAX14001_REG_WEN, MAX14001_REG_WEN_VALUE_WRITE); + if (ret) + return ret; + + /* + * Reads all registers and writes the values to their appropriate + * verification registers to clear the Memory Validation fault. + */ + for (reg = MAX14001_REG_FLTEN; reg <= MAX14001_REG_ENBL; reg++) { + ret = max14001_write_verification_reg(st, reg); + if (ret) + return ret; + } + + /* Disable writing to the SPI registers. */ + return max14001_write(st, MAX14001_REG_WEN, 0); +} + +static int max14001_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, unsigned int writeval, + unsigned int *readval) +{ + struct max14001_state *st = iio_priv(indio_dev); + + if (readval) + return regmap_read(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static int max14001_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct max14001_state *st = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = regmap_read(st->regmap, MAX14001_REG_ADC, val); + if (ret) + return ret; + + return IIO_VAL_INT; + case IIO_CHAN_INFO_SCALE: + *val = st->vref_mV; + *val2 = 10; + + return IIO_VAL_FRACTIONAL_LOG2; + default: + return -EINVAL; + } +} + +static const struct regmap_range max14001_regmap_rd_range[] = { + regmap_reg_range(MAX14001_REG_ADC, MAX14001_REG_ENBL), + regmap_reg_range(MAX14001_REG_WEN, MAX14001_REG_WEN), + regmap_reg_range(MAX14001_REG_VERIFICATION(MAX14001_REG_FLTEN), + MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL)), +}; + +static const struct regmap_access_table max14001_regmap_rd_table = { + .yes_ranges = max14001_regmap_rd_range, + .n_yes_ranges = ARRAY_SIZE(max14001_regmap_rd_range), +}; + +static const struct regmap_range max14001_regmap_wr_range[] = { + regmap_reg_range(MAX14001_REG_FLTEN, MAX14001_REG_WEN), + regmap_reg_range(MAX14001_REG_VERIFICATION(MAX14001_REG_FLTEN), + MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL)), +}; + +static const struct regmap_access_table max14001_regmap_wr_table = { + .yes_ranges = max14001_regmap_wr_range, + .n_yes_ranges = ARRAY_SIZE(max14001_regmap_wr_range), +}; + +static const struct regmap_config max14001_regmap_config = { + .reg_read = max14001_read, + .reg_write = max14001_write_single_reg, + .max_register = MAX14001_REG_VERIFICATION(MAX14001_REG_ENBL), + .rd_table = &max14001_regmap_rd_table, + .wr_table = &max14001_regmap_wr_table, +}; + +static const struct iio_info max14001_info = { + .read_raw = max14001_read_raw, + .debugfs_reg_access = max14001_debugfs_reg_access, +}; + +static const struct iio_chan_spec max14001_channel[] = { + { + .type = IIO_VOLTAGE, + .indexed = 1, + .channel = 0, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int max14001_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct max14001_state *st; + int ret; + bool use_ext_vrefin = false; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + st->spi_hw_has_lsb_first = spi->mode & SPI_LSB_FIRST; + st->chip_info = spi_get_device_match_data(spi); + if (!st->chip_info) + return -EINVAL; + + indio_dev->name = st->chip_info->name; + indio_dev->info = &max14001_info; + indio_dev->channels = max14001_channel; + indio_dev->num_channels = ARRAY_SIZE(max14001_channel); + indio_dev->modes = INDIO_DIRECT_MODE; + + st->regmap = devm_regmap_init(dev, NULL, st, &max14001_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), "Failed to initialize regmap\n"); + + ret = devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable Vdd supply\n"); + + ret = devm_regulator_get_enable(dev, "vddl"); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable Vddl supply\n"); + + ret = devm_regulator_get_enable_read_voltage(dev, "refin"); + if (ret < 0 && ret != -ENODEV) + return dev_err_probe(dev, ret, "Failed to get REFIN voltage\n"); + + if (ret == -ENODEV) + ret = 1250000; + else + use_ext_vrefin = true; + st->vref_mV = ret / (MICRO / MILLI); + + if (use_ext_vrefin) { + /* + * Configure the MAX14001/MAX14002 to use an external voltage + * reference source by setting the bit 5 of the configuration register. + */ + ret = regmap_set_bits(st->regmap, MAX14001_REG_CFG, + MAX14001_REG_CFG_BIT_EXRF); + if (ret) + return dev_err_probe(dev, ret, + "Failed to set External REFIN in Configuration Register\n"); + } + + ret = max14001_disable_mv_fault(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to disable MV Fault\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static struct max14001_chip_info max14001_chip_info = { + .name = "max14001", +}; + +static struct max14001_chip_info max14002_chip_info = { + .name = "max14002", +}; + +static const struct spi_device_id max14001_id_table[] = { + { "max14001", (kernel_ulong_t)&max14001_chip_info }, + { "max14002", (kernel_ulong_t)&max14002_chip_info }, + { } +}; + +static const struct of_device_id max14001_of_match[] = { + { .compatible = "adi,max14001", .data = &max14001_chip_info }, + { .compatible = "adi,max14002", .data = &max14002_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, max14001_of_match); + +static struct spi_driver max14001_driver = { + .driver = { + .name = "max14001", + .of_match_table = max14001_of_match, + }, + .probe = max14001_probe, + .id_table = max14001_id_table, +}; +module_spi_driver(max14001_driver); + +MODULE_AUTHOR("Kim Seer Paller "); +MODULE_AUTHOR("Marilene Andrade Garcia "); +MODULE_DESCRIPTION("Analog Devices MAX14001/MAX14002 ADCs driver"); +MODULE_LICENSE("GPL"); From c5ebcc80fcf7d2c6ed917371f024d2da5bce9128 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 17 Oct 2025 00:07:27 -0700 Subject: [PATCH 094/150] iio: adc: qcom-vadc-common: fix vadc_scale_fn_type kernel-doc Fix multiple warnings in enum vadc_scale_fn_type by adding a leading '@' to the kernel-doc descriptions. Fixed 14 warnings in this one enum, such as: Warning: include/linux/iio/adc/qcom-vadc-common.h:123 Enum value 'SCALE_DEFAULT' not described in enum 'vadc_scale_fn_type' Warning: ../include/linux/iio/adc/qcom-vadc-common.h:123 Enum value 'SCALE_THERM_100K_PULLUP' not described in enum 'vadc_scale_fn_type' Warning: ../include/linux/iio/adc/qcom-vadc-common.h:123 Enum value 'SCALE_PMIC_THERM' not described in enum 'vadc_scale_fn_type' Also prevent the warning on SCALE_HW_CALIB_INVALID by marking it "private:" so that kernel-doc notation is not needed for it. This leaves only one warning here, which I don't know the appropriate description of: qcom-vadc-common.h:125: warning: Enum value 'SCALE_HW_CALIB_PMIC_THERM_PM7' not described in enum 'vadc_scale_fn_type' Signed-off-by: Randy Dunlap Signed-off-by: Jonathan Cameron --- include/linux/iio/adc/qcom-vadc-common.h | 27 ++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/include/linux/iio/adc/qcom-vadc-common.h b/include/linux/iio/adc/qcom-vadc-common.h index aa21b032e861..3bf4c49726a7 100644 --- a/include/linux/iio/adc/qcom-vadc-common.h +++ b/include/linux/iio/adc/qcom-vadc-common.h @@ -83,27 +83,27 @@ struct vadc_linear_graph { /** * enum vadc_scale_fn_type - Scaling function to convert ADC code to * physical scaled units for the channel. - * SCALE_DEFAULT: Default scaling to convert raw adc code to voltage (uV). - * SCALE_THERM_100K_PULLUP: Returns temperature in millidegC. + * @SCALE_DEFAULT: Default scaling to convert raw adc code to voltage (uV). + * @SCALE_THERM_100K_PULLUP: Returns temperature in millidegC. * Uses a mapping table with 100K pullup. - * SCALE_PMIC_THERM: Returns result in milli degree's Centigrade. - * SCALE_XOTHERM: Returns XO thermistor voltage in millidegC. - * SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp - * SCALE_HW_CALIB_DEFAULT: Default scaling to convert raw adc code to + * @SCALE_PMIC_THERM: Returns result in milli degree's Centigrade. + * @SCALE_XOTHERM: Returns XO thermistor voltage in millidegC. + * @SCALE_PMI_CHG_TEMP: Conversion for PMI CHG temp + * @SCALE_HW_CALIB_DEFAULT: Default scaling to convert raw adc code to * voltage (uV) with hardware applied offset/slope values to adc code. - * SCALE_HW_CALIB_THERM_100K_PULLUP: Returns temperature in millidegC using + * @SCALE_HW_CALIB_THERM_100K_PULLUP: Returns temperature in millidegC using * lookup table. The hardware applies offset/slope to adc code. - * SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC using + * @SCALE_HW_CALIB_XOTHERM: Returns XO thermistor voltage in millidegC using * 100k pullup. The hardware applies offset/slope to adc code. - * SCALE_HW_CALIB_THERM_100K_PU_PM7: Returns temperature in millidegC using + * @SCALE_HW_CALIB_THERM_100K_PU_PM7: Returns temperature in millidegC using * lookup table for PMIC7. The hardware applies offset/slope to adc code. - * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade. + * @SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade. * The hardware applies offset/slope to adc code. - * SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade. + * @SCALE_HW_CALIB_PMIC_THERM: Returns result in milli degree's Centigrade. * The hardware applies offset/slope to adc code. This is for PMIC7. - * SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for PMIC5 + * @SCALE_HW_CALIB_PM5_CHG_TEMP: Returns result in millidegrees for PMIC5 * charger temperature. - * SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for PMIC5 + * @SCALE_HW_CALIB_PM5_SMB_TEMP: Returns result in millidegrees for PMIC5 * SMB1390 temperature. */ enum vadc_scale_fn_type { @@ -120,6 +120,7 @@ enum vadc_scale_fn_type { SCALE_HW_CALIB_PMIC_THERM_PM7, SCALE_HW_CALIB_PM5_CHG_TEMP, SCALE_HW_CALIB_PM5_SMB_TEMP, + /* private: */ SCALE_HW_CALIB_INVALID, }; From 1c9986e782de45bf32fb4f886a40c1393d169568 Mon Sep 17 00:00:00 2001 From: Jianping Shen Date: Thu, 9 Oct 2025 17:31:48 +0200 Subject: [PATCH 095/150] dt-bindings: iio: imu: smi330: Add binding Add devicetree binding for Bosch imu smi330. The smi330 is a combined three axis angular rate and three axis acceleration sensor module. Signed-off-by: Jianping Shen Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- .../bindings/iio/imu/bosch,smi330.yaml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/imu/bosch,smi330.yaml diff --git a/Documentation/devicetree/bindings/iio/imu/bosch,smi330.yaml b/Documentation/devicetree/bindings/iio/imu/bosch,smi330.yaml new file mode 100644 index 000000000000..0270ca456d2b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/imu/bosch,smi330.yaml @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/imu/bosch,smi330.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Bosch SMI330 6-Axis IMU + +maintainers: + - Stefan Gutmann + +description: + SMI330 is a 6-axis inertial measurement unit that supports acceleration and + gyroscopic measurements with hardware fifo buffering. Sensor also provides + events information such as motion, no-motion and tilt detection. + +properties: + compatible: + const: bosch,smi330 + + reg: + maxItems: 1 + + vdd-supply: + description: provide VDD power to the sensor. + + vddio-supply: + description: provide VDD IO power to the sensor. + + interrupts: + minItems: 1 + maxItems: 2 + + interrupt-names: + minItems: 1 + maxItems: 2 + items: + enum: + - INT1 + - INT2 + + drive-open-drain: + type: boolean + description: + set if the interrupt pin(s) should be configured as + open drain. If not set, defaults to push-pull. + +required: + - compatible + - reg + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + // Example for I2C + #include + i2c { + #address-cells = <1>; + #size-cells = <0>; + + imu@68 { + compatible = "bosch,smi330"; + reg = <0x68>; + vddio-supply = <&vddio>; + vdd-supply = <&vdd>; + interrupt-parent = <&gpio>; + interrupts = <26 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "INT1"; + }; + }; + + // Example for SPI + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + + imu@0 { + compatible = "bosch,smi330"; + reg = <0>; + spi-max-frequency = <10000000>; + interrupt-parent = <&gpio>; + interrupts = <26 IRQ_TYPE_EDGE_RISING>; + interrupt-names = "INT1"; + }; + }; From 6f3d8de8886dae587467e1a2600d3bff4ad23e30 Mon Sep 17 00:00:00 2001 From: Jianping Shen Date: Thu, 9 Oct 2025 17:31:49 +0200 Subject: [PATCH 096/150] iio: imu: smi330: Add driver Add the iio driver for bosch imu smi330. The smi330 is a combined three axis angular rate and three axis acceleration sensor. Signed-off-by: Jianping Shen Signed-off-by: Jonathan Cameron --- drivers/iio/imu/Kconfig | 1 + drivers/iio/imu/Makefile | 1 + drivers/iio/imu/smi330/Kconfig | 33 + drivers/iio/imu/smi330/Makefile | 7 + drivers/iio/imu/smi330/smi330.h | 25 + drivers/iio/imu/smi330/smi330_core.c | 918 +++++++++++++++++++++++++++ drivers/iio/imu/smi330/smi330_i2c.c | 133 ++++ drivers/iio/imu/smi330/smi330_spi.c | 85 +++ 8 files changed, 1203 insertions(+) create mode 100644 drivers/iio/imu/smi330/Kconfig create mode 100644 drivers/iio/imu/smi330/Makefile create mode 100644 drivers/iio/imu/smi330/smi330.h create mode 100644 drivers/iio/imu/smi330/smi330_core.c create mode 100644 drivers/iio/imu/smi330/smi330_i2c.c create mode 100644 drivers/iio/imu/smi330/smi330_spi.c diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig index 9d732bed9fcd..7e0181c27bb6 100644 --- a/drivers/iio/imu/Kconfig +++ b/drivers/iio/imu/Kconfig @@ -125,6 +125,7 @@ config SMI240 This driver can also be built as a module. If so, the module will be called smi240. +source "drivers/iio/imu/smi330/Kconfig" source "drivers/iio/imu/st_lsm6dsx/Kconfig" source "drivers/iio/imu/st_lsm9ds0/Kconfig" diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile index 2ae6344f8469..13fb7846e9c9 100644 --- a/drivers/iio/imu/Makefile +++ b/drivers/iio/imu/Makefile @@ -32,5 +32,6 @@ obj-$(CONFIG_KMX61) += kmx61.o obj-$(CONFIG_SMI240) += smi240.o +obj-y += smi330/ obj-y += st_lsm6dsx/ obj-y += st_lsm9ds0/ diff --git a/drivers/iio/imu/smi330/Kconfig b/drivers/iio/imu/smi330/Kconfig new file mode 100644 index 000000000000..856a315e15aa --- /dev/null +++ b/drivers/iio/imu/smi330/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# SMI330 IMU driver +# + +config SMI330 + tristate + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + +config SMI330_I2C + tristate "Bosch SMI330 I2C driver" + depends on I2C + select SMI330 + select REGMAP_I2C + help + Enable support for the Bosch SMI330 6-Axis IMU connected to I2C + interface. + + This driver can also be built as a module. If so, the module will be + called smi330_i2c. + +config SMI330_SPI + tristate "Bosch SMI330 SPI driver" + depends on SPI + select SMI330 + select REGMAP_SPI + help + Enable support for the Bosch SMI330 6-Axis IMU connected to SPI + interface. + + This driver can also be built as a module. If so, the module will be + called smi330_spi. diff --git a/drivers/iio/imu/smi330/Makefile b/drivers/iio/imu/smi330/Makefile new file mode 100644 index 000000000000..c663dcb5a9f2 --- /dev/null +++ b/drivers/iio/imu/smi330/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for Bosch SMI330 IMU +# +obj-$(CONFIG_SMI330) += smi330_core.o +obj-$(CONFIG_SMI330_I2C) += smi330_i2c.o +obj-$(CONFIG_SMI330_SPI) += smi330_spi.o diff --git a/drivers/iio/imu/smi330/smi330.h b/drivers/iio/imu/smi330/smi330.h new file mode 100644 index 000000000000..a5c765645aaa --- /dev/null +++ b/drivers/iio/imu/smi330/smi330.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */ +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#ifndef _SMI330_H +#define _SMI330_H + +#include + +enum { + SMI330_SCAN_ACCEL_X, + SMI330_SCAN_ACCEL_Y, + SMI330_SCAN_ACCEL_Z, + SMI330_SCAN_GYRO_X, + SMI330_SCAN_GYRO_Y, + SMI330_SCAN_GYRO_Z, + SMI330_SCAN_TIMESTAMP, + SMI330_SCAN_LEN = SMI330_SCAN_TIMESTAMP, +}; + +extern const struct regmap_config smi330_regmap_config; + +int smi330_core_probe(struct device *dev, struct regmap *regmap); + +#endif /* _SMI330_H */ diff --git a/drivers/iio/imu/smi330/smi330_core.c b/drivers/iio/imu/smi330/smi330_core.c new file mode 100644 index 000000000000..7564f12543e0 --- /dev/null +++ b/drivers/iio/imu/smi330/smi330_core.c @@ -0,0 +1,918 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "smi330.h" + +/* Register map */ +#define SMI330_CHIP_ID_REG 0x00 +#define SMI330_ERR_REG 0x01 +#define SMI330_STATUS_REG 0x02 +#define SMI330_ACCEL_X_REG 0x03 +#define SMI330_GYRO_X_REG 0x06 +#define SMI330_TEMP_REG 0x09 +#define SMI330_INT1_STATUS_REG 0x0D +#define SMI330_ACCEL_CFG_REG 0x20 +#define SMI330_GYRO_CFG_REG 0x21 +#define SMI330_IO_INT_CTRL_REG 0x38 +#define SMI330_INT_CONF_REG 0x39 +#define SMI330_INT_MAP1_REG 0x3A +#define SMI330_INT_MAP2_REG 0x3B +#define SMI330_CMD_REG 0x7E + +/* Register mask */ +#define SMI330_CHIP_ID_MASK GENMASK(7, 0) +#define SMI330_ERR_FATAL_MASK BIT(0) +#define SMI330_ERR_ACC_CONF_MASK BIT(5) +#define SMI330_ERR_GYR_CONF_MASK BIT(6) +#define SMI330_STATUS_POR_MASK BIT(0) +#define SMI330_INT_STATUS_ACC_GYR_DRDY_MASK GENMASK(13, 12) +#define SMI330_CFG_ODR_MASK GENMASK(3, 0) +#define SMI330_CFG_RANGE_MASK GENMASK(6, 4) +#define SMI330_CFG_BW_MASK BIT(7) +#define SMI330_CFG_AVG_NUM_MASK GENMASK(10, 8) +#define SMI330_CFG_MODE_MASK GENMASK(14, 12) +#define SMI330_IO_INT_CTRL_INT1_MASK GENMASK(2, 0) +#define SMI330_IO_INT_CTRL_INT2_MASK GENMASK(10, 8) +#define SMI330_INT_CONF_LATCH_MASK BIT(0) +#define SMI330_INT_MAP2_ACC_DRDY_MASK GENMASK(11, 10) +#define SMI330_INT_MAP2_GYR_DRDY_MASK GENMASK(9, 8) + +/* Register values */ +#define SMI330_IO_INT_CTRL_LVL BIT(0) +#define SMI330_IO_INT_CTRL_OD BIT(1) +#define SMI330_IO_INT_CTRL_EN BIT(2) +#define SMI330_CMD_SOFT_RESET 0xDEAF + +/* T°C = (temp / 512) + 23 */ +#define SMI330_TEMP_OFFSET 11776 /* 23 * 512 */ +#define SMI330_TEMP_SCALE 1953125 /* (1 / 512) * 1e9 */ + +#define SMI330_CHIP_ID 0x42 +#define SMI330_SOFT_RESET_DELAY 2000 + +/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */ +#define smi330_field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1)) +#define smi330_field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask)) + +#define SMI330_ACCEL_CHANNEL(_axis) { \ + .type = IIO_ACCEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_dir_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = SMI330_SCAN_ACCEL_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +#define SMI330_GYRO_CHANNEL(_axis) { \ + .type = IIO_ANGL_VEL, \ + .modified = 1, \ + .channel2 = IIO_MOD_##_axis, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ) | \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_type_available = \ + BIT(IIO_CHAN_INFO_SCALE) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY), \ + .info_mask_shared_by_dir_available = \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .scan_index = SMI330_SCAN_GYRO_##_axis, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +#define SMI330_TEMP_CHANNEL(_index) { \ + .type = IIO_TEMP, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OFFSET) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .scan_index = _index, \ + .scan_type = { \ + .sign = 's', \ + .realbits = 16, \ + .storagebits = 16, \ + .endianness = IIO_LE, \ + }, \ +} + +enum smi330_accel_range { + SMI330_ACCEL_RANGE_2G = 0x00, + SMI330_ACCEL_RANGE_4G = 0x01, + SMI330_ACCEL_RANGE_8G = 0x02, + SMI330_ACCEL_RANGE_16G = 0x03 +}; + +enum smi330_gyro_range { + SMI330_GYRO_RANGE_125 = 0x0, + SMI330_GYRO_RANGE_250 = 0x01, + SMI330_GYRO_RANGE_500 = 0x02 +}; + +enum smi330_odr { + SMI330_ODR_12_5_HZ = 0x05, + SMI330_ODR_25_HZ = 0x06, + SMI330_ODR_50_HZ = 0x07, + SMI330_ODR_100_HZ = 0x08, + SMI330_ODR_200_HZ = 0x09, + SMI330_ODR_400_HZ = 0x0A, + SMI330_ODR_800_HZ = 0x0B, + SMI330_ODR_1600_HZ = 0x0C, + SMI330_ODR_3200_HZ = 0x0D, + SMI330_ODR_6400_HZ = 0x0E +}; + +enum smi330_avg_num { + SMI330_AVG_NUM_1 = 0x00, + SMI330_AVG_NUM_2 = 0x01, + SMI330_AVG_NUM_4 = 0x02, + SMI330_AVG_NUM_8 = 0x03, + SMI330_AVG_NUM_16 = 0x04, + SMI330_AVG_NUM_32 = 0x05, + SMI330_AVG_NUM_64 = 0x06 +}; + +enum smi330_mode { + SMI330_MODE_SUSPEND = 0x00, + SMI330_MODE_GYRO_DRIVE = 0x01, + SMI330_MODE_LOW_POWER = 0x03, + SMI330_MODE_NORMAL = 0x04, + SMI330_MODE_HIGH_PERF = 0x07 +}; + +enum smi330_bw { + SMI330_BW_2 = 0x00, /* ODR/2 */ + SMI330_BW_4 = 0x01 /* ODR/4 */ +}; + +enum smi330_operation_mode { + SMI330_POLLING, + SMI330_DATA_READY, +}; + +enum smi330_sensor { + SMI330_ACCEL, + SMI330_GYRO, +}; + +enum smi330_sensor_conf_select { + SMI330_ODR, + SMI330_RANGE, + SMI330_BW, + SMI330_AVG_NUM, +}; + +enum smi330_int_out { + SMI330_INT_DISABLED, + SMI330_INT_1, + SMI330_INT_2, +}; + +struct smi330_attributes { + int *reg_vals; + int *vals; + int len; + int type; + int mask; +}; + +struct smi330_cfg { + enum smi330_operation_mode op_mode; + enum smi330_int_out data_irq; +}; + +struct smi330_data { + struct regmap *regmap; + struct smi330_cfg cfg; + struct iio_trigger *trig; + IIO_DECLARE_BUFFER_WITH_TS(__le16, buf, SMI330_SCAN_LEN); +}; + +const struct regmap_config smi330_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; +EXPORT_SYMBOL_NS_GPL(smi330_regmap_config, "IIO_SMI330"); + +static const struct iio_chan_spec smi330_channels[] = { + SMI330_ACCEL_CHANNEL(X), + SMI330_ACCEL_CHANNEL(Y), + SMI330_ACCEL_CHANNEL(Z), + SMI330_GYRO_CHANNEL(X), + SMI330_GYRO_CHANNEL(Y), + SMI330_GYRO_CHANNEL(Z), + SMI330_TEMP_CHANNEL(-1), /* No buffer support */ + IIO_CHAN_SOFT_TIMESTAMP(SMI330_SCAN_TIMESTAMP), +}; + +static const unsigned long smi330_avail_scan_masks[] = { + (BIT(SMI330_SCAN_ACCEL_X) | BIT(SMI330_SCAN_ACCEL_Y) | + BIT(SMI330_SCAN_ACCEL_Z) | BIT(SMI330_SCAN_GYRO_X) | + BIT(SMI330_SCAN_GYRO_Y) | BIT(SMI330_SCAN_GYRO_Z)), + 0 +}; + +static const struct smi330_attributes smi330_accel_scale_attr = { + .reg_vals = (int[]){ SMI330_ACCEL_RANGE_2G, SMI330_ACCEL_RANGE_4G, + SMI330_ACCEL_RANGE_8G, SMI330_ACCEL_RANGE_16G }, + .vals = (int[]){ 0, 61035, 0, 122070, 0, 244140, 0, 488281 }, + .len = 8, + .type = IIO_VAL_INT_PLUS_NANO, + .mask = SMI330_CFG_RANGE_MASK +}; + +static const struct smi330_attributes smi330_gyro_scale_attr = { + .reg_vals = (int[]){ SMI330_GYRO_RANGE_125, SMI330_GYRO_RANGE_250, + SMI330_GYRO_RANGE_500 }, + .vals = (int[]){ 0, 3814697, 0, 7629395, 0, 15258789 }, + .len = 6, + .type = IIO_VAL_INT_PLUS_NANO, + .mask = SMI330_CFG_RANGE_MASK +}; + +static const struct smi330_attributes smi330_average_attr = { + .reg_vals = (int[]){ SMI330_AVG_NUM_1, SMI330_AVG_NUM_2, + SMI330_AVG_NUM_4, SMI330_AVG_NUM_8, + SMI330_AVG_NUM_16, SMI330_AVG_NUM_32, + SMI330_AVG_NUM_64 }, + .vals = (int[]){ 1, 2, 4, 8, 16, 32, 64 }, + .len = 7, + .type = IIO_VAL_INT, + .mask = SMI330_CFG_AVG_NUM_MASK +}; + +static const struct smi330_attributes smi330_bandwidth_attr = { + .reg_vals = (int[]){ SMI330_BW_2, SMI330_BW_4 }, + .vals = (int[]){ 2, 4 }, + .len = 2, + .type = IIO_VAL_INT, + .mask = SMI330_CFG_BW_MASK +}; + +static const struct smi330_attributes smi330_odr_attr = { + .reg_vals = (int[]){ SMI330_ODR_12_5_HZ, SMI330_ODR_25_HZ, + SMI330_ODR_50_HZ, SMI330_ODR_100_HZ, + SMI330_ODR_200_HZ, SMI330_ODR_400_HZ, + SMI330_ODR_800_HZ, SMI330_ODR_1600_HZ, + SMI330_ODR_3200_HZ, SMI330_ODR_6400_HZ }, + .vals = (int[]){ 12, 25, 50, 100, 200, 400, 800, 1600, 3200, 6400 }, + .len = 10, + .type = IIO_VAL_INT, + .mask = SMI330_CFG_ODR_MASK +}; + +static int smi330_get_attributes(enum smi330_sensor_conf_select config, + enum smi330_sensor sensor, + const struct smi330_attributes **attr) +{ + switch (config) { + case SMI330_ODR: + *attr = &smi330_odr_attr; + return 0; + case SMI330_RANGE: + if (sensor == SMI330_ACCEL) + *attr = &smi330_accel_scale_attr; + else + *attr = &smi330_gyro_scale_attr; + return 0; + case SMI330_BW: + *attr = &smi330_bandwidth_attr; + return 0; + case SMI330_AVG_NUM: + *attr = &smi330_average_attr; + return 0; + default: + return -EINVAL; + } +} + +static int smi330_get_config_reg(enum smi330_sensor sensor, int *reg) +{ + switch (sensor) { + case SMI330_ACCEL: + *reg = SMI330_ACCEL_CFG_REG; + return 0; + case SMI330_GYRO: + *reg = SMI330_GYRO_CFG_REG; + return 0; + default: + return -EINVAL; + } +} + +static int smi330_get_sensor_config(struct smi330_data *data, + enum smi330_sensor sensor, + enum smi330_sensor_conf_select config, + int *value) + +{ + int ret, reg, reg_val, i; + const struct smi330_attributes *attr; + + ret = smi330_get_config_reg(sensor, ®); + if (ret) + return ret; + + ret = regmap_read(data->regmap, reg, ®_val); + if (ret) + return ret; + + ret = smi330_get_attributes(config, sensor, &attr); + if (ret) + return ret; + + reg_val = smi330_field_get(attr->mask, reg_val); + + if (attr->type == IIO_VAL_INT) { + for (i = 0; i < attr->len; i++) { + if (attr->reg_vals[i] == reg_val) { + *value = attr->vals[i]; + return 0; + } + } + } else { + for (i = 0; i < attr->len / 2; i++) { + if (attr->reg_vals[i] == reg_val) { + *value = attr->vals[2 * i + 1]; + return 0; + } + } + } + + return -EINVAL; +} + +static int smi330_set_sensor_config(struct smi330_data *data, + enum smi330_sensor sensor, + enum smi330_sensor_conf_select config, + int value) +{ + int ret, i, reg, reg_val, error; + const struct smi330_attributes *attr; + + ret = smi330_get_attributes(config, sensor, &attr); + if (ret) + return ret; + + for (i = 0; i < attr->len; i++) { + if (attr->vals[i] == value) { + if (attr->type == IIO_VAL_INT) + reg_val = attr->reg_vals[i]; + else + reg_val = attr->reg_vals[i / 2]; + break; + } + } + if (i == attr->len) + return -EINVAL; + + ret = smi330_get_config_reg(sensor, ®); + if (ret) + return ret; + + reg_val = smi330_field_prep(attr->mask, reg_val); + ret = regmap_update_bits(data->regmap, reg, attr->mask, reg_val); + if (ret) + return ret; + + ret = regmap_read(data->regmap, SMI330_ERR_REG, &error); + if (ret) + return ret; + + if (FIELD_GET(SMI330_ERR_ACC_CONF_MASK, error) || + FIELD_GET(SMI330_ERR_GYR_CONF_MASK, error)) + return -EIO; + + return 0; +} + +static int smi330_get_data(struct smi330_data *data, int chan_type, int axis, + int *val) +{ + u8 reg; + int ret, sample; + + switch (chan_type) { + case IIO_ACCEL: + reg = SMI330_ACCEL_X_REG + (axis - IIO_MOD_X); + break; + case IIO_ANGL_VEL: + reg = SMI330_GYRO_X_REG + (axis - IIO_MOD_X); + break; + case IIO_TEMP: + reg = SMI330_TEMP_REG; + break; + default: + return -EINVAL; + } + + ret = regmap_read(data->regmap, reg, &sample); + if (ret) + return ret; + + *val = sign_extend32(sample, 15); + + return 0; +} + +static int smi330_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SCALE: + if (chan->type == IIO_ACCEL) { + *vals = smi330_accel_scale_attr.vals; + *length = smi330_accel_scale_attr.len; + *type = smi330_accel_scale_attr.type; + } else { + *vals = smi330_gyro_scale_attr.vals; + *length = smi330_gyro_scale_attr.len; + *type = smi330_gyro_scale_attr.type; + } + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals = smi330_average_attr.vals; + *length = smi330_average_attr.len; + *type = smi330_average_attr.type; + *type = IIO_VAL_INT; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + *vals = smi330_bandwidth_attr.vals; + *length = smi330_bandwidth_attr.len; + *type = smi330_bandwidth_attr.type; + return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_SAMP_FREQ: + *vals = smi330_odr_attr.vals; + *length = smi330_odr_attr.len; + *type = smi330_odr_attr.type; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int smi330_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + int ret; + struct smi330_data *data = iio_priv(indio_dev); + enum smi330_sensor sensor; + + /* valid for all channel types */ + switch (mask) { + case IIO_CHAN_INFO_RAW: + if (!iio_device_claim_direct(indio_dev)) + return -EBUSY; + ret = smi330_get_data(data, chan->type, chan->channel2, val); + iio_device_release_direct(indio_dev); + return ret ? ret : IIO_VAL_INT; + default: + break; + } + + switch (chan->type) { + case IIO_ACCEL: + sensor = SMI330_ACCEL; + break; + case IIO_ANGL_VEL: + sensor = SMI330_GYRO; + break; + case IIO_TEMP: + switch (mask) { + case IIO_CHAN_INFO_SCALE: + *val = SMI330_TEMP_SCALE / GIGA; + *val2 = SMI330_TEMP_SCALE % GIGA; + return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_OFFSET: + *val = SMI330_TEMP_OFFSET; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } + + /* valid for acc and gyro channels */ + switch (mask) { + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + ret = smi330_get_sensor_config(data, sensor, SMI330_AVG_NUM, + val); + return ret ? ret : IIO_VAL_INT; + + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + ret = smi330_get_sensor_config(data, sensor, SMI330_BW, val); + return ret ? ret : IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + ret = smi330_get_sensor_config(data, sensor, SMI330_ODR, val); + return ret ? ret : IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + *val = 0; + ret = smi330_get_sensor_config(data, sensor, SMI330_RANGE, + val2); + return ret ? ret : IIO_VAL_INT_PLUS_NANO; + + default: + return -EINVAL; + } +} + +static int smi330_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, int val2, + long mask) +{ + struct smi330_data *data = iio_priv(indio_dev); + enum smi330_sensor sensor; + + switch (chan->type) { + case IIO_ACCEL: + sensor = SMI330_ACCEL; + break; + case IIO_ANGL_VEL: + sensor = SMI330_GYRO; + break; + default: + return -EINVAL; + } + + switch (mask) { + case IIO_CHAN_INFO_SCALE: + return smi330_set_sensor_config(data, sensor, SMI330_RANGE, + val2); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + return smi330_set_sensor_config(data, sensor, SMI330_AVG_NUM, + val); + case IIO_CHAN_INFO_LOW_PASS_FILTER_3DB_FREQUENCY: + return smi330_set_sensor_config(data, sensor, SMI330_BW, val); + case IIO_CHAN_INFO_SAMP_FREQ: + return smi330_set_sensor_config(data, sensor, SMI330_ODR, val); + default: + return -EINVAL; + } +} + +static int smi330_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long info) +{ + switch (info) { + case IIO_CHAN_INFO_SCALE: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int smi330_soft_reset(struct smi330_data *data) +{ + int ret, dummy_byte; + + ret = regmap_write(data->regmap, SMI330_CMD_REG, SMI330_CMD_SOFT_RESET); + if (ret) + return ret; + fsleep(SMI330_SOFT_RESET_DELAY); + + /* Performing a dummy read after a soft-reset */ + regmap_read(data->regmap, SMI330_CHIP_ID_REG, &dummy_byte); + + return 0; +} + +static irqreturn_t smi330_trigger_handler(int irq, void *p) +{ + int ret; + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct smi330_data *data = iio_priv(indio_dev); + + ret = regmap_bulk_read(data->regmap, SMI330_ACCEL_X_REG, data->buf, + SMI330_SCAN_LEN); + if (ret) + goto out; + + iio_push_to_buffers_with_timestamp(indio_dev, data->buf, pf->timestamp); + +out: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static irqreturn_t smi330_irq_thread_handler(int irq, void *indio_dev_) +{ + int ret, int_stat; + s16 int_status[2] = { 0 }; + struct iio_dev *indio_dev = indio_dev_; + struct smi330_data *data = iio_priv(indio_dev); + + ret = regmap_bulk_read(data->regmap, SMI330_INT1_STATUS_REG, int_status, 2); + if (ret) + return IRQ_NONE; + + int_stat = int_status[0] | int_status[1]; + + if (FIELD_GET(SMI330_INT_STATUS_ACC_GYR_DRDY_MASK, int_stat)) { + indio_dev->pollfunc->timestamp = iio_get_time_ns(indio_dev); + iio_trigger_poll_nested(data->trig); + } + + return IRQ_HANDLED; +} + +static int smi330_set_int_pin_config(struct smi330_data *data, + enum smi330_int_out irq_num, + bool active_high, bool open_drain, + bool latch) +{ + int ret, val; + + val = active_high ? SMI330_IO_INT_CTRL_LVL : 0; + val |= open_drain ? SMI330_IO_INT_CTRL_OD : 0; + val |= SMI330_IO_INT_CTRL_EN; + + switch (irq_num) { + case SMI330_INT_1: + val = FIELD_PREP(SMI330_IO_INT_CTRL_INT1_MASK, val); + ret = regmap_update_bits(data->regmap, SMI330_IO_INT_CTRL_REG, + SMI330_IO_INT_CTRL_INT1_MASK, val); + if (ret) + return ret; + break; + case SMI330_INT_2: + val = FIELD_PREP(SMI330_IO_INT_CTRL_INT2_MASK, val); + ret = regmap_update_bits(data->regmap, SMI330_IO_INT_CTRL_REG, + SMI330_IO_INT_CTRL_INT2_MASK, val); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return regmap_update_bits(data->regmap, SMI330_INT_CONF_REG, + SMI330_INT_CONF_LATCH_MASK, + FIELD_PREP(SMI330_INT_CONF_LATCH_MASK, + latch)); +} + +static int smi330_setup_irq(struct device *dev, struct iio_dev *indio_dev, + int irq, enum smi330_int_out irq_num) +{ + int ret, irq_type; + bool open_drain, active_high, latch; + struct smi330_data *data = iio_priv(indio_dev); + struct irq_data *desc; + + desc = irq_get_irq_data(irq); + if (!desc) + return -EINVAL; + + irq_type = irqd_get_trigger_type(desc); + switch (irq_type) { + case IRQF_TRIGGER_RISING: + latch = false; + active_high = true; + break; + case IRQF_TRIGGER_HIGH: + latch = true; + active_high = true; + break; + case IRQF_TRIGGER_FALLING: + latch = false; + active_high = false; + break; + case IRQF_TRIGGER_LOW: + latch = true; + active_high = false; + break; + default: + return -EINVAL; + } + + open_drain = device_property_read_bool(dev, "drive-open-drain"); + + ret = smi330_set_int_pin_config(data, irq_num, active_high, open_drain, + latch); + if (ret) + return ret; + + return devm_request_threaded_irq(dev, irq, NULL, + smi330_irq_thread_handler, + irq_type | IRQF_ONESHOT, + indio_dev->name, indio_dev); +} + +static int smi330_register_irq(struct device *dev, struct iio_dev *indio_dev) +{ + int ret, irq; + struct smi330_data *data = iio_priv(indio_dev); + struct fwnode_handle *fwnode; + + fwnode = dev_fwnode(dev); + if (!fwnode) + return -ENODEV; + + data->cfg.data_irq = SMI330_INT_DISABLED; + + irq = fwnode_irq_get_byname(fwnode, "INT1"); + if (irq > 0) { + ret = smi330_setup_irq(dev, indio_dev, irq, SMI330_INT_1); + if (ret) + return ret; + data->cfg.data_irq = SMI330_INT_1; + } else { + irq = fwnode_irq_get_byname(fwnode, "INT2"); + if (irq > 0) { + ret = smi330_setup_irq(dev, indio_dev, irq, + SMI330_INT_2); + if (ret) + return ret; + data->cfg.data_irq = SMI330_INT_2; + } + } + + return 0; +} + +static int smi330_set_drdy_trigger_state(struct iio_trigger *trig, bool enable) +{ + int val; + struct smi330_data *data = iio_trigger_get_drvdata(trig); + + if (enable) + data->cfg.op_mode = SMI330_DATA_READY; + else + data->cfg.op_mode = SMI330_POLLING; + + val = FIELD_PREP(SMI330_INT_MAP2_ACC_DRDY_MASK, + enable ? data->cfg.data_irq : 0); + val |= FIELD_PREP(SMI330_INT_MAP2_GYR_DRDY_MASK, + enable ? data->cfg.data_irq : 0); + return regmap_update_bits(data->regmap, SMI330_INT_MAP2_REG, + SMI330_INT_MAP2_ACC_DRDY_MASK | + SMI330_INT_MAP2_GYR_DRDY_MASK, + val); +} + +static const struct iio_trigger_ops smi330_trigger_ops = { + .set_trigger_state = &smi330_set_drdy_trigger_state, +}; + +static struct iio_info smi330_info = { + .read_avail = smi330_read_avail, + .read_raw = smi330_read_raw, + .write_raw = smi330_write_raw, + .write_raw_get_fmt = smi330_write_raw_get_fmt, +}; + +static int smi330_dev_init(struct smi330_data *data) +{ + int ret, chip_id, val, mode; + struct device *dev = regmap_get_device(data->regmap); + + ret = regmap_read(data->regmap, SMI330_CHIP_ID_REG, &chip_id); + if (ret) + return ret; + + chip_id = FIELD_GET(SMI330_CHIP_ID_MASK, chip_id); + if (chip_id != SMI330_CHIP_ID) + dev_info(dev, "Unknown chip id: 0x%04x\n", chip_id); + + ret = regmap_read(data->regmap, SMI330_ERR_REG, &val); + if (ret) + return ret; + if (FIELD_GET(SMI330_ERR_FATAL_MASK, val)) + return -ENODEV; + + ret = regmap_read(data->regmap, SMI330_STATUS_REG, &val); + if (ret) + return ret; + if (FIELD_GET(SMI330_STATUS_POR_MASK, val) == 0) + return -ENODEV; + + mode = FIELD_PREP(SMI330_CFG_MODE_MASK, SMI330_MODE_NORMAL); + + ret = regmap_update_bits(data->regmap, SMI330_ACCEL_CFG_REG, + SMI330_CFG_MODE_MASK, mode); + if (ret) + return ret; + + return regmap_update_bits(data->regmap, SMI330_GYRO_CFG_REG, + SMI330_CFG_MODE_MASK, mode); +} + +int smi330_core_probe(struct device *dev, struct regmap *regmap) +{ + int ret; + struct iio_dev *indio_dev; + struct smi330_data *data; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->regmap = regmap; + + ret = smi330_soft_reset(data); + if (ret) + return dev_err_probe(dev, ret, "Soft reset failed\n"); + + indio_dev->channels = smi330_channels; + indio_dev->num_channels = ARRAY_SIZE(smi330_channels); + indio_dev->available_scan_masks = smi330_avail_scan_masks; + indio_dev->name = "smi330"; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &smi330_info; + + data->cfg.op_mode = SMI330_POLLING; + + ret = smi330_dev_init(data); + if (ret) + return dev_err_probe(dev, ret, "Init failed\n"); + + ret = smi330_register_irq(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Register IRQ failed\n"); + + if (data->cfg.data_irq != SMI330_INT_DISABLED) { + data->trig = devm_iio_trigger_alloc(dev, "%s-drdy-trigger", + indio_dev->name); + if (!data->trig) + return -ENOMEM; + + data->trig->ops = &smi330_trigger_ops; + iio_trigger_set_drvdata(data->trig, data); + + ret = devm_iio_trigger_register(dev, data->trig); + if (ret) + return dev_err_probe(dev, ret, + "IIO register trigger failed\n"); + + /* Set default operation mode to data ready. */ + indio_dev->trig = iio_trigger_get(data->trig); + } + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + smi330_trigger_handler, NULL); + if (ret) + return dev_err_probe(dev, ret, "IIO buffer setup failed\n"); + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Register IIO device failed\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(smi330_core_probe, "IIO_SMI330"); + +MODULE_AUTHOR("Stefan Gutmann "); +MODULE_AUTHOR("Roman Huber "); +MODULE_AUTHOR("Filip Andrei "); +MODULE_AUTHOR("Drimbarean Avram Andrei "); +MODULE_DESCRIPTION("Bosch SMI330 IMU driver"); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/iio/imu/smi330/smi330_i2c.c b/drivers/iio/imu/smi330/smi330_i2c.c new file mode 100644 index 000000000000..e5f1825beb71 --- /dev/null +++ b/drivers/iio/imu/smi330/smi330_i2c.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#include +#include +#include +#include + +#include "smi330.h" + +#define SMI330_NUM_DUMMY_BYTES 2 +#define SMI330_I2C_MAX_RX_BUFFER_SIZE \ + (SMI330_NUM_DUMMY_BYTES + SMI330_SCAN_LEN * sizeof(s16)) + +struct smi330_i2c_priv { + struct i2c_client *i2c; + u8 rx_buffer[SMI330_I2C_MAX_RX_BUFFER_SIZE]; +}; + +static int smi330_regmap_i2c_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct smi330_i2c_priv *priv = context; + int ret; + + if (SMI330_NUM_DUMMY_BYTES + val_size > SMI330_I2C_MAX_RX_BUFFER_SIZE) + return -EINVAL; + + /* + * SMI330 I2C read frame: + * + * ... + * + * Remark: Slave address is not considered part of the frame in the following definitions + */ + struct i2c_msg msgs[] = { + { + .addr = priv->i2c->addr, + .flags = priv->i2c->flags, + .len = reg_size, + .buf = (u8 *)reg_buf, + }, + { + .addr = priv->i2c->addr, + .flags = priv->i2c->flags | I2C_M_RD, + .len = SMI330_NUM_DUMMY_BYTES + val_size, + .buf = priv->rx_buffer, + }, + }; + + ret = i2c_transfer(priv->i2c->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + return ret; + + memcpy(val_buf, priv->rx_buffer + SMI330_NUM_DUMMY_BYTES, val_size); + + return 0; +} + +static int smi330_regmap_i2c_write(void *context, const void *data, + size_t count) +{ + struct smi330_i2c_priv *priv = context; + u8 reg; + + /* + * SMI330 I2C write frame: + * ... + * + * Remark: Slave address is not considered part of the frame in the following definitions + */ + reg = *(u8 *)data; + return i2c_smbus_write_i2c_block_data(priv->i2c, reg, + count - sizeof(u8), + data + sizeof(u8)); +} + +static const struct regmap_bus smi330_regmap_bus = { + .read = smi330_regmap_i2c_read, + .write = smi330_regmap_i2c_write, +}; + +static int smi330_i2c_probe(struct i2c_client *i2c) +{ + struct device *dev = &i2c->dev; + struct smi330_i2c_priv *priv; + struct regmap *regmap; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->i2c = i2c; + regmap = devm_regmap_init(dev, &smi330_regmap_bus, priv, + &smi330_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), + "Failed to initialize I2C Regmap\n"); + + return smi330_core_probe(dev, regmap); +} + +static const struct i2c_device_id smi330_i2c_device_id[] = { + { .name = "smi330" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smi330_i2c_device_id); + +static const struct of_device_id smi330_of_match[] = { + { .compatible = "bosch,smi330" }, + { } +}; +MODULE_DEVICE_TABLE(of, smi330_of_match); + +static struct i2c_driver smi330_i2c_driver = { + .probe = smi330_i2c_probe, + .id_table = smi330_i2c_device_id, + .driver = { + .of_match_table = smi330_of_match, + .name = "smi330_i2c", + }, +}; +module_i2c_driver(smi330_i2c_driver); + +MODULE_AUTHOR("Stefan Gutmann "); +MODULE_AUTHOR("Roman Huber "); +MODULE_AUTHOR("Filip Andrei "); +MODULE_AUTHOR("Drimbarean Avram Andrei "); +MODULE_DESCRIPTION("Bosch SMI330 I2C driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("IIO_SMI330"); diff --git a/drivers/iio/imu/smi330/smi330_spi.c b/drivers/iio/imu/smi330/smi330_spi.c new file mode 100644 index 000000000000..a6044e02b451 --- /dev/null +++ b/drivers/iio/imu/smi330/smi330_spi.c @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 +/* + * Copyright (c) 2025 Robert Bosch GmbH. + */ +#include +#include +#include +#include + +#include "smi330.h" + +static int smi330_regmap_spi_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, + size_t val_size) +{ + struct spi_device *spi = context; + + /* Insert pad byte for reading */ + u8 reg[] = { *(u8 *)reg_buf, 0 }; + + if (reg_size + 1 != ARRAY_SIZE(reg)) { + dev_err(&spi->dev, "Invalid register size %zu\n", reg_size); + return -EINVAL; + } + + return spi_write_then_read(spi, reg, ARRAY_SIZE(reg), val_buf, + val_size); +} + +static int smi330_regmap_spi_write(void *context, const void *data, + size_t count) +{ + struct spi_device *spi = context; + + return spi_write(spi, data, count); +} + +static const struct regmap_bus smi330_regmap_bus = { + .read = smi330_regmap_spi_read, + .write = smi330_regmap_spi_write, + .read_flag_mask = 0x80, +}; + +static int smi330_spi_probe(struct spi_device *spi) +{ + struct regmap *regmap; + + regmap = devm_regmap_init(&spi->dev, &smi330_regmap_bus, &spi->dev, + &smi330_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(&spi->dev, PTR_ERR(regmap), + "Failed to initialize SPI Regmap\n"); + + return smi330_core_probe(&spi->dev, regmap); +} + +static const struct spi_device_id smi330_spi_device_id[] = { + { .name = "smi330" }, + { } +}; +MODULE_DEVICE_TABLE(spi, smi330_spi_device_id); + +static const struct of_device_id smi330_of_match[] = { + { .compatible = "bosch,smi330" }, + { } +}; +MODULE_DEVICE_TABLE(of, smi330_of_match); + +static struct spi_driver smi330_spi_driver = { + .probe = smi330_spi_probe, + .id_table = smi330_spi_device_id, + .driver = { + .of_match_table = smi330_of_match, + .name = "smi330_spi", + }, +}; +module_spi_driver(smi330_spi_driver); + +MODULE_AUTHOR("Stefan Gutmann "); +MODULE_AUTHOR("Roman Huber "); +MODULE_AUTHOR("Filip Andrei "); +MODULE_AUTHOR("Drimbarean Avram Andrei "); +MODULE_DESCRIPTION("Bosch SMI330 SPI driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_IMPORT_NS("IIO_SMI330"); From db52c405f352f809b6ff96466fe5456b0220e354 Mon Sep 17 00:00:00 2001 From: Petre Rodan Date: Tue, 21 Oct 2025 13:31:49 +0300 Subject: [PATCH 097/150] iio: accel: bma220: move set_wdt() out of bma220_core Move bma220_set_wdt() into bma220_i2c.c instead of using a conditional based on i2c_verify_client() in bma220_core.c that would make core always depend on the i2c module. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510102117.Jqxrw1vF-lkp@intel.com/ Signed-off-by: Petre Rodan Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/accel/bma220.h | 6 ++++++ drivers/iio/accel/bma220_core.c | 19 ------------------- drivers/iio/accel/bma220_i2c.c | 14 +++++++++++++- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/drivers/iio/accel/bma220.h b/drivers/iio/accel/bma220.h index e53ca63de54b..00dfe275256b 100644 --- a/drivers/iio/accel/bma220.h +++ b/drivers/iio/accel/bma220.h @@ -11,6 +11,12 @@ #include #include +#define BMA220_REG_WDT 0x17 +#define BMA220_WDT_MASK GENMASK(2, 1) +#define BMA220_WDT_OFF 0x0 +#define BMA220_WDT_1MS 0x2 +#define BMA220_WDT_10MS 0x3 + struct device; extern const struct regmap_config bma220_i2c_regmap_config; diff --git a/drivers/iio/accel/bma220_core.c b/drivers/iio/accel/bma220_core.c index 871342d21456..f32d875b994e 100644 --- a/drivers/iio/accel/bma220_core.c +++ b/drivers/iio/accel/bma220_core.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -78,11 +77,6 @@ #define BMA220_FILTER_MASK GENMASK(3, 0) #define BMA220_REG_RANGE 0x11 #define BMA220_RANGE_MASK GENMASK(1, 0) -#define BMA220_REG_WDT 0x17 -#define BMA220_WDT_MASK GENMASK(2, 1) -#define BMA220_WDT_OFF 0x0 -#define BMA220_WDT_1MS 0x2 -#define BMA220_WDT_10MS 0x3 #define BMA220_REG_SUSPEND 0x18 #define BMA220_REG_SOFTRESET 0x19 @@ -443,12 +437,6 @@ static int bma220_power(struct bma220_data *data, bool up) return -EBUSY; } -static int bma220_set_wdt(struct bma220_data *data, const u8 val) -{ - return regmap_update_bits(data->regmap, BMA220_REG_WDT, BMA220_WDT_MASK, - FIELD_PREP(BMA220_WDT_MASK, val)); -} - static int bma220_init(struct device *dev, struct bma220_data *data) { int ret; @@ -477,13 +465,6 @@ static int bma220_init(struct device *dev, struct bma220_data *data) if (ret) return dev_err_probe(dev, ret, "Failed to soft reset chip\n"); - if (i2c_verify_client(dev)) { - ret = bma220_set_wdt(data, BMA220_WDT_1MS); - if (ret) - return dev_err_probe(dev, ret, - "Failed to set i2c watchdog\n"); - } - return 0; } diff --git a/drivers/iio/accel/bma220_i2c.c b/drivers/iio/accel/bma220_i2c.c index 2b85d4921768..8b6f8e305c8c 100644 --- a/drivers/iio/accel/bma220_i2c.c +++ b/drivers/iio/accel/bma220_i2c.c @@ -8,6 +8,7 @@ * I2C address is either 0x0b or 0x0a depending on CSB (pin 10) */ +#include #include #include #include @@ -16,16 +17,27 @@ #include "bma220.h" +static int bma220_set_wdt(struct regmap *regmap, const u8 val) +{ + return regmap_update_bits(regmap, BMA220_REG_WDT, BMA220_WDT_MASK, + FIELD_PREP(BMA220_WDT_MASK, val)); +} + static int bma220_i2c_probe(struct i2c_client *client) { struct regmap *regmap; + int ret; regmap = devm_regmap_init_i2c(client, &bma220_i2c_regmap_config); if (IS_ERR(regmap)) return dev_err_probe(&client->dev, PTR_ERR(regmap), "failed to create regmap\n"); - return bma220_common_probe(&client->dev, regmap, client->irq); + ret = bma220_common_probe(&client->dev, regmap, client->irq); + if (ret) + return ret; + + return bma220_set_wdt(regmap, BMA220_WDT_1MS); } static const struct of_device_id bma220_i2c_match[] = { From f11de95215311816595a1f2ee81cf8239d9b660a Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 21 Oct 2025 10:53:43 +0000 Subject: [PATCH 098/150] dt-bindings: iio: adc: adi,ad4080: add support for AD4083 Add device tree binding support for the AD4083 16-bit SAR ADC. Add adi,ad4083 to the compatible enum. A fallback compatible string to adi,ad4080 is not appropriate as the AD4083 has different resolution (16-bit vs 20-bit) and LVDS CNV clock count maximum (5 vs 7), requiring different driver configuration. Signed-off-by: Antoniu Miclaus Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml index a9fa068189ea..9d2b4b8edf42 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml @@ -27,6 +27,7 @@ properties: enum: - adi,ad4080 - adi,ad4081 + - adi,ad4083 - adi,ad4084 reg: From 4028cbcf3415f6e0810326d87c6e0b494814ae12 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 21 Oct 2025 10:53:44 +0000 Subject: [PATCH 099/150] iio: adc: ad4080: add support for AD4083 Add support for AD4083 16-bit SAR ADC. The AD4083 differs from AD4080 in resolution (16-bit vs 20-bit) and LVDS CNV clock count maximum (5 vs 7). Changes: - Add AD4083_CHIP_ID definition (0x0053) - Create ad4083_channel with 16-bit resolution and storage - Add ad4083_chip_info with lvds_cnv_clk_cnt_max = 5 - Register AD4083 in device ID and OF match tables Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index 5940651655df..e2cdca2e9174 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -127,6 +127,7 @@ #define AD4080_SPI_READ BIT(7) #define AD4080_CHIP_ID 0x0050 #define AD4081_CHIP_ID 0x0051 +#define AD4083_CHIP_ID 0x0053 #define AD4084_CHIP_ID 0x0054 #define AD4080_LVDS_CNV_CLK_CNT_MAX 7 @@ -439,6 +440,8 @@ static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32) static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32); +static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16); + static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16); static const struct ad4080_chip_info ad4080_chip_info = { @@ -461,6 +464,16 @@ static const struct ad4080_chip_info ad4081_chip_info = { .lvds_cnv_clk_cnt_max = 2, }; +static const struct ad4080_chip_info ad4083_chip_info = { + .name = "ad4083", + .product_id = AD4083_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4083_channel, + .lvds_cnv_clk_cnt_max = 5, +}; + static const struct ad4080_chip_info ad4084_chip_info = { .name = "ad4084", .product_id = AD4084_CHIP_ID, @@ -627,6 +640,7 @@ static int ad4080_probe(struct spi_device *spi) static const struct spi_device_id ad4080_id[] = { { "ad4080", (kernel_ulong_t)&ad4080_chip_info }, { "ad4081", (kernel_ulong_t)&ad4081_chip_info }, + { "ad4083", (kernel_ulong_t)&ad4083_chip_info }, { "ad4084", (kernel_ulong_t)&ad4084_chip_info }, { } }; @@ -635,6 +649,7 @@ MODULE_DEVICE_TABLE(spi, ad4080_id); static const struct of_device_id ad4080_of_match[] = { { .compatible = "adi,ad4080", &ad4080_chip_info }, { .compatible = "adi,ad4081", &ad4081_chip_info }, + { .compatible = "adi,ad4083", &ad4083_chip_info }, { .compatible = "adi,ad4084", &ad4084_chip_info }, { } }; From 45e81d6ac0e1af349e5da050dc0be48a72fad9dd Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 21 Oct 2025 10:53:45 +0000 Subject: [PATCH 100/150] dt-bindings: iio: adc: adi,ad4080: add support for AD4086 Add device tree binding support for the AD4086 14-bit SAR ADC. Add adi,ad4086 to the compatible enum. A fallback compatible string to adi,ad4080 is not appropriate as the AD4086 has different resolution (14-bit vs 20-bit) and LVDS CNV clock count maximum (4 vs 7), requiring different driver configuration. Signed-off-by: Antoniu Miclaus Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml index 9d2b4b8edf42..db136bff45b7 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml @@ -29,6 +29,7 @@ properties: - adi,ad4081 - adi,ad4083 - adi,ad4084 + - adi,ad4086 reg: maxItems: 1 From 916354e7c7c08aedff37bdf322e049836aa8d2aa Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 21 Oct 2025 10:53:46 +0000 Subject: [PATCH 101/150] iio: adc: ad4080: add support for AD4086 Add support for AD4086 14-bit SAR ADC. The AD4086 differs from AD4080 in resolution (14-bit vs 20-bit) and LVDS CNV clock count maximum (4 vs 7). Changes: - Add AD4086_CHIP_ID definition (0x0056) - Create ad4086_channel with 14-bit resolution and 16-bit storage - Add ad4086_chip_info with lvds_cnv_clk_cnt_max = 4 - Register AD4086 in device ID and OF match tables Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index e2cdca2e9174..d6d0688adc3d 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -129,6 +129,7 @@ #define AD4081_CHIP_ID 0x0051 #define AD4083_CHIP_ID 0x0053 #define AD4084_CHIP_ID 0x0054 +#define AD4086_CHIP_ID 0x0056 #define AD4080_LVDS_CNV_CLK_CNT_MAX 7 @@ -444,6 +445,8 @@ static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16) static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16); +static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16); + static const struct ad4080_chip_info ad4080_chip_info = { .name = "ad4080", .product_id = AD4080_CHIP_ID, @@ -484,6 +487,16 @@ static const struct ad4080_chip_info ad4084_chip_info = { .lvds_cnv_clk_cnt_max = 2, }; +static const struct ad4080_chip_info ad4086_chip_info = { + .name = "ad4086", + .product_id = AD4086_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4086_channel, + .lvds_cnv_clk_cnt_max = 4, +}; + static int ad4080_setup(struct iio_dev *indio_dev) { struct ad4080_state *st = iio_priv(indio_dev); @@ -642,6 +655,7 @@ static const struct spi_device_id ad4080_id[] = { { "ad4081", (kernel_ulong_t)&ad4081_chip_info }, { "ad4083", (kernel_ulong_t)&ad4083_chip_info }, { "ad4084", (kernel_ulong_t)&ad4084_chip_info }, + { "ad4086", (kernel_ulong_t)&ad4086_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad4080_id); @@ -651,6 +665,7 @@ static const struct of_device_id ad4080_of_match[] = { { .compatible = "adi,ad4081", &ad4081_chip_info }, { .compatible = "adi,ad4083", &ad4083_chip_info }, { .compatible = "adi,ad4084", &ad4084_chip_info }, + { .compatible = "adi,ad4086", &ad4086_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad4080_of_match); From 24e6d7e9f2fce0afc1a63593d1470dbb6df5e9c3 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 21 Oct 2025 10:53:47 +0000 Subject: [PATCH 102/150] dt-bindings: iio: adc: adi,ad4080: add support for AD4087 Add device tree binding support for the AD4087 14-bit SAR ADC. Add adi,ad4087 to the compatible enum. A fallback compatible string to adi,ad4080 is not appropriate as the AD4087 has different resolution (14-bit vs 20-bit) and LVDS CNV clock count maximum (1 vs 7), requiring different driver configuration. Signed-off-by: Antoniu Miclaus Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml index db136bff45b7..ccd6a0ac1539 100644 --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4080.yaml @@ -30,6 +30,7 @@ properties: - adi,ad4083 - adi,ad4084 - adi,ad4086 + - adi,ad4087 reg: maxItems: 1 From 451a9c4a415dae08f88792e6874e85b025eb9420 Mon Sep 17 00:00:00 2001 From: Antoniu Miclaus Date: Tue, 21 Oct 2025 10:53:48 +0000 Subject: [PATCH 103/150] iio: adc: ad4080: add support for AD4087 Add support for AD4087 14-bit SAR ADC. The AD4087 differs from AD4080 in resolution (14-bit vs 20-bit) and LVDS CNV clock count maximum (1 vs 7). Changes: - Add AD4087_CHIP_ID definition (0x0057) - Create ad4087_channel with 14-bit resolution and 16-bit storage - Add ad4087_chip_info with lvds_cnv_clk_cnt_max = 1 - Register AD4087 in device ID and OF match tables Signed-off-by: Antoniu Miclaus Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad4080.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c index d6d0688adc3d..7cf3b6ed7940 100644 --- a/drivers/iio/adc/ad4080.c +++ b/drivers/iio/adc/ad4080.c @@ -130,6 +130,7 @@ #define AD4083_CHIP_ID 0x0053 #define AD4084_CHIP_ID 0x0054 #define AD4086_CHIP_ID 0x0056 +#define AD4087_CHIP_ID 0x0057 #define AD4080_LVDS_CNV_CLK_CNT_MAX 7 @@ -447,6 +448,8 @@ static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16) static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16); +static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16); + static const struct ad4080_chip_info ad4080_chip_info = { .name = "ad4080", .product_id = AD4080_CHIP_ID, @@ -497,6 +500,16 @@ static const struct ad4080_chip_info ad4086_chip_info = { .lvds_cnv_clk_cnt_max = 4, }; +static const struct ad4080_chip_info ad4087_chip_info = { + .name = "ad4087", + .product_id = AD4087_CHIP_ID, + .scale_table = ad4080_scale_table, + .num_scales = ARRAY_SIZE(ad4080_scale_table), + .num_channels = 1, + .channels = &ad4087_channel, + .lvds_cnv_clk_cnt_max = 1, +}; + static int ad4080_setup(struct iio_dev *indio_dev) { struct ad4080_state *st = iio_priv(indio_dev); @@ -656,6 +669,7 @@ static const struct spi_device_id ad4080_id[] = { { "ad4083", (kernel_ulong_t)&ad4083_chip_info }, { "ad4084", (kernel_ulong_t)&ad4084_chip_info }, { "ad4086", (kernel_ulong_t)&ad4086_chip_info }, + { "ad4087", (kernel_ulong_t)&ad4087_chip_info }, { } }; MODULE_DEVICE_TABLE(spi, ad4080_id); @@ -666,6 +680,7 @@ static const struct of_device_id ad4080_of_match[] = { { .compatible = "adi,ad4083", &ad4083_chip_info }, { .compatible = "adi,ad4084", &ad4084_chip_info }, { .compatible = "adi,ad4086", &ad4086_chip_info }, + { .compatible = "adi,ad4087", &ad4087_chip_info }, { } }; MODULE_DEVICE_TABLE(of, ad4080_of_match); From 3ddda1db1514f3cb5efd1c95c35b24fd7e4ef7ff Mon Sep 17 00:00:00 2001 From: Heiko Stuebner Date: Wed, 22 Oct 2025 00:31:07 +0200 Subject: [PATCH 104/150] dt-bindings: iio: adc: Add rockchip,rk3506-saradc variant The SARADC of the RK3506 is similar to the one found in the RK3528 in terms of number of channels and the other implementation details. So add a variant compatible for it, that reflects this fact. Signed-off-by: Heiko Stuebner Acked-by: Conor Dooley Signed-off-by: Jonathan Cameron --- Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml index f776041fd08f..6769d679c907 100644 --- a/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/rockchip-saradc.yaml @@ -16,6 +16,9 @@ properties: - const: rockchip,rk3066-tsadc - const: rockchip,rk3399-saradc - const: rockchip,rk3528-saradc + - items: + - const: rockchip,rk3506-saradc + - const: rockchip,rk3528-saradc - const: rockchip,rk3562-saradc - const: rockchip,rk3588-saradc - items: From c6763b15c49edc4926a8c6cd8cd2f01d49134d74 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 22 Oct 2025 14:02:20 +0300 Subject: [PATCH 105/150] iio: imu: inv_icm45600: Add a missing return statement in probe() The intention here was clearly to return -ENODEV but the return statement was missing. It would result in an off by one read in i3c_chip_info[] on the next line. Add the return statement. Fixes: 1bef24e9007e ("iio: imu: inv_icm45600: add I3C driver for inv_icm45600 driver") Signed-off-by: Dan Carpenter Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c index b5df06b97d44..9247eae9b3e2 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c @@ -57,7 +57,8 @@ static int inv_icm45600_i3c_probe(struct i3c_device *i3cdev) } if (chip == nb_chip) - dev_err_probe(&i3cdev->dev, -ENODEV, "Failed to match part id %d\n", whoami); + return dev_err_probe(&i3cdev->dev, -ENODEV, + "Failed to match part id %d\n", whoami); return inv_icm45600_core_probe(regmap, i3c_chip_info[chip], false, NULL); } From 97289f6accca405d63149e56774912c8be85f76b Mon Sep 17 00:00:00 2001 From: David Lechner Date: Wed, 22 Oct 2025 10:15:05 -0500 Subject: [PATCH 106/150] iio: adc: ad7124: fix possible OOB array access Reorder the channel bounds check before using it to index into the channels array in ad7124_release_config_slot(). This prevents reading past the end of the array. The value read from invalid memory was not used, so this was mostly harmless, but we still should not be reading out of bounds in the first place. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-iio/aPi6V-hcaKReSNWK@stanley.mountain/ Fixes: 9065197e0d41 ("iio: adc: ad7124: change setup reg allocation strategy") Signed-off-by: David Lechner Reviewed-by: Marcelo Schmitt Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ad7124.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c index 9d58ced7371d..ed828a82acb7 100644 --- a/drivers/iio/adc/ad7124.c +++ b/drivers/iio/adc/ad7124.c @@ -586,13 +586,18 @@ static int ad7124_request_config_slot(struct ad7124_state *st, u8 channel) static void ad7124_release_config_slot(struct ad7124_state *st, u8 channel) { - unsigned int slot = st->channels[channel].cfg.cfg_slot; + unsigned int slot; /* - * All of these conditions can happen at probe when all channels are - * disabled. Otherwise, they should not happen normally. + * All of these early return conditions can happen at probe when all + * channels are disabled. Otherwise, they should not happen normally. */ - if (channel >= st->num_channels || slot == AD7124_CFG_SLOT_UNASSIGNED || + if (channel >= st->num_channels) + return; + + slot = st->channels[channel].cfg.cfg_slot; + + if (slot == AD7124_CFG_SLOT_UNASSIGNED || st->cfg_slot_use_count[slot] == 0) return; From f9c30b3e6735bf8ff26414f6452f482d45b179e9 Mon Sep 17 00:00:00 2001 From: Akhilesh Patil Date: Sat, 25 Oct 2025 11:48:41 +0530 Subject: [PATCH 107/150] dt-bindings: iio: pressure: Add Aosong adp810 Add bindings for adp810 differential pressure and temperature sensor. This sensor communicates over I2C with CRC support and can measure pressure in the range -500 to 500Pa and temperature in the range -40 to +85 degree celsius. Signed-off-by: Akhilesh Patil Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../bindings/iio/pressure/aosong,adp810.yaml | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/pressure/aosong,adp810.yaml diff --git a/Documentation/devicetree/bindings/iio/pressure/aosong,adp810.yaml b/Documentation/devicetree/bindings/iio/pressure/aosong,adp810.yaml new file mode 100644 index 000000000000..ad5f26ce5043 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/pressure/aosong,adp810.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/pressure/aosong,adp810.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: aosong adp810 differential pressure sensor + +maintainers: + - Akhilesh Patil + +description: + ADP810 is differential pressure and temperature sensor. It has I2C bus + interface with fixed address of 0x25. This sensor supports 8 bit CRC for + reliable data transfer. It can measure differential pressure in the + range -500 to 500Pa and temperate in the range -40 to +85 degree celsius. + +properties: + compatible: + enum: + - aosong,adp810 + + reg: + maxItems: 1 + + vdd-supply: true + +required: + - compatible + - reg + - vdd-supply + +additionalProperties: false + +examples: + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + pressure-sensor@25 { + compatible = "aosong,adp810"; + reg = <0x25>; + vdd-supply = <&vdd_regulator>; + }; + }; From bb578dd7a01ebbb186dd6d1c27285b29f7fdba37 Mon Sep 17 00:00:00 2001 From: Akhilesh Patil Date: Sat, 25 Oct 2025 11:50:55 +0530 Subject: [PATCH 108/150] iio: pressure: adp810: Add driver for adp810 sensor Add driver for Aosong adp810 differential pressure and temperature sensor. This sensor provides an I2C interface for reading data. Calculate CRC of the data received using standard crc8 library to verify data integrity. Tested on TI am62x sk board with sensor connected at i2c-2. Signed-off-by: Akhilesh Patil Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- MAINTAINERS | 7 ++ drivers/iio/pressure/Kconfig | 12 ++ drivers/iio/pressure/Makefile | 1 + drivers/iio/pressure/adp810.c | 225 ++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 drivers/iio/pressure/adp810.c diff --git a/MAINTAINERS b/MAINTAINERS index 940889b158eb..9f3413e05c83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3745,6 +3745,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/chemical/aosong,ags02ma.yaml F: drivers/iio/chemical/ags02ma.c +AOSONG ADP810 DIFFERENTIAL PRESSURE SENSOR DRIVER +M: Akhilesh Patil +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/pressure/aosong,adp810.yaml +F: drivers/iio/pressure/adp810.c + ASC7621 HARDWARE MONITOR DRIVER M: George Joseph L: linux-hwmon@vger.kernel.org diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig index d2cb8c871f6a..2fe9dc90cceb 100644 --- a/drivers/iio/pressure/Kconfig +++ b/drivers/iio/pressure/Kconfig @@ -339,4 +339,16 @@ config ZPA2326_SPI tristate select REGMAP_SPI +config ADP810 + tristate "Aosong adp810 differential pressure and temperature sensor" + depends on I2C + select CRC8 + help + Say yes here to build adp810 differential pressure and temperature + sensor driver. ADP810 can measure pressure range up to 500Pa. + It supports an I2C interface for data communication. + + To compile this driver as a module, choose M here: the module will + be called adp810 + endmenu diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index 6482288e07ee..47bf7656f975 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -5,6 +5,7 @@ # When adding new entries keep the list in alphabetical order obj-$(CONFIG_ABP060MG) += abp060mg.o +obj-$(CONFIG_ADP810) += adp810.o obj-$(CONFIG_ROHM_BM1390) += rohm-bm1390.o obj-$(CONFIG_BMP280) += bmp280.o bmp280-objs := bmp280-core.o bmp280-regmap.o diff --git a/drivers/iio/pressure/adp810.c b/drivers/iio/pressure/adp810.c new file mode 100644 index 000000000000..5282612d1309 --- /dev/null +++ b/drivers/iio/pressure/adp810.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Akhilesh Patil + * + * Driver for adp810 pressure and temperature sensor + * Datasheet: + * https://aosong.com/userfiles/files/media/Datasheet%20ADP810-Digital.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * Refer section 5.4 checksum calculation from datasheet. + * This sensor uses CRC polynomial x^8 + x^5 + x^4 + 1 (0x31) + */ +#define ADP810_CRC8_POLYNOMIAL 0x31 + +DECLARE_CRC8_TABLE(crc_table); + +/* + * Buffer declaration which holds 9 bytes of measurement data read + * from the sensor. Use __packed to avoid any paddings, as data sent + * from the sensor is strictly contiguous 9 bytes. + */ +struct adp810_read_buf { + __be16 dp; + u8 dp_crc; + __be16 tmp; + u8 tmp_crc; + __be16 sf; + u8 sf_crc; +} __packed; + +struct adp810_data { + struct i2c_client *client; + /* Use lock to synchronize access to device during read sequence */ + struct mutex lock; +}; + +static int adp810_measure(struct adp810_data *data, struct adp810_read_buf *buf) +{ + struct i2c_client *client = data->client; + struct device *dev = &client->dev; + int ret; + u8 trig_cmd[2] = {0x37, 0x2d}; + + /* Send trigger command to the sensor for measurement */ + ret = i2c_master_send(client, trig_cmd, sizeof(trig_cmd)); + if (ret < 0) { + dev_err(dev, "Error sending trigger command\n"); + return ret; + } + if (ret != sizeof(trig_cmd)) + return -EIO; + + /* + * Wait for the sensor to acquire data. As per datasheet section 5.3.1, + * at least 10ms delay before reading from the sensor is recommended. + * Here, we wait for 20ms to have some safe margin on the top + * of recommendation and to compensate for any possible variations. + */ + msleep(20); + + /* Read sensor values */ + ret = i2c_master_recv(client, (char *)buf, sizeof(*buf)); + if (ret < 0) { + dev_err(dev, "Error reading from sensor\n"); + return ret; + } + if (ret != sizeof(*buf)) + return -EIO; + + /* CRC checks */ + crc8_populate_msb(crc_table, ADP810_CRC8_POLYNOMIAL); + if (buf->dp_crc != crc8(crc_table, (u8 *)&buf->dp, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for pressure\n"); + return -EIO; + } + + if (buf->tmp_crc != crc8(crc_table, (u8 *)&buf->tmp, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for temperature\n"); + return -EIO; + } + + if (buf->sf_crc != crc8(crc_table, (u8 *)&buf->sf, 0x2, CRC8_INIT_VALUE)) { + dev_err(dev, "CRC error for scale\n"); + return -EIO; + } + + return 0; +} + +static int adp810_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct adp810_data *data = iio_priv(indio_dev); + struct device *dev = &data->client->dev; + struct adp810_read_buf buf = { }; + int ret; + + scoped_guard(mutex, &data->lock) { + ret = adp810_measure(data, &buf); + if (ret) { + dev_err(dev, "Failed to read from device\n"); + return ret; + } + } + + switch (mask) { + case IIO_CHAN_INFO_RAW: + switch (chan->type) { + case IIO_PRESSURE: + *val = get_unaligned_be16(&buf.dp); + return IIO_VAL_INT; + case IIO_TEMP: + *val = get_unaligned_be16(&buf.tmp); + return IIO_VAL_INT; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->type) { + case IIO_PRESSURE: + *val = get_unaligned_be16(&buf.sf); + return IIO_VAL_INT; + case IIO_TEMP: + *val = 200; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static const struct iio_info adp810_info = { + .read_raw = adp810_read_raw, +}; + +static const struct iio_chan_spec adp810_channels[] = { + { + .type = IIO_PRESSURE, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, + { + .type = IIO_TEMP, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), + }, +}; + +static int adp810_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct iio_dev *indio_dev; + struct adp810_data *data; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*data)); + if (!indio_dev) + return -ENOMEM; + + data = iio_priv(indio_dev); + data->client = client; + + ret = devm_mutex_init(dev, &data->lock); + if (ret) + return ret; + + indio_dev->name = "adp810"; + indio_dev->channels = adp810_channels; + indio_dev->num_channels = ARRAY_SIZE(adp810_channels); + indio_dev->info = &adp810_info; + indio_dev->modes = INDIO_DIRECT_MODE; + + ret = devm_iio_device_register(dev, indio_dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to register IIO device\n"); + + return 0; +} + +static const struct i2c_device_id adp810_id_table[] = { + { "adp810" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adp810_id_table); + +static const struct of_device_id adp810_of_table[] = { + { .compatible = "aosong,adp810" }, + { } +}; +MODULE_DEVICE_TABLE(of, adp810_of_table); + +static struct i2c_driver adp810_driver = { + .driver = { + .name = "adp810", + .of_match_table = adp810_of_table, + }, + .probe = adp810_probe, + .id_table = adp810_id_table, +}; +module_i2c_driver(adp810_driver); + +MODULE_AUTHOR("Akhilesh Patil "); +MODULE_DESCRIPTION("Driver for Aosong ADP810 sensor"); +MODULE_LICENSE("GPL"); From 1d165919c8261b927f8dc8cfe61eb04342bedb7e Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 25 Oct 2025 19:47:59 -0700 Subject: [PATCH 109/150] iio: imu: adis: fix all kernel-doc warnings in header file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct and add to adis.h to resolve all kernel-doc warnings: - add a missing struct member description - change one non-kernel-doc comment to use /* instead of /** - correct function parameter @value to @val (7 locations) - add function return value comments (13 locations) Warning: include/linux/iio/imu/adis.h:97 struct member 'has_fifo' not described in 'adis_data' Warning: include/linux/iio/imu/adis.h:139 Incorrect use of kernel-doc format: * The state_lock is meant to be used during operations that require Warning: include/linux/iio/imu/adis.h:158 struct member '"__adis_"' not described in 'adis' Warning: include/linux/iio/imu/adis.h:264 function parameter 'val' not described in 'adis_write_reg' Warning: include/linux/iio/imu/adis.h:371 No description found for return value of 'adis_update_bits_base' Signed-off-by: Randy Dunlap Reviewed-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- include/linux/iio/imu/adis.h | 45 ++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/include/linux/iio/imu/adis.h b/include/linux/iio/imu/adis.h index aa160511e265..bfb6df68e6c9 100644 --- a/include/linux/iio/imu/adis.h +++ b/include/linux/iio/imu/adis.h @@ -57,6 +57,7 @@ struct adis_timeout { * @enable_irq: Hook for ADIS devices that have a special IRQ enable/disable * @unmasked_drdy: True for devices that cannot mask/unmask the data ready pin * @has_paging: True if ADIS device has paged registers + * @has_fifo: True if ADIS device has a hardware FIFO * @burst_reg_cmd: Register command that triggers burst * @burst_len: Burst size in the SPI RX buffer. If @burst_max_len is defined, * this should be the minimum size supported by the device. @@ -136,7 +137,7 @@ struct adis { const struct adis_data *data; unsigned int burst_extra_len; const struct adis_ops *ops; - /** + /* * The state_lock is meant to be used during operations that require * a sequence of SPI R/W in order to protect the SPI transfer * information (fields 'xfer', 'msg' & 'current_page') between @@ -166,7 +167,7 @@ int __adis_reset(struct adis *adis); * adis_reset() - Reset the device * @adis: The adis device * - * Returns 0 on success, a negative error code otherwise + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_reset(struct adis *adis) { @@ -183,7 +184,9 @@ int __adis_read_reg(struct adis *adis, unsigned int reg, * __adis_write_reg_8() - Write single byte to a register (unlocked) * @adis: The adis device * @reg: The address of the register to be written - * @value: The value to write + * @val: The value to write + * + * Returns: %0 on success, a negative error code otherwise */ static inline int __adis_write_reg_8(struct adis *adis, unsigned int reg, u8 val) @@ -195,7 +198,9 @@ static inline int __adis_write_reg_8(struct adis *adis, unsigned int reg, * __adis_write_reg_16() - Write 2 bytes to a pair of registers (unlocked) * @adis: The adis device * @reg: The address of the lower of the two registers - * @value: Value to be written + * @val: Value to be written + * + * Returns: %0 on success, a negative error code otherwise */ static inline int __adis_write_reg_16(struct adis *adis, unsigned int reg, u16 val) @@ -207,7 +212,9 @@ static inline int __adis_write_reg_16(struct adis *adis, unsigned int reg, * __adis_write_reg_32() - write 4 bytes to four registers (unlocked) * @adis: The adis device * @reg: The address of the lower of the four register - * @value: Value to be written + * @val: Value to be written + * + * Returns: %0 on success, a negative error code otherwise */ static inline int __adis_write_reg_32(struct adis *adis, unsigned int reg, u32 val) @@ -220,6 +227,8 @@ static inline int __adis_write_reg_32(struct adis *adis, unsigned int reg, * @adis: The adis device * @reg: The address of the lower of the two registers * @val: The value read back from the device + * + * Returns: %0 on success, a negative error code otherwise */ static inline int __adis_read_reg_16(struct adis *adis, unsigned int reg, u16 *val) @@ -239,6 +248,8 @@ static inline int __adis_read_reg_16(struct adis *adis, unsigned int reg, * @adis: The adis device * @reg: The address of the lower of the two registers * @val: The value read back from the device + * + * Returns: %0 on success, a negative error code otherwise */ static inline int __adis_read_reg_32(struct adis *adis, unsigned int reg, u32 *val) @@ -257,8 +268,10 @@ static inline int __adis_read_reg_32(struct adis *adis, unsigned int reg, * adis_write_reg() - write N bytes to register * @adis: The adis device * @reg: The address of the lower of the two registers - * @value: The value to write to device (up to 4 bytes) + * @val: The value to write to device (up to 4 bytes) * @size: The size of the @value (in bytes) + * + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_write_reg(struct adis *adis, unsigned int reg, unsigned int val, unsigned int size) @@ -273,6 +286,8 @@ static inline int adis_write_reg(struct adis *adis, unsigned int reg, * @reg: The address of the lower of the two registers * @val: The value read back from the device * @size: The size of the @val buffer + * + * Returns: %0 on success, a negative error code otherwise */ static int adis_read_reg(struct adis *adis, unsigned int reg, unsigned int *val, unsigned int size) @@ -285,7 +300,9 @@ static int adis_read_reg(struct adis *adis, unsigned int reg, * adis_write_reg_8() - Write single byte to a register * @adis: The adis device * @reg: The address of the register to be written - * @value: The value to write + * @val: The value to write + * + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_write_reg_8(struct adis *adis, unsigned int reg, u8 val) @@ -297,7 +314,9 @@ static inline int adis_write_reg_8(struct adis *adis, unsigned int reg, * adis_write_reg_16() - Write 2 bytes to a pair of registers * @adis: The adis device * @reg: The address of the lower of the two registers - * @value: Value to be written + * @val: Value to be written + * + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_write_reg_16(struct adis *adis, unsigned int reg, u16 val) @@ -309,7 +328,9 @@ static inline int adis_write_reg_16(struct adis *adis, unsigned int reg, * adis_write_reg_32() - write 4 bytes to four registers * @adis: The adis device * @reg: The address of the lower of the four register - * @value: Value to be written + * @val: Value to be written + * + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_write_reg_32(struct adis *adis, unsigned int reg, u32 val) @@ -322,6 +343,8 @@ static inline int adis_write_reg_32(struct adis *adis, unsigned int reg, * @adis: The adis device * @reg: The address of the lower of the two registers * @val: The value read back from the device + * + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_read_reg_16(struct adis *adis, unsigned int reg, u16 *val) @@ -341,6 +364,8 @@ static inline int adis_read_reg_16(struct adis *adis, unsigned int reg, * @adis: The adis device * @reg: The address of the lower of the two registers * @val: The value read back from the device + * + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_read_reg_32(struct adis *adis, unsigned int reg, u32 *val) @@ -366,6 +391,8 @@ int __adis_update_bits_base(struct adis *adis, unsigned int reg, const u32 mask, * @size: Size of the register to update * * Updates the desired bits of @reg in accordance with @mask and @val. + * + * Returns: %0 on success, a negative error code otherwise */ static inline int adis_update_bits_base(struct adis *adis, unsigned int reg, const u32 mask, const u32 val, u8 size) From d2e805319bf092ea748cbfe84b775e1f398ed9b3 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 27 Oct 2025 14:50:30 +0200 Subject: [PATCH 110/150] iio: adc: rohm-bd79112: Use regmap_reg_range() Initializing the regmap_ranges using direct assignment to the range_min and range_max members is slightly verbose. We can make it a tad cleaner when using the regmap_reg_range() macro. Clean up the code using regmap_reg_range() when initializing the regmap_range structure. Signed-off-by: Matti Vaittinen Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rohm-bd79112.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/iio/adc/rohm-bd79112.c b/drivers/iio/adc/rohm-bd79112.c index d15e06c8b94d..7420aa6627d5 100644 --- a/drivers/iio/adc/rohm-bd79112.c +++ b/drivers/iio/adc/rohm-bd79112.c @@ -168,15 +168,10 @@ static int _get_gpio_reg(unsigned int offset, unsigned int base) #define GET_GPI_VAL_REG(offset) _get_gpio_reg((offset), BD79112_REG_GPI_VALUE_A0_A7) static const struct regmap_range bd71815_volatile_ro_ranges[] = { - { - /* Read ADC data */ - .range_min = BD79112_REG_AGIO0A, - .range_max = BD79112_REG_AGIO15B, - }, { - /* GPI state */ - .range_min = BD79112_REG_GPI_VALUE_B8_15, - .range_max = BD79112_REG_GPI_VALUE_A0_A7, - }, + /* Read ADC data */ + regmap_reg_range(BD79112_REG_AGIO0A, BD79112_REG_AGIO15B), + /* GPI state */ + regmap_reg_range(BD79112_REG_GPI_VALUE_B8_15, BD79112_REG_GPI_VALUE_A0_A7), }; static const struct regmap_access_table bd79112_volatile_regs = { From a337775ed8e7f9a8cf273cd61a3920f73aa9b422 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Mon, 27 Oct 2025 14:50:51 +0200 Subject: [PATCH 111/150] iio: adc: rohm-bd79124: Use regmap_reg_range() Initializing the regmap_ranges using direct assignment to the range_min and range_max members is slightly verbose. We can make it a tad cleaner when using the regmap_reg_range() macro. Clean up the code using regmap_reg_range() when initializing the regmap_range structure. Signed-off-by: Matti Vaittinen Signed-off-by: Jonathan Cameron --- drivers/iio/adc/rohm-bd79124.c | 39 ++++++++-------------------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/drivers/iio/adc/rohm-bd79124.c b/drivers/iio/adc/rohm-bd79124.c index 06c55c8da93f..fc0452749b79 100644 --- a/drivers/iio/adc/rohm-bd79124.c +++ b/drivers/iio/adc/rohm-bd79124.c @@ -126,13 +126,8 @@ struct bd79124_data { }; static const struct regmap_range bd79124_ro_ranges[] = { - { - .range_min = BD79124_REG_EVENT_FLAG, - .range_max = BD79124_REG_EVENT_FLAG, - }, { - .range_min = BD79124_REG_RECENT_CH0_LSB, - .range_max = BD79124_REG_RECENT_CH7_MSB, - }, + regmap_reg_range(BD79124_REG_EVENT_FLAG, BD79124_REG_EVENT_FLAG), + regmap_reg_range(BD79124_REG_RECENT_CH0_LSB, BD79124_REG_RECENT_CH7_MSB), }; static const struct regmap_access_table bd79124_ro_regs = { @@ -141,22 +136,11 @@ static const struct regmap_access_table bd79124_ro_regs = { }; static const struct regmap_range bd79124_volatile_ranges[] = { - { - .range_min = BD79124_REG_RECENT_CH0_LSB, - .range_max = BD79124_REG_RECENT_CH7_MSB, - }, { - .range_min = BD79124_REG_EVENT_FLAG, - .range_max = BD79124_REG_EVENT_FLAG, - }, { - .range_min = BD79124_REG_EVENT_FLAG_HI, - .range_max = BD79124_REG_EVENT_FLAG_HI, - }, { - .range_min = BD79124_REG_EVENT_FLAG_LO, - .range_max = BD79124_REG_EVENT_FLAG_LO, - }, { - .range_min = BD79124_REG_SYSTEM_STATUS, - .range_max = BD79124_REG_SYSTEM_STATUS, - }, + regmap_reg_range(BD79124_REG_RECENT_CH0_LSB, BD79124_REG_RECENT_CH7_MSB), + regmap_reg_range(BD79124_REG_EVENT_FLAG, BD79124_REG_EVENT_FLAG), + regmap_reg_range(BD79124_REG_EVENT_FLAG_HI, BD79124_REG_EVENT_FLAG_HI), + regmap_reg_range(BD79124_REG_EVENT_FLAG_LO, BD79124_REG_EVENT_FLAG_LO), + regmap_reg_range(BD79124_REG_SYSTEM_STATUS, BD79124_REG_SYSTEM_STATUS), }; static const struct regmap_access_table bd79124_volatile_regs = { @@ -165,13 +149,8 @@ static const struct regmap_access_table bd79124_volatile_regs = { }; static const struct regmap_range bd79124_precious_ranges[] = { - { - .range_min = BD79124_REG_EVENT_FLAG_HI, - .range_max = BD79124_REG_EVENT_FLAG_HI, - }, { - .range_min = BD79124_REG_EVENT_FLAG_LO, - .range_max = BD79124_REG_EVENT_FLAG_LO, - }, + regmap_reg_range(BD79124_REG_EVENT_FLAG_HI, BD79124_REG_EVENT_FLAG_HI), + regmap_reg_range(BD79124_REG_EVENT_FLAG_LO, BD79124_REG_EVENT_FLAG_LO), }; static const struct regmap_access_table bd79124_precious_regs = { From 759cafdd8b4997b4f266b67ea5684373722742f2 Mon Sep 17 00:00:00 2001 From: Jack Hsu Date: Thu, 30 Oct 2025 21:44:34 +0800 Subject: [PATCH 112/150] dt-bindings: iio: adc: Support MediaTek MT8189 evb board auxadc add compatible string for mt8189 evb board dts node of auxadc Signed-off-by: Jack Hsu Acked-by: Conor Dooley Reviewed-by: AngeloGioacchino Del Regno Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml b/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml index 14363389f30a..d9e825e5054f 100644 --- a/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/mediatek,mt2701-auxadc.yaml @@ -42,6 +42,7 @@ properties: - mediatek,mt8183-auxadc - mediatek,mt8186-auxadc - mediatek,mt8188-auxadc + - mediatek,mt8189-auxadc - mediatek,mt8195-auxadc - mediatek,mt8516-auxadc - const: mediatek,mt8173-auxadc From 59f40887509d258d1ac40300f6b224cac853a6d1 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 29 Oct 2025 15:27:18 -0400 Subject: [PATCH 113/150] dt-bindings: iio: imu: mpu6050: remove interrupts from required list Irq is optional signal to make sensor work. Not all boards connect this signals, so remove it from required list. Signed-off-by: Frank Li Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/imu/invensense,mpu6050.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml b/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml index 0bce71529e34..1af0855c33e6 100644 --- a/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml +++ b/Documentation/devicetree/bindings/iio/imu/invensense,mpu6050.yaml @@ -86,7 +86,6 @@ unevaluatedProperties: false required: - compatible - reg - - interrupts examples: - | From 49708f45be675d726b6c39a0eee260c0df1f77bc Mon Sep 17 00:00:00 2001 From: Kriish Sharma Date: Tue, 28 Oct 2025 09:33:26 +0000 Subject: [PATCH 114/150] iio: backend: fix kernel-doc to avoid warnings and ensure consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix multiple kernel-doc warnings and make the documentation style consistent in drivers/iio/industrialio-backend.c. Changes include: - Add missing @chan parameter description in iio_backend_oversampling_ratio_set(). - Add missing RETURNS section in iio_backend_get_priv(). - Replace Return: with “RETURNS:” across the file for consistency. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202506292344.HLJbrrgR-lkp@intel.com Suggested-by: Andy Shevchenko Suggested-by: David Lechner Signed-off-by: Kriish Sharma Reviewed-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-backend.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-backend.c b/drivers/iio/industrialio-backend.c index 23760652a046..447b694d6d5f 100644 --- a/drivers/iio/industrialio-backend.c +++ b/drivers/iio/industrialio-backend.c @@ -702,7 +702,7 @@ EXPORT_SYMBOL_NS_GPL(iio_backend_interface_type_get, "IIO_BACKEND"); * interface/data bus. Hence, the backend device needs to be aware of it so * data can be correctly transferred. * - * Return: + * RETURNS: * 0 on success, negative error number on failure. */ int iio_backend_data_size_set(struct iio_backend *back, unsigned int size) @@ -717,9 +717,10 @@ EXPORT_SYMBOL_NS_GPL(iio_backend_data_size_set, "IIO_BACKEND"); /** * iio_backend_oversampling_ratio_set - set the oversampling ratio * @back: Backend device + * @chan: Channel number * @ratio: The oversampling ratio - value 1 corresponds to no oversampling. * - * Return: + * RETURNS: * 0 on success, negative error number on failure. */ int iio_backend_oversampling_ratio_set(struct iio_backend *back, @@ -1064,6 +1065,9 @@ EXPORT_SYMBOL_NS_GPL(__devm_iio_backend_get_from_fwnode_lookup, "IIO_BACKEND"); /** * iio_backend_get_priv - Get driver private data * @back: Backend device + * + * RETURNS: + * Pointer to the driver private data associated with the backend. */ void *iio_backend_get_priv(const struct iio_backend *back) { From e5191f62a67aada464ff2c4ab6d96a657b1486aa Mon Sep 17 00:00:00 2001 From: Chu Guangqing Date: Fri, 24 Oct 2025 15:38:20 +0800 Subject: [PATCH 115/150] iio: adc: ade9000: convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Chu Guangqing Signed-off-by: Jonathan Cameron --- drivers/iio/adc/ade9000.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/ade9000.c b/drivers/iio/adc/ade9000.c index 94e05e11abd9..2de8a718d62a 100644 --- a/drivers/iio/adc/ade9000.c +++ b/drivers/iio/adc/ade9000.c @@ -1629,7 +1629,7 @@ static const struct regmap_config ade9000_regmap_config = { .val_bits = 32, .max_register = 0x6bc, .zero_flag_mask = true, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .reg_read = ade9000_spi_read_reg, .reg_write = ade9000_spi_write_reg, .volatile_reg = ade9000_is_volatile_reg, From 00d3bd9e3f7166220838d7065e52bffc85b871df Mon Sep 17 00:00:00 2001 From: Chu Guangqing Date: Fri, 24 Oct 2025 15:38:21 +0800 Subject: [PATCH 116/150] iio: light: veml3235: convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Chu Guangqing Signed-off-by: Jonathan Cameron --- drivers/iio/light/veml3235.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/veml3235.c b/drivers/iio/light/veml3235.c index 77c9ae17ed47..9309ad83ca9e 100644 --- a/drivers/iio/light/veml3235.c +++ b/drivers/iio/light/veml3235.c @@ -154,7 +154,7 @@ static const struct regmap_config veml3235_regmap_config = { .rd_table = &veml3235_readable_table, .wr_table = &veml3235_writable_table, .volatile_table = &veml3235_volatile_table, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static int veml3235_get_it(struct veml3235_data *data, int *val, int *val2) From 52d182b2ff99d5e50e763ad5a5a874da3b49434f Mon Sep 17 00:00:00 2001 From: Chu Guangqing Date: Fri, 24 Oct 2025 15:38:22 +0800 Subject: [PATCH 117/150] iio: light: apds9306: convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Chu Guangqing Acked-by: Subhajit Ghosh Signed-off-by: Jonathan Cameron --- drivers/iio/light/apds9306.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/apds9306.c b/drivers/iio/light/apds9306.c index 389125675caa..7e68cca0edfa 100644 --- a/drivers/iio/light/apds9306.c +++ b/drivers/iio/light/apds9306.c @@ -350,7 +350,7 @@ static const struct regmap_config apds9306_regmap = { .volatile_table = &apds9306_volatile_table, .precious_table = &apds9306_precious_table, .max_register = APDS9306_ALS_THRES_VAR_REG, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static const struct reg_field apds9306_rf_sw_reset = From 698dcf54282a221588281b2e1e6107707cc61f37 Mon Sep 17 00:00:00 2001 From: Chu Guangqing Date: Fri, 24 Oct 2025 15:38:23 +0800 Subject: [PATCH 118/150] iio: light: apds9960: convert to use maple tree register cache The maple tree register cache is based on a much more modern data structure than the rbtree cache and makes optimisation choices which are probably more appropriate for modern systems than those made by the rbtree cache. Signed-off-by: Chu Guangqing Signed-off-by: Jonathan Cameron --- drivers/iio/light/apds9960.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/light/apds9960.c b/drivers/iio/light/apds9960.c index 79b202c59a0f..785c5dbe2d08 100644 --- a/drivers/iio/light/apds9960.c +++ b/drivers/iio/light/apds9960.c @@ -234,7 +234,7 @@ static const struct regmap_config apds9960_regmap_config = { .reg_defaults = apds9960_reg_defaults, .num_reg_defaults = ARRAY_SIZE(apds9960_reg_defaults), .max_register = APDS9960_REG_GFIFO_DIR(RIGHT), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static const struct iio_event_spec apds9960_pxs_event_spec[] = { From 02f86101e430cce9a99a044b483c4ed5b91bb3b8 Mon Sep 17 00:00:00 2001 From: Rodrigo Gobbi Date: Sun, 2 Nov 2025 19:30:18 -0300 Subject: [PATCH 119/150] iio: imu: bmi270: fix dev_err_probe error msg The bmi270 can be connected to I2C or a SPI interface. If it is a SPI, during probe, if devm_regmap_init() fails, it should print the "spi" term rather "i2c". Fixes: 92cc50a00574 ("iio: imu: bmi270: Add spi driver for bmi270 imu") Signed-off-by: Rodrigo Gobbi Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/bmi270/bmi270_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/imu/bmi270/bmi270_spi.c b/drivers/iio/imu/bmi270/bmi270_spi.c index 19dd7734f9d0..80c9fa1d685a 100644 --- a/drivers/iio/imu/bmi270/bmi270_spi.c +++ b/drivers/iio/imu/bmi270/bmi270_spi.c @@ -60,7 +60,7 @@ static int bmi270_spi_probe(struct spi_device *spi) &bmi270_spi_regmap_config); if (IS_ERR(regmap)) return dev_err_probe(dev, PTR_ERR(regmap), - "Failed to init i2c regmap"); + "Failed to init spi regmap\n"); return bmi270_core_probe(dev, regmap, chip_info); } From 69f5dcaa990e3f43c89fc5cec4ae92ececdb12f3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 3 Nov 2025 09:29:11 +0100 Subject: [PATCH 120/150] iio: common: scmi_sensors: Get rid of const_ilog2() Fisrt of all, const_ilog2() was a workaround of some sparse issue, which was never appeared in the C functions. Second, the calls here are done against constants and work with a bit of luck. Replace this altogether by a pre-calculated simple integer constant. Amend a comment to give a hint where it comes from. Signed-off-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/common/scmi_sensors/scmi_iio.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/iio/common/scmi_sensors/scmi_iio.c b/drivers/iio/common/scmi_sensors/scmi_iio.c index 39c61c47022a..5136ad9ada04 100644 --- a/drivers/iio/common/scmi_sensors/scmi_iio.c +++ b/drivers/iio/common/scmi_sensors/scmi_iio.c @@ -66,10 +66,9 @@ static int scmi_iio_sensor_update_cb(struct notifier_block *nb, /* * Timestamp returned by SCMI is in seconds and is equal to * time * power-of-10 multiplier(tstamp_scale) seconds. - * Converting the timestamp to nanoseconds below. + * Converting the timestamp to nanoseconds (10⁹) below. */ - tstamp_scale = sensor->sensor_info->tstamp_scale + - const_ilog2(NSEC_PER_SEC) / const_ilog2(10); + tstamp_scale = sensor->sensor_info->tstamp_scale + 9; if (tstamp_scale < 0) { do_div(time, int_pow(10, abs(tstamp_scale))); time_ns = time; From 436cd7712598e9ca07c76f14d1531a1f6c3c502f Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Mon, 3 Nov 2025 18:52:16 +0800 Subject: [PATCH 121/150] dt-bindings: iio: adc: Add AST2700 ADC compatible strings Add the compatible strings "aspeed,ast2700-adc0" and "aspeed,ast2700-adc1" to the binding for the Aspeed ADC. These new compatibles represent the ADC instances found on the AST2700 SoC, which are similar to the AST2600 but have their trimming data located at different SCU offset. Signed-off-by: Billy Tsai Acked-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml index 5c08d8b6e995..509bfb1007c4 100644 --- a/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml +++ b/Documentation/devicetree/bindings/iio/adc/aspeed,ast2600-adc.yaml @@ -29,6 +29,8 @@ properties: enum: - aspeed,ast2600-adc0 - aspeed,ast2600-adc1 + - aspeed,ast2700-adc0 + - aspeed,ast2700-adc1 description: Their trimming data, which is used to calibrate internal reference volage, locates in different address of OTP. From 9d8b88ffb773470ea3a324b67e77e64c0fa8400d Mon Sep 17 00:00:00 2001 From: Billy Tsai Date: Mon, 3 Nov 2025 18:52:17 +0800 Subject: [PATCH 122/150] iio: adc: aspeed: Add AST2700 ADC support This patch adds support for the ADCs found on the Aspeed AST2700 SoC, which includes two instances: "ast2700-adc0" and "ast2700-adc1". While they are functionally similar to those on AST2600, the OTP trimming data is located at the same offset (0x820), but uses different bitfields. Signed-off-by: Billy Tsai Reviewed-by: David Lechner Signed-off-by: Jonathan Cameron --- drivers/iio/adc/aspeed_adc.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/drivers/iio/adc/aspeed_adc.c b/drivers/iio/adc/aspeed_adc.c index 1d5fd5f534b8..bf2bfd6bdc41 100644 --- a/drivers/iio/adc/aspeed_adc.c +++ b/drivers/iio/adc/aspeed_adc.c @@ -645,6 +645,16 @@ static const struct aspeed_adc_trim_locate ast2600_adc1_trim = { .field = GENMASK(7, 4), }; +static const struct aspeed_adc_trim_locate ast2700_adc0_trim = { + .offset = 0x820, + .field = GENMASK(3, 0), +}; + +static const struct aspeed_adc_trim_locate ast2700_adc1_trim = { + .offset = 0x820, + .field = GENMASK(7, 4), +}; + static const struct aspeed_adc_model_data ast2400_model_data = { .model_name = "ast2400-adc", .vref_fixed_mv = 2500, @@ -689,11 +699,35 @@ static const struct aspeed_adc_model_data ast2600_adc1_model_data = { .trim_locate = &ast2600_adc1_trim, }; +static const struct aspeed_adc_model_data ast2700_adc0_model_data = { + .model_name = "ast2700-adc0", + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, + .wait_init_sequence = true, + .bat_sense_sup = true, + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2700_adc0_trim, +}; + +static const struct aspeed_adc_model_data ast2700_adc1_model_data = { + .model_name = "ast2700-adc1", + .min_sampling_rate = 10000, + .max_sampling_rate = 500000, + .wait_init_sequence = true, + .bat_sense_sup = true, + .scaler_bit_width = 16, + .num_channels = 8, + .trim_locate = &ast2700_adc1_trim, +}; + static const struct of_device_id aspeed_adc_matches[] = { { .compatible = "aspeed,ast2400-adc", .data = &ast2400_model_data }, { .compatible = "aspeed,ast2500-adc", .data = &ast2500_model_data }, { .compatible = "aspeed,ast2600-adc0", .data = &ast2600_adc0_model_data }, { .compatible = "aspeed,ast2600-adc1", .data = &ast2600_adc1_model_data }, + { .compatible = "aspeed,ast2700-adc0", .data = &ast2700_adc0_model_data }, + { .compatible = "aspeed,ast2700-adc1", .data = &ast2700_adc1_model_data }, { } }; MODULE_DEVICE_TABLE(of, aspeed_adc_matches); From 77538d110933de556c8a2dae7c04fd459db58d8b Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Mon, 3 Nov 2025 15:18:31 +0100 Subject: [PATCH 123/150] dt-bindings: iio: adc: Add the Renesas RZ/N1 ADC The Renesas RZ/N1 ADC controller is the ADC controller available in the Renesas RZ/N1 SoCs family. Signed-off-by: Herve Codina (Schneider Electric) Reviewed-by: Rob Herring (Arm) Signed-off-by: Jonathan Cameron --- .../bindings/iio/adc/renesas,rzn1-adc.yaml | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml diff --git a/Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml b/Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml new file mode 100644 index 000000000000..1a40352165fb --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/renesas,rzn1-adc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Renesas RZ/N1 Analog to Digital Converter (ADC) + +maintainers: + - Herve Codina + +description: + The Renesas RZ/N1 ADC controller available in the Renesas RZ/N1 SoCs family + can use up to two internal ADC cores (ADC1 and ADC2) those internal cores are + handled through ADC controller virtual channels. + +properties: + compatible: + items: + - const: renesas,r9a06g032-adc # RZ/N1D + - const: renesas,rzn1-adc + + reg: + maxItems: 1 + + clocks: + items: + - description: APB internal bus clock + - description: ADC clock + + clock-names: + items: + - const: pclk + - const: adc + + power-domains: + maxItems: 1 + + adc1-avdd-supply: + description: + ADC1 analog power supply. + + adc1-vref-supply: + description: + ADC1 reference voltage supply. + + adc2-avdd-supply: + description: + ADC2 analog power supply. + + adc2-vref-supply: + description: + ADC2 reference voltage supply. + + '#io-channel-cells': + const: 1 + description: | + Channels numbers available: + if ADC1 is used (i.e. adc1-{avdd,vref}-supply present): + - 0: ADC1 IN0 + - 1: ADC1 IN1 + - 2: ADC1 IN2 + - 3: ADC1 IN3 + - 4: ADC1 IN4 + - 5: ADC1 IN6 + - 6: ADC1 IN7 + - 7: ADC1 IN8 + if ADC2 is used (i.e. adc2-{avdd,vref}-supply present): + - 8: ADC2 IN0 + - 9: ADC2 IN1 + - 10: ADC2 IN2 + - 11: ADC2 IN3 + - 12: ADC2 IN4 + - 13: ADC2 IN6 + - 14: ADC2 IN7 + - 15: ADC2 IN8 + +required: + - compatible + - reg + - clocks + - clock-names + - power-domains + - '#io-channel-cells' + +# At least one of avvd/vref supplies +anyOf: + - required: + - adc1-vref-supply + - adc1-avdd-supply + - required: + - adc2-vref-supply + - adc2-avdd-supply + +additionalProperties: false + +examples: + - | + #include + + adc: adc@40065000 { + compatible = "renesas,r9a06g032-adc", "renesas,rzn1-adc"; + reg = <0x40065000 0x200>; + clocks = <&sysctrl R9A06G032_HCLK_ADC>, <&sysctrl R9A06G032_CLK_ADC>; + clock-names = "pclk", "adc"; + power-domains = <&sysctrl>; + adc1-avdd-supply = <&adc1_avdd>; + adc1-vref-supply = <&adc1_vref>; + #io-channel-cells = <1>; + }; +... From 2387a7d6e5ab81cded0310f2d7544f4470666a6c Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Mon, 3 Nov 2025 15:18:32 +0100 Subject: [PATCH 124/150] iio: adc: Add support for the Renesas RZ/N1 ADC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Renesas RZ/N1 ADC controller is the ADC controller available in the Renesas RZ/N1 SoCs family. It can use up to two internal ADC cores (ADC1 and ADC2) those internal cores are not directly accessed but are handled through ADC controller virtual channels. Signed-off-by: Herve Codina (Schneider Electric) Reviewed-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/adc/Kconfig | 10 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/rzn1-adc.c | 490 +++++++++++++++++++++++++++++++++++++ 3 files changed, 501 insertions(+) create mode 100644 drivers/iio/adc/rzn1-adc.c diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 31335af6b2f1..58da8255525e 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1413,6 +1413,16 @@ config RZG2L_ADC To compile this driver as a module, choose M here: the module will be called rzg2l_adc. +config RZN1_ADC + tristate "Renesas RZ/N1 ADC driver" + depends on ARCH_RZN1 || COMPILE_TEST + help + Say yes here to build support for the ADC found in Renesas + RZ/N1 family. + + To compile this driver as a module, choose M here: the + module will be called rzn1-adc. + config RZT2H_ADC tristate "Renesas RZ/T2H / RZ/N2H ADC driver" depends on ARCH_RENESAS || COMPILE_TEST diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index e5349b01e4d9..7cc8f9a12f76 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -124,6 +124,7 @@ obj-$(CONFIG_ROHM_BD79112) += rohm-bd79112.o obj-$(CONFIG_ROHM_BD79124) += rohm-bd79124.o obj-$(CONFIG_ROCKCHIP_SARADC) += rockchip_saradc.o obj-$(CONFIG_RZG2L_ADC) += rzg2l_adc.o +obj-$(CONFIG_RZN1_ADC) += rzn1-adc.o obj-$(CONFIG_RZT2H_ADC) += rzt2h_adc.o obj-$(CONFIG_SC27XX_ADC) += sc27xx_adc.o obj-$(CONFIG_SD_ADC_MODULATOR) += sd_adc_modulator.o diff --git a/drivers/iio/adc/rzn1-adc.c b/drivers/iio/adc/rzn1-adc.c new file mode 100644 index 000000000000..93b0feef8ea0 --- /dev/null +++ b/drivers/iio/adc/rzn1-adc.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Renesas RZ/N1 ADC driver + * + * Copyright (C) 2025 Schneider-Electric + * + * Author: Herve Codina + * + * The RZ/N1 ADC controller can handle channels from its internal ADC1 and/or + * ADC2 cores. The driver use ADC1 and/or ADC2 cores depending on the presence + * of the related power supplies (AVDD and VREF) description in the device-tree. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RZN1_ADC_CONTROL_REG 0x02c +#define RZN1_ADC_CONTROL_ADC_BUSY BIT(6) + +#define RZN1_ADC_FORCE_REG 0x030 +#define RZN1_ADC_SET_FORCE_REG 0x034 +#define RZN1_ADC_CLEAR_FORCE_REG 0x038 +#define RZN1_ADC_FORCE_VC(_n) BIT(_n) + +#define RZN1_ADC_CONFIG_REG 0x040 +#define RZN1_ADC_CONFIG_ADC_POWER_DOWN BIT(3) + +#define RZN1_ADC_VC_REG(_n) (0x0c0 + 4 * (_n)) +#define RZN1_ADC_VC_ADC2_ENABLE BIT(16) +#define RZN1_ADC_VC_ADC1_ENABLE BIT(15) +#define RZN1_ADC_VC_ADC2_CHANNEL_SEL_MASK GENMASK(5, 3) +#define RZN1_ADC_VC_ADC1_CHANNEL_SEL_MASK GENMASK(2, 0) + +#define RZN1_ADC_ADC1_DATA_REG(_n) (0x100 + 4 * (_n)) +#define RZN1_ADC_ADC2_DATA_REG(_n) (0x140 + 4 * (_n)) +#define RZN1_ADC_ADCX_DATA_DATA_MASK GENMASK(11, 0) + +#define RZN1_ADC_NO_CHANNEL -1 + +#define RZN1_ADC_CHANNEL_SHARED_SCALE(_ch, _ds_name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = (_ds_name), \ +} + +#define RZN1_ADC_CHANNEL_SEPARATED_SCALE(_ch, _ds_name) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .channel = (_ch), \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .datasheet_name = (_ds_name), \ +} + +/* + * 8 ADC1_IN signals existed numbered 0..4, 6..8 + * ADCx_IN5 doesn't exist in RZ/N1 datasheet + */ +static struct iio_chan_spec rzn1_adc1_channels[] = { + RZN1_ADC_CHANNEL_SHARED_SCALE(0, "ADC1_IN0"), + RZN1_ADC_CHANNEL_SHARED_SCALE(1, "ADC1_IN1"), + RZN1_ADC_CHANNEL_SHARED_SCALE(2, "ADC1_IN2"), + RZN1_ADC_CHANNEL_SHARED_SCALE(3, "ADC1_IN3"), + RZN1_ADC_CHANNEL_SHARED_SCALE(4, "ADC1_IN4"), + RZN1_ADC_CHANNEL_SHARED_SCALE(5, "ADC1_IN6"), + RZN1_ADC_CHANNEL_SHARED_SCALE(6, "ADC1_IN7"), + RZN1_ADC_CHANNEL_SHARED_SCALE(7, "ADC1_IN8"), +}; + +static struct iio_chan_spec rzn1_adc2_channels[] = { + RZN1_ADC_CHANNEL_SHARED_SCALE(8, "ADC2_IN0"), + RZN1_ADC_CHANNEL_SHARED_SCALE(9, "ADC2_IN1"), + RZN1_ADC_CHANNEL_SHARED_SCALE(10, "ADC2_IN2"), + RZN1_ADC_CHANNEL_SHARED_SCALE(11, "ADC2_IN3"), + RZN1_ADC_CHANNEL_SHARED_SCALE(12, "ADC2_IN4"), + RZN1_ADC_CHANNEL_SHARED_SCALE(13, "ADC2_IN6"), + RZN1_ADC_CHANNEL_SHARED_SCALE(14, "ADC2_IN7"), + RZN1_ADC_CHANNEL_SHARED_SCALE(15, "ADC2_IN8"), +}; + +/* + * If both ADCs core are used, scale cannot be common. Indeed, scale is + * based on Vref connected on each ADC core. + */ +static struct iio_chan_spec rzn1_adc1_adc2_channels[] = { + RZN1_ADC_CHANNEL_SEPARATED_SCALE(0, "ADC1_IN0"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(1, "ADC1_IN1"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(2, "ADC1_IN2"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(3, "ADC1_IN3"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(4, "ADC1_IN4"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(5, "ADC1_IN6"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(6, "ADC1_IN7"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(7, "ADC1_IN8"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(8, "ADC2_IN0"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(9, "ADC2_IN1"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(10, "ADC2_IN2"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(11, "ADC2_IN3"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(12, "ADC2_IN4"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(13, "ADC2_IN6"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(14, "ADC2_IN7"), + RZN1_ADC_CHANNEL_SEPARATED_SCALE(15, "ADC2_IN8"), +}; + +struct rzn1_adc { + struct device *dev; + void __iomem *regs; + struct mutex lock; /* ADC lock */ + int adc1_vref_mV; /* ADC1 Vref in mV. Negative if ADC1 is not used */ + int adc2_vref_mV; /* ADC2 Vref in mV. Negative if ADC2 is not used */ +}; + +static int rzn1_adc_power(struct rzn1_adc *rzn1_adc, bool power) +{ + u32 v; + + writel(power ? 0 : RZN1_ADC_CONFIG_ADC_POWER_DOWN, + rzn1_adc->regs + RZN1_ADC_CONFIG_REG); + + /* Wait for the ADC_BUSY to clear */ + return readl_poll_timeout_atomic(rzn1_adc->regs + RZN1_ADC_CONTROL_REG, + v, !(v & RZN1_ADC_CONTROL_ADC_BUSY), + 0, 500); +} + +static void rzn1_adc_vc_setup_conversion(struct rzn1_adc *rzn1_adc, u32 ch, + int adc1_ch, int adc2_ch) +{ + u32 vc = 0; + + if (adc1_ch != RZN1_ADC_NO_CHANNEL) + vc |= RZN1_ADC_VC_ADC1_ENABLE | + FIELD_PREP(RZN1_ADC_VC_ADC1_CHANNEL_SEL_MASK, adc1_ch); + + if (adc2_ch != RZN1_ADC_NO_CHANNEL) + vc |= RZN1_ADC_VC_ADC2_ENABLE | + FIELD_PREP(RZN1_ADC_VC_ADC2_CHANNEL_SEL_MASK, adc2_ch); + + writel(vc, rzn1_adc->regs + RZN1_ADC_VC_REG(ch)); +} + +static int rzn1_adc_vc_start_conversion(struct rzn1_adc *rzn1_adc, u32 ch) +{ + u32 val; + + val = readl(rzn1_adc->regs + RZN1_ADC_FORCE_REG); + if (val & RZN1_ADC_FORCE_VC(ch)) + return -EBUSY; + + writel(RZN1_ADC_FORCE_VC(ch), rzn1_adc->regs + RZN1_ADC_SET_FORCE_REG); + + return 0; +} + +static void rzn1_adc_vc_stop_conversion(struct rzn1_adc *rzn1_adc, u32 ch) +{ + writel(RZN1_ADC_FORCE_VC(ch), rzn1_adc->regs + RZN1_ADC_CLEAR_FORCE_REG); +} + +static int rzn1_adc_vc_wait_conversion(struct rzn1_adc *rzn1_adc, u32 ch, + u32 *adc1_data, u32 *adc2_data) +{ + u32 data_reg; + int ret; + u32 v; + + /* + * When a VC is selected, it needs 20 ADC clocks to perform the + * conversion. + * + * The worst case is when the 16 VCs need to perform a conversion and + * our VC is the lowest in term of priority. + * + * In that case, the conversion is performed in 16 * 20 ADC clocks. + * + * The ADC clock can be set from 4MHz to 20MHz. This leads to a worst + * case of 16 * 20 * 1/4Mhz = 80us. + * + * Round it up to 100us. + */ + + /* Wait for the ADC_FORCE_VC(n) to clear */ + ret = readl_poll_timeout_atomic(rzn1_adc->regs + RZN1_ADC_FORCE_REG, + v, !(v & RZN1_ADC_FORCE_VC(ch)), + 0, 100); + if (ret) + return ret; + + if (adc1_data) { + data_reg = readl(rzn1_adc->regs + RZN1_ADC_ADC1_DATA_REG(ch)); + *adc1_data = FIELD_GET(RZN1_ADC_ADCX_DATA_DATA_MASK, data_reg); + } + + if (adc2_data) { + data_reg = readl(rzn1_adc->regs + RZN1_ADC_ADC2_DATA_REG(ch)); + *adc2_data = FIELD_GET(RZN1_ADC_ADCX_DATA_DATA_MASK, data_reg); + } + + return 0; +} + +static int rzn1_adc_read_raw_ch(struct rzn1_adc *rzn1_adc, unsigned int chan, int *val) +{ + u32 *adc1_data, *adc2_data; + int adc1_ch, adc2_ch; + u32 adc_data; + int ret; + + /* + * IIO chan are decoupled from chans used in rzn1_adc_vc_*() functions. + * The RZ/N1 ADC VC controller can handle on a single VC chan one + * channel from the ADC1 core and one channel from the ADC2 core. + * + * Even if IIO chans are mapped 1:1 to ADC core chans and so uses only + * a chan from ADC1 or a chan from ADC2, future improvements can define + * an IIO chan that uses one chan from ADC1 and one chan from ADC2. + */ + + if (chan < 8) { + /* chan 0..7 used to get ADC1 ch 0..7 */ + adc1_ch = chan; + adc1_data = &adc_data; + adc2_ch = RZN1_ADC_NO_CHANNEL; + adc2_data = NULL; + } else if (chan < 16) { + /* chan 8..15 used to get ADC2 ch 0..7 */ + adc1_ch = RZN1_ADC_NO_CHANNEL; + adc1_data = NULL; + adc2_ch = chan - 8; + adc2_data = &adc_data; + } else { + return -EINVAL; + } + + ACQUIRE(pm_runtime_active_auto_try_enabled, pm)(rzn1_adc->dev); + ret = ACQUIRE_ERR(pm_runtime_active_auto_try_enabled, &pm); + if (ret < 0) + return ret; + + scoped_guard(mutex, &rzn1_adc->lock) { + rzn1_adc_vc_setup_conversion(rzn1_adc, chan, adc1_ch, adc2_ch); + + ret = rzn1_adc_vc_start_conversion(rzn1_adc, chan); + if (ret) + return ret; + + ret = rzn1_adc_vc_wait_conversion(rzn1_adc, chan, adc1_data, adc2_data); + if (ret) { + rzn1_adc_vc_stop_conversion(rzn1_adc, chan); + return ret; + } + } + + *val = adc_data; + ret = IIO_VAL_INT; + + return 0; +} + +static int rzn1_adc_get_vref_mV(struct rzn1_adc *rzn1_adc, unsigned int chan) +{ + /* chan 0..7 use ADC1 ch 0..7. Vref related to ADC1 core */ + if (chan < 8) + return rzn1_adc->adc1_vref_mV; + + /* chan 8..15 use ADC2 ch 0..7. Vref related to ADC2 core */ + if (chan < 16) + return rzn1_adc->adc2_vref_mV; + + return -EINVAL; +} + +static int rzn1_adc_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct rzn1_adc *rzn1_adc = iio_priv(indio_dev); + int ret; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = rzn1_adc_read_raw_ch(rzn1_adc, chan->channel, val); + if (ret) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + ret = rzn1_adc_get_vref_mV(rzn1_adc, chan->channel); + if (ret < 0) + return ret; + *val = ret; + *val2 = 12; + return IIO_VAL_FRACTIONAL_LOG2; + + default: + return -EINVAL; + } +} + +static const struct iio_info rzn1_adc_info = { + .read_raw = &rzn1_adc_read_raw, +}; + +static int rzn1_adc_set_iio_dev_channels(struct rzn1_adc *rzn1_adc, + struct iio_dev *indio_dev) +{ + /* + * When an ADC core is not used, its related vref_mV is set to a + * negative error code. Use the correct IIO channels table based on + * those vref_mV values. + */ + if (rzn1_adc->adc1_vref_mV >= 0) { + if (rzn1_adc->adc2_vref_mV >= 0) { + indio_dev->channels = rzn1_adc1_adc2_channels; + indio_dev->num_channels = ARRAY_SIZE(rzn1_adc1_adc2_channels); + } else { + indio_dev->channels = rzn1_adc1_channels; + indio_dev->num_channels = ARRAY_SIZE(rzn1_adc1_channels); + } + return 0; + } + + if (rzn1_adc->adc2_vref_mV >= 0) { + indio_dev->channels = rzn1_adc2_channels; + indio_dev->num_channels = ARRAY_SIZE(rzn1_adc2_channels); + return 0; + } + + return dev_err_probe(rzn1_adc->dev, -ENODEV, + "Failed to set IIO channels, no ADC core used\n"); +} + +static int rzn1_adc_core_get_regulators(struct rzn1_adc *rzn1_adc, + int *adc_vref_mV, + const char *avdd_name, const char *vref_name) +{ + struct device *dev = rzn1_adc->dev; + int ret; + + /* + * For a given ADC core (ADC1 or ADC2), both regulators (AVDD and VREF) + * must be available in order to have the ADC core used. + * + * We use the regulators presence to check the usage of the related + * ADC core. If both regulators are available, the ADC core is used. + * Otherwise, the ADC core is not used. + * + * The adc_vref_mV value is set to a negative error code (-ENODEV) when + * the ADC core is not used. Otherwise it is set to the VRef mV value. + */ + + *adc_vref_mV = -ENODEV; + + ret = devm_regulator_get_enable_optional(dev, avdd_name); + if (ret == -ENODEV) + return 0; + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get '%s' regulator\n", + avdd_name); + + ret = devm_regulator_get_enable_read_voltage(dev, vref_name); + if (ret == -ENODEV) + return 0; + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get '%s' regulator\n", + vref_name); + + /* + * Both regulators are available. + * Set adc_vref_mV to the Vref value in mV. This, as the value set is + * positive, also signals that the ADC is used. + */ + *adc_vref_mV = ret / 1000; + + return 0; +} + +static int rzn1_adc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iio_dev *indio_dev; + struct rzn1_adc *rzn1_adc; + struct clk *clk; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*rzn1_adc)); + if (!indio_dev) + return -ENOMEM; + + rzn1_adc = iio_priv(indio_dev); + rzn1_adc->dev = dev; + + ret = devm_mutex_init(dev, &rzn1_adc->lock); + if (ret) + return ret; + + rzn1_adc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rzn1_adc->regs)) + return PTR_ERR(rzn1_adc->regs); + + clk = devm_clk_get_enabled(dev, "pclk"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get pclk\n"); + + clk = devm_clk_get_enabled(dev, "adc"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get adc clk\n"); + + ret = rzn1_adc_core_get_regulators(rzn1_adc, &rzn1_adc->adc1_vref_mV, + "adc1-avdd", "adc1-vref"); + if (ret) + return ret; + + ret = rzn1_adc_core_get_regulators(rzn1_adc, &rzn1_adc->adc2_vref_mV, + "adc2-avdd", "adc2-vref"); + if (ret) + return ret; + + platform_set_drvdata(pdev, rzn1_adc); + + indio_dev->name = "rzn1-adc"; + indio_dev->info = &rzn1_adc_info; + indio_dev->modes = INDIO_DIRECT_MODE; + ret = rzn1_adc_set_iio_dev_channels(rzn1_adc, indio_dev); + if (ret) + return ret; + + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable runtime PM\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static int rzn1_adc_pm_runtime_suspend(struct device *dev) +{ + struct rzn1_adc *rzn1_adc = dev_get_drvdata(dev); + + return rzn1_adc_power(rzn1_adc, false); +} + +static int rzn1_adc_pm_runtime_resume(struct device *dev) +{ + struct rzn1_adc *rzn1_adc = dev_get_drvdata(dev); + + return rzn1_adc_power(rzn1_adc, true); +} + +static DEFINE_RUNTIME_DEV_PM_OPS(rzn1_adc_pm_ops, + rzn1_adc_pm_runtime_suspend, + rzn1_adc_pm_runtime_resume, + NULL); + +static const struct of_device_id rzn1_adc_of_match[] = { + { .compatible = "renesas,rzn1-adc" }, + { } +}; +MODULE_DEVICE_TABLE(of, rzn1_adc_of_match); + +static struct platform_driver rzn1_adc_driver = { + .probe = rzn1_adc_probe, + .driver = { + .name = "rzn1-adc", + .of_match_table = rzn1_adc_of_match, + .pm = pm_ptr(&rzn1_adc_pm_ops), + }, +}; +module_platform_driver(rzn1_adc_driver); + +MODULE_AUTHOR("Herve Codina "); +MODULE_DESCRIPTION("Renesas RZ/N1 ADC Driver"); +MODULE_LICENSE("GPL"); From 5478fd5a599463970378c874dcc4ccb9e1113c63 Mon Sep 17 00:00:00 2001 From: "Herve Codina (Schneider Electric)" Date: Mon, 3 Nov 2025 15:18:34 +0100 Subject: [PATCH 125/150] MAINTAINERS: Add the Renesas RZ/N1 ADC driver entry After contributing the driver, add myself as the maintainer for the Renesas RZ/N1 ADC driver. Signed-off-by: Herve Codina (Schneider Electric) Signed-off-by: Jonathan Cameron --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9f3413e05c83..647ba29ede5c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21914,6 +21914,13 @@ F: include/dt-bindings/net/pcs-rzn1-miic.h F: include/linux/pcs-rzn1-miic.h F: net/dsa/tag_rzn1_a5psw.c +RENESAS RZ/N1 ADC DRIVER +M: Herve Codina +L: linux-renesas-soc@vger.kernel.org +S: Supported +F: Documentation/devicetree/bindings/iio/adc/renesas,rzn1-adc.yaml +F: drivers/iio/adc/rzn1-adc.c + RENESAS RZ/N1 DWMAC GLUE LAYER M: Romain Gantois S: Maintained From 2e5f09334719bb9bd319c97c067dac9a9aa82df5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:06 +0000 Subject: [PATCH 126/150] dt-bindings: iio: dac: Document AD5446 and similar devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add device tree binding documentation for the Analog Devices AD5446 family of Digital-to-Analog Converters and derivative devices from Texas Instruments. There's both SPI and I2C interfaces and feature resolutions ranging from 8-bit to 16-bit. The binding covers 29 derivatives devices including the AD5446 series, AD5600 series, AD5620/5640/5660 variants with different voltage ranges, and TI DAC081s101/DAC101s101/DAC121s101 devices. Signed-off-by: Nuno Sá Acked-by: Conor Dooley Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- .../bindings/iio/dac/adi,ad5446.yaml | 138 ++++++++++++++++++ MAINTAINERS | 8 + 2 files changed, 146 insertions(+) create mode 100644 Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml diff --git a/Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml b/Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml new file mode 100644 index 000000000000..2669d2c4948b --- /dev/null +++ b/Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml @@ -0,0 +1,138 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/dac/adi,ad5446.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Analog Devices AD5446 and similar DACs + +maintainers: + - Michael Hennerich + - Nuno Sá + +description: + Digital to Analog Converter devices supporting both SPI and I2C interfaces. + These devices feature a range of resolutions from 8-bit to 16-bit. + +properties: + compatible: + oneOf: + - description: SPI DACs + enum: + - adi,ad5300 + - adi,ad5310 + - adi,ad5320 + - adi,ad5444 + - adi,ad5446 + - adi,ad5450 + - adi,ad5451 + - adi,ad5452 + - adi,ad5453 + - adi,ad5512a + - adi,ad5541a + - adi,ad5542 + - adi,ad5542a + - adi,ad5543 + - adi,ad5553 + - adi,ad5600 + - adi,ad5601 + - adi,ad5611 + - adi,ad5621 + - adi,ad5641 + - adi,ad5620-2500 + - adi,ad5620-1250 + - adi,ad5640-2500 + - adi,ad5640-1250 + - adi,ad5660-2500 + - adi,ad5660-1250 + - adi,ad5662 + - ti,dac081s101 + - ti,dac101s101 + - ti,dac121s101 + - description: I2C DACs + enum: + - adi,ad5301 + - adi,ad5311 + - adi,ad5321 + - adi,ad5602 + - adi,ad5612 + - adi,ad5622 + + reg: + maxItems: 1 + + vcc-supply: + description: + Reference voltage supply. If not supplied, devices with internal + voltage reference will use that. + +required: + - compatible + - reg + +allOf: + - if: + properties: + compatible: + contains: + enum: + - adi,ad5300 + - adi,ad5310 + - adi,ad5320 + - adi,ad5444 + - adi,ad5446 + - adi,ad5450 + - adi,ad5451 + - adi,ad5452 + - adi,ad5453 + - adi,ad5512a + - adi,ad5541a + - adi,ad5542 + - adi,ad5542a + - adi,ad5543 + - adi,ad5553 + - adi,ad5600 + - adi,ad5601 + - adi,ad5611 + - adi,ad5621 + - adi,ad5641 + - adi,ad5620-2500 + - adi,ad5620-1250 + - adi,ad5640-2500 + - adi,ad5640-1250 + - adi,ad5660-2500 + - adi,ad5660-1250 + - adi,ad5662 + - ti,dac081s101 + - ti,dac101s101 + - ti,dac121s101 + then: + allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + spi { + #address-cells = <1>; + #size-cells = <0>; + + dac@0 { + compatible = "adi,ad5446"; + reg = <0>; + vcc-supply = <&dac_vref>; + }; + }; + - | + i2c { + #address-cells = <1>; + #size-cells = <0>; + + dac@42 { + compatible = "adi,ad5622"; + reg = <0x42>; + vcc-supply = <&dac_vref>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 647ba29ede5c..f4b661860127 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -440,6 +440,14 @@ W: http://wiki.analog.com/AD5398 W: https://ez.analog.com/linux-software-drivers F: drivers/regulator/ad5398.c +AD5446 ANALOG DEVICES INC AD5446 DAC DRIVER +M: Michael Hennerich +M: Nuno Sá +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml + AD714X CAPACITANCE TOUCH SENSOR DRIVER (AD7142/3/7/8/7A) M: Michael Hennerich S: Supported From 86c341d796b91f147d8995fe10ff92f8c9d57f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:07 +0000 Subject: [PATCH 127/150] iio: dac: ad5446: Use DMA safe buffer for transfers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure to use DMA safe buffer. While for i2c we could be fine without them, we need it for spi anyways. As we now have DMA safe buffers, use i2c_master_send_dmasafe(). Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index ad304b0fec08..ce19e20dd38e 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -37,6 +37,8 @@ * @pwr_down_mode: power down mode (1k, 100k or tristate) * @pwr_down: true if the device is in power down * @lock: lock to protect the data buffer during write ops + * @d16: 16bit DMA safe buffer + * @d24: 24bit DMA safe buffer */ struct ad5446_state { @@ -47,6 +49,10 @@ struct ad5446_state { unsigned pwr_down_mode; unsigned pwr_down; struct mutex lock; + union { + __be16 d16; + u8 d24[3]; + } __aligned(IIO_DMA_MINALIGN); }; /** @@ -265,19 +271,18 @@ static int ad5446_probe(struct device *dev, const char *name, static int ad5446_write(struct ad5446_state *st, unsigned val) { struct spi_device *spi = to_spi_device(st->dev); - __be16 data = cpu_to_be16(val); + st->d16 = cpu_to_be16(val); - return spi_write(spi, &data, sizeof(data)); + return spi_write(spi, &st->d16, sizeof(st->d16)); } static int ad5660_write(struct ad5446_state *st, unsigned val) { struct spi_device *spi = to_spi_device(st->dev); - uint8_t data[3]; - put_unaligned_be24(val, &data[0]); + put_unaligned_be24(val, st->d24); - return spi_write(spi, data, sizeof(data)); + return spi_write(spi, st->d24, sizeof(st->d24)); } /* @@ -489,13 +494,13 @@ static inline void ad5446_spi_unregister_driver(void) { } static int ad5622_write(struct ad5446_state *st, unsigned val) { struct i2c_client *client = to_i2c_client(st->dev); - __be16 data = cpu_to_be16(val); + st->d16 = cpu_to_be16(val); int ret; - ret = i2c_master_send(client, (char *)&data, sizeof(data)); + ret = i2c_master_send_dmasafe(client, (char *)&st->d16, sizeof(st->d16)); if (ret < 0) return ret; - if (ret != sizeof(data)) + if (ret != sizeof(st->d16)) return -EIO; return 0; From 63232c9b5413bf398f62425266dfacaa321564c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:08 +0000 Subject: [PATCH 128/150] iio: dac: ad5446: Drop duplicated spi_id entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AD5600 and AD5541A are compatible so there's no need to have a dedicated entry for ID_AD5600. Suggested-by: Andy Shevchenko Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index ce19e20dd38e..4e4e080833d9 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -303,7 +303,6 @@ enum ad5446_supported_spi_device_ids { ID_AD5541A, ID_AD5512A, ID_AD5553, - ID_AD5600, ID_AD5601, ID_AD5611, ID_AD5621, @@ -358,10 +357,6 @@ static const struct ad5446_chip_info ad5446_spi_chip_info[] = { .channel = AD5446_CHANNEL(14, 16, 0), .write = ad5446_write, }, - [ID_AD5600] = { - .channel = AD5446_CHANNEL(16, 16, 0), - .write = ad5446_write, - }, [ID_AD5601] = { .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6), .write = ad5446_write, @@ -429,7 +424,7 @@ static const struct spi_device_id ad5446_spi_ids[] = { {"ad5542a", ID_AD5541A}, /* ad5541a and ad5542a are compatible */ {"ad5543", ID_AD5541A}, /* ad5541a and ad5543 are compatible */ {"ad5553", ID_AD5553}, - {"ad5600", ID_AD5600}, + {"ad5600", ID_AD5541A}, /* ad5541a and ad5600 are compatible */ {"ad5601", ID_AD5601}, {"ad5611", ID_AD5611}, {"ad5621", ID_AD5621}, From 14b72d8acea0a5c53dcffacafa43a9090fda3311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:09 +0000 Subject: [PATCH 129/150] iio: dac: ad5446: Don't ignore missing regulator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the chip does not have an internal reference, do not ignore a missing regulator as we won't be able to actually provide a proper scale for the DAC. Since it's now seen as an error, flip the if() logic so errors are treated first (which is the typical pattern). Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index 4e4e080833d9..f7ec65249322 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -255,10 +255,11 @@ static int ad5446_probe(struct device *dev, const char *name, if (ret < 0 && ret != -ENODEV) return ret; if (ret == -ENODEV) { - if (chip_info->int_vref_mv) - st->vref_mv = chip_info->int_vref_mv; - else - dev_warn(dev, "reference voltage unspecified\n"); + if (!chip_info->int_vref_mv) + return dev_err_probe(dev, ret, + "reference voltage unspecified\n"); + + st->vref_mv = chip_info->int_vref_mv; } else { st->vref_mv = ret / 1000; } From 45d510a7203f63caa0298e40190b364d4646e2b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:10 +0000 Subject: [PATCH 130/150] iio: dac: ad5446: Move to single chip_info structures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not use an array with an enum id kind of thing. Use the more maintainable chip_info variable per chip. Adapt the probe functions to use the proper helpers (for SPI and I2c). Note that in a following patch we'll also add the chip_info variables to the of_device_id tables. Hence already use the helpers that internally use device_get_match_data(). Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 340 +++++++++++++++++++-------------------- 1 file changed, 170 insertions(+), 170 deletions(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index f7ec65249322..fa0c543cb9fb 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -293,154 +293,150 @@ static int ad5660_write(struct ad5446_state *st, unsigned val) * (and a bit cryptic), however this style is used to make clear which * parts are supported here. */ -enum ad5446_supported_spi_device_ids { - ID_AD5300, - ID_AD5310, - ID_AD5320, - ID_AD5444, - ID_AD5446, - ID_AD5450, - ID_AD5451, - ID_AD5541A, - ID_AD5512A, - ID_AD5553, - ID_AD5601, - ID_AD5611, - ID_AD5621, - ID_AD5641, - ID_AD5620_2500, - ID_AD5620_1250, - ID_AD5640_2500, - ID_AD5640_1250, - ID_AD5660_2500, - ID_AD5660_1250, - ID_AD5662, + +static const struct ad5446_chip_info ad5300_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5446_write, }; -static const struct ad5446_chip_info ad5446_spi_chip_info[] = { - [ID_AD5300] = { - .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), - .write = ad5446_write, - }, - [ID_AD5310] = { - .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), - .write = ad5446_write, - }, - [ID_AD5320] = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), - .write = ad5446_write, - }, - [ID_AD5444] = { - .channel = AD5446_CHANNEL(12, 16, 2), - .write = ad5446_write, - }, - [ID_AD5446] = { - .channel = AD5446_CHANNEL(14, 16, 0), - .write = ad5446_write, - }, - [ID_AD5450] = { - .channel = AD5446_CHANNEL(8, 16, 6), - .write = ad5446_write, - }, - [ID_AD5451] = { - .channel = AD5446_CHANNEL(10, 16, 4), - .write = ad5446_write, - }, - [ID_AD5541A] = { - .channel = AD5446_CHANNEL(16, 16, 0), - .write = ad5446_write, - }, - [ID_AD5512A] = { - .channel = AD5446_CHANNEL(12, 16, 4), - .write = ad5446_write, - }, - [ID_AD5553] = { - .channel = AD5446_CHANNEL(14, 16, 0), - .write = ad5446_write, - }, - [ID_AD5601] = { - .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6), - .write = ad5446_write, - }, - [ID_AD5611] = { - .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4), - .write = ad5446_write, - }, - [ID_AD5621] = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), - .write = ad5446_write, - }, - [ID_AD5641] = { - .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), - .write = ad5446_write, - }, - [ID_AD5620_2500] = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), - .int_vref_mv = 2500, - .write = ad5446_write, - }, - [ID_AD5620_1250] = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), - .int_vref_mv = 1250, - .write = ad5446_write, - }, - [ID_AD5640_2500] = { - .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), - .int_vref_mv = 2500, - .write = ad5446_write, - }, - [ID_AD5640_1250] = { - .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), - .int_vref_mv = 1250, - .write = ad5446_write, - }, - [ID_AD5660_2500] = { - .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), - .int_vref_mv = 2500, - .write = ad5660_write, - }, - [ID_AD5660_1250] = { - .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), - .int_vref_mv = 1250, - .write = ad5660_write, - }, - [ID_AD5662] = { - .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), - .write = ad5660_write, - }, +static const struct ad5446_chip_info ad5310_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5320_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5444_chip_info = { + .channel = AD5446_CHANNEL(12, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5446_chip_info = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5450_chip_info = { + .channel = AD5446_CHANNEL(8, 16, 6), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5451_chip_info = { + .channel = AD5446_CHANNEL(10, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5541a_chip_info = { + .channel = AD5446_CHANNEL(16, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5512a_chip_info = { + .channel = AD5446_CHANNEL(12, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5553_chip_info = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5601_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5611_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5621_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5641_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5620_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 2500, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5620_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 1250, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5640_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 2500, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5640_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 1250, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5660_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 2500, + .write = ad5660_write, +}; + +static const struct ad5446_chip_info ad5660_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 1250, + .write = ad5660_write, +}; + +static const struct ad5446_chip_info ad5662_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .write = ad5660_write, }; static const struct spi_device_id ad5446_spi_ids[] = { - {"ad5300", ID_AD5300}, - {"ad5310", ID_AD5310}, - {"ad5320", ID_AD5320}, - {"ad5444", ID_AD5444}, - {"ad5446", ID_AD5446}, - {"ad5450", ID_AD5450}, - {"ad5451", ID_AD5451}, - {"ad5452", ID_AD5444}, /* ad5452 is compatible to the ad5444 */ - {"ad5453", ID_AD5446}, /* ad5453 is compatible to the ad5446 */ - {"ad5512a", ID_AD5512A}, - {"ad5541a", ID_AD5541A}, - {"ad5542a", ID_AD5541A}, /* ad5541a and ad5542a are compatible */ - {"ad5543", ID_AD5541A}, /* ad5541a and ad5543 are compatible */ - {"ad5553", ID_AD5553}, - {"ad5600", ID_AD5541A}, /* ad5541a and ad5600 are compatible */ - {"ad5601", ID_AD5601}, - {"ad5611", ID_AD5611}, - {"ad5621", ID_AD5621}, - {"ad5641", ID_AD5641}, - {"ad5620-2500", ID_AD5620_2500}, /* AD5620/40/60: */ - {"ad5620-1250", ID_AD5620_1250}, /* part numbers may look differently */ - {"ad5640-2500", ID_AD5640_2500}, - {"ad5640-1250", ID_AD5640_1250}, - {"ad5660-2500", ID_AD5660_2500}, - {"ad5660-1250", ID_AD5660_1250}, - {"ad5662", ID_AD5662}, - {"dac081s101", ID_AD5300}, /* compatible Texas Instruments chips */ - {"dac101s101", ID_AD5310}, - {"dac121s101", ID_AD5320}, - {"dac7512", ID_AD5320}, + {"ad5300", (kernel_ulong_t)&ad5300_chip_info}, + {"ad5310", (kernel_ulong_t)&ad5310_chip_info}, + {"ad5320", (kernel_ulong_t)&ad5320_chip_info}, + {"ad5444", (kernel_ulong_t)&ad5444_chip_info}, + {"ad5446", (kernel_ulong_t)&ad5446_chip_info}, + {"ad5450", (kernel_ulong_t)&ad5450_chip_info}, + {"ad5451", (kernel_ulong_t)&ad5451_chip_info}, + {"ad5452", (kernel_ulong_t)&ad5444_chip_info}, /* ad5452 is compatible to the ad5444 */ + {"ad5453", (kernel_ulong_t)&ad5446_chip_info}, /* ad5453 is compatible to the ad5446 */ + {"ad5512a", (kernel_ulong_t)&ad5512a_chip_info}, + {"ad5541a", (kernel_ulong_t)&ad5541a_chip_info}, + {"ad5542a", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5542a are compatible */ + {"ad5543", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5543 are compatible */ + {"ad5553", (kernel_ulong_t)&ad5553_chip_info}, + {"ad5600", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5600 are compatible */ + {"ad5601", (kernel_ulong_t)&ad5601_chip_info}, + {"ad5611", (kernel_ulong_t)&ad5611_chip_info}, + {"ad5621", (kernel_ulong_t)&ad5621_chip_info}, + {"ad5641", (kernel_ulong_t)&ad5641_chip_info}, + {"ad5620-2500", (kernel_ulong_t)&ad5620_2500_chip_info}, /* AD5620/40/60: */ + /* part numbers may look differently */ + {"ad5620-1250", (kernel_ulong_t)&ad5620_1250_chip_info}, + {"ad5640-2500", (kernel_ulong_t)&ad5640_2500_chip_info}, + {"ad5640-1250", (kernel_ulong_t)&ad5640_1250_chip_info}, + {"ad5660-2500", (kernel_ulong_t)&ad5660_2500_chip_info}, + {"ad5660-1250", (kernel_ulong_t)&ad5660_1250_chip_info}, + {"ad5662", (kernel_ulong_t)&ad5662_chip_info}, + {"dac081s101", (kernel_ulong_t)&ad5300_chip_info}, /* compatible Texas Instruments chips */ + {"dac101s101", (kernel_ulong_t)&ad5310_chip_info}, + {"dac121s101", (kernel_ulong_t)&ad5320_chip_info}, + {"dac7512", (kernel_ulong_t)&ad5320_chip_info}, { } }; MODULE_DEVICE_TABLE(spi, ad5446_spi_ids); @@ -454,9 +450,13 @@ MODULE_DEVICE_TABLE(of, ad5446_of_ids); static int ad5446_spi_probe(struct spi_device *spi) { const struct spi_device_id *id = spi_get_device_id(spi); + const struct ad5446_chip_info *chip_info; - return ad5446_probe(&spi->dev, id->name, - &ad5446_spi_chip_info[id->driver_data]); + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODEV; + + return ad5446_probe(&spi->dev, id->name, chip_info); } static struct spi_driver ad5446_spi_driver = { @@ -509,41 +509,41 @@ static int ad5622_write(struct ad5446_state *st, unsigned val) * (and a bit cryptic), however this style is used to make clear which * parts are supported here. */ -enum ad5446_supported_i2c_device_ids { - ID_AD5602, - ID_AD5612, - ID_AD5622, + +static const struct ad5446_chip_info ad5602_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5622_write, }; -static const struct ad5446_chip_info ad5446_i2c_chip_info[] = { - [ID_AD5602] = { - .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), - .write = ad5622_write, - }, - [ID_AD5612] = { - .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), - .write = ad5622_write, - }, - [ID_AD5622] = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), - .write = ad5622_write, - }, +static const struct ad5446_chip_info ad5612_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5622_write, +}; + +static const struct ad5446_chip_info ad5622_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5622_write, }; static int ad5446_i2c_probe(struct i2c_client *i2c) { const struct i2c_device_id *id = i2c_client_get_device_id(i2c); - return ad5446_probe(&i2c->dev, id->name, - &ad5446_i2c_chip_info[id->driver_data]); + const struct ad5446_chip_info *chip_info; + + chip_info = i2c_get_match_data(i2c); + if (!chip_info) + return -ENODEV; + + return ad5446_probe(&i2c->dev, id->name, chip_info); } static const struct i2c_device_id ad5446_i2c_ids[] = { - {"ad5301", ID_AD5602}, - {"ad5311", ID_AD5612}, - {"ad5321", ID_AD5622}, - {"ad5602", ID_AD5602}, - {"ad5612", ID_AD5612}, - {"ad5622", ID_AD5622}, + {"ad5301", (kernel_ulong_t)&ad5602_chip_info}, + {"ad5311", (kernel_ulong_t)&ad5612_chip_info}, + {"ad5321", (kernel_ulong_t)&ad5622_chip_info}, + {"ad5602", (kernel_ulong_t)&ad5602_chip_info}, + {"ad5612", (kernel_ulong_t)&ad5612_chip_info}, + {"ad5622", (kernel_ulong_t)&ad5622_chip_info}, { } }; MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids); From 5b622340bf19c2192ee5769f1b603ab5711949be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:11 +0000 Subject: [PATCH 131/150] iio: dac: ad5456: Add missing DT compatibles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing of_device_id compatibles for the i2c and spi drivers. Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index fa0c543cb9fb..37e17b7eb2fa 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -442,6 +442,35 @@ static const struct spi_device_id ad5446_spi_ids[] = { MODULE_DEVICE_TABLE(spi, ad5446_spi_ids); static const struct of_device_id ad5446_of_ids[] = { + { .compatible = "adi,ad5300", .data = &ad5300_chip_info }, + { .compatible = "adi,ad5310", .data = &ad5310_chip_info }, + { .compatible = "adi,ad5320", .data = &ad5320_chip_info }, + { .compatible = "adi,ad5444", .data = &ad5444_chip_info }, + { .compatible = "adi,ad5446", .data = &ad5446_chip_info }, + { .compatible = "adi,ad5450", .data = &ad5450_chip_info }, + { .compatible = "adi,ad5451", .data = &ad5451_chip_info }, + { .compatible = "adi,ad5452", .data = &ad5444_chip_info }, + { .compatible = "adi,ad5453", .data = &ad5446_chip_info }, + { .compatible = "adi,ad5512a", .data = &ad5512a_chip_info }, + { .compatible = "adi,ad5541a", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5542a", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5543", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5553", .data = &ad5553_chip_info }, + { .compatible = "adi,ad5600", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5601", .data = &ad5601_chip_info }, + { .compatible = "adi,ad5611", .data = &ad5611_chip_info }, + { .compatible = "adi,ad5621", .data = &ad5621_chip_info }, + { .compatible = "adi,ad5641", .data = &ad5641_chip_info }, + { .compatible = "adi,ad5620-2500", .data = &ad5620_2500_chip_info }, + { .compatible = "adi,ad5620-1250", .data = &ad5620_1250_chip_info }, + { .compatible = "adi,ad5640-2500", .data = &ad5640_2500_chip_info }, + { .compatible = "adi,ad5640-1250", .data = &ad5640_1250_chip_info }, + { .compatible = "adi,ad5660-2500", .data = &ad5660_2500_chip_info }, + { .compatible = "adi,ad5660-1250", .data = &ad5660_1250_chip_info }, + { .compatible = "adi,ad5662", .data = &ad5662_chip_info }, + { .compatible = "ti,dac081s101", .data = &ad5300_chip_info }, + { .compatible = "ti,dac101s101", .data = &ad5310_chip_info }, + { .compatible = "ti,dac121s101", .data = &ad5320_chip_info }, { .compatible = "ti,dac7512" }, { } }; @@ -548,9 +577,21 @@ static const struct i2c_device_id ad5446_i2c_ids[] = { }; MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids); +static const struct of_device_id ad5446_i2c_of_ids[] = { + { .compatible = "adi,ad5301", .data = &ad5602_chip_info }, + { .compatible = "adi,ad5311", .data = &ad5612_chip_info }, + { .compatible = "adi,ad5321", .data = &ad5622_chip_info }, + { .compatible = "adi,ad5602", .data = &ad5602_chip_info }, + { .compatible = "adi,ad5612", .data = &ad5612_chip_info }, + { .compatible = "adi,ad5622", .data = &ad5622_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5446_i2c_of_ids); + static struct i2c_driver ad5446_i2c_driver = { .driver = { .name = "ad5446", + .of_match_table = ad5446_i2c_of_ids, }, .probe = ad5446_i2c_probe, .id_table = ad5446_i2c_ids, From 876d94024087b03494b206a5b5561fcd267824e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:12 +0000 Subject: [PATCH 132/150] iio: dac: ad5446: Separate I2C/SPI into different drivers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Properly separate the I2C and SPI drivers into two different drivers living in their own source file (as usual). So that no need for the hacky ifdefery. Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- MAINTAINERS | 4 + drivers/iio/dac/Kconfig | 31 ++- drivers/iio/dac/Makefile | 2 + drivers/iio/dac/ad5446-i2c.c | 102 ++++++++ drivers/iio/dac/ad5446-spi.c | 250 +++++++++++++++++++ drivers/iio/dac/ad5446.c | 452 +---------------------------------- drivers/iio/dac/ad5446.h | 76 ++++++ 7 files changed, 466 insertions(+), 451 deletions(-) create mode 100644 drivers/iio/dac/ad5446-i2c.c create mode 100644 drivers/iio/dac/ad5446-spi.c create mode 100644 drivers/iio/dac/ad5446.h diff --git a/MAINTAINERS b/MAINTAINERS index f4b661860127..31d98efb1ad1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -447,6 +447,10 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad5446.yaml +F: drivers/iio/dac/ad5446-i2c.c +F: drivers/iio/dac/ad5446-spi.c +F: drivers/iio/dac/ad5446.c +F: drivers/iio/dac/ad5446.h AD714X CAPACITANCE TOUCH SENSOR DRIVER (AD7142/3/7/8/7A) M: Michael Hennerich diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig index e0996dc014a3..7cd3caec1262 100644 --- a/drivers/iio/dac/Kconfig +++ b/drivers/iio/dac/Kconfig @@ -97,17 +97,32 @@ config AD5421 ad5421. config AD5446 - tristate "Analog Devices AD5446 and similar single channel DACs driver" - depends on (SPI_MASTER && I2C!=m) || I2C + tristate + +config AD5446_SPI + tristate "Analog Devices AD5446 and similar single channel DACs driver (SPI)" + depends on SPI + select AD5446 help - Say yes here to build support for Analog Devices AD5300, AD5301, AD5310, - AD5311, AD5320, AD5321, AD5444, AD5446, AD5450, AD5451, AD5452, AD5453, - AD5512A, AD5541A, AD5542A, AD5543, AD5553, AD5600, AD5601, AD5602, AD5611, - AD5612, AD5620, AD5621, AD5622, AD5640, AD5641, AD5660, AD5662 DACs - as well as Texas Instruments DAC081S101, DAC101S101, DAC121S101. + Say yes here to build support for Analog Devices AD5300, AD5310, + AD5320, AD5444, AD5446, AD5450, AD5451, AD5452, AD5453, AD5512A, + AD5541A, AD5542A, AD5543, AD5553, AD5600, AD5601, AD5611, AD5620, + AD5621, AD5640, AD5641, AD5660, AD5662 DACs as well as + Texas Instruments DAC081S101, DAC101S101, DAC121S101. To compile this driver as a module, choose M here: the - module will be called ad5446. + module will be called ad5446-spi. + +config AD5446_I2C + tristate "Analog Devices AD5446 and similar single channel DACs driver (I2C)" + depends on I2C + select AD5446 + help + Say yes here to build support for Analog Devices AD5301, AD5311, AD5321, + AD5602, AD5612, AD5622 DACs. + + To compile this driver as a module, choose M here: the + module will be called ad5446-i2c. config AD5449 tristate "Analog Devices AD5449 and similar DACs driver" diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile index 3684cd52b7fa..e6ac4c67e337 100644 --- a/drivers/iio/dac/Makefile +++ b/drivers/iio/dac/Makefile @@ -15,6 +15,8 @@ obj-$(CONFIG_AD5624R_SPI) += ad5624r_spi.o obj-$(CONFIG_AD5064) += ad5064.o obj-$(CONFIG_AD5504) += ad5504.o obj-$(CONFIG_AD5446) += ad5446.o +obj-$(CONFIG_AD5446_SPI) += ad5446-spi.o +obj-$(CONFIG_AD5446_I2C) += ad5446-i2c.o obj-$(CONFIG_AD5449) += ad5449.o obj-$(CONFIG_AD5592R_BASE) += ad5592r-base.o obj-$(CONFIG_AD5592R) += ad5592r.o diff --git a/drivers/iio/dac/ad5446-i2c.c b/drivers/iio/dac/ad5446-i2c.c new file mode 100644 index 000000000000..40fe7e17fce4 --- /dev/null +++ b/drivers/iio/dac/ad5446-i2c.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD5446 SPI I2C driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include +#include +#include +#include + +#include + +#include "ad5446.h" + +static int ad5622_write(struct ad5446_state *st, unsigned int val) +{ + struct i2c_client *client = to_i2c_client(st->dev); + int ret; + + st->d16 = cpu_to_be16(val); + + ret = i2c_master_send_dmasafe(client, (char *)&st->d16, sizeof(st->d16)); + if (ret < 0) + return ret; + if (ret != sizeof(st->d16)) + return -EIO; + + return 0; +} + +static int ad5446_i2c_probe(struct i2c_client *i2c) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(i2c); + const struct ad5446_chip_info *chip_info; + + chip_info = i2c_get_match_data(i2c); + if (!chip_info) + return -ENODEV; + + return ad5446_probe(&i2c->dev, id->name, chip_info); +} + +/* + * ad5446_supported_i2c_device_ids: + * The AD5620/40/60 parts are available in different fixed internal reference + * voltage options. The actual part numbers may look differently + * (and a bit cryptic), however this style is used to make clear which + * parts are supported here. + */ + +static const struct ad5446_chip_info ad5602_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5622_write, +}; + +static const struct ad5446_chip_info ad5612_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5622_write, +}; + +static const struct ad5446_chip_info ad5622_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5622_write, +}; + +static const struct i2c_device_id ad5446_i2c_ids[] = { + {"ad5301", (kernel_ulong_t)&ad5602_chip_info}, + {"ad5311", (kernel_ulong_t)&ad5612_chip_info}, + {"ad5321", (kernel_ulong_t)&ad5622_chip_info}, + {"ad5602", (kernel_ulong_t)&ad5602_chip_info}, + {"ad5612", (kernel_ulong_t)&ad5612_chip_info}, + {"ad5622", (kernel_ulong_t)&ad5622_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids); + +static const struct of_device_id ad5446_i2c_of_ids[] = { + { .compatible = "adi,ad5301", .data = &ad5602_chip_info }, + { .compatible = "adi,ad5311", .data = &ad5612_chip_info }, + { .compatible = "adi,ad5321", .data = &ad5622_chip_info }, + { .compatible = "adi,ad5602", .data = &ad5602_chip_info }, + { .compatible = "adi,ad5612", .data = &ad5612_chip_info }, + { .compatible = "adi,ad5622", .data = &ad5622_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(OF, ad5446_i2c_of_ids); + +static struct i2c_driver ad5446_i2c_driver = { + .driver = { + .name = "ad5446", + .of_match_table = ad5446_i2c_of_ids, + }, + .probe = ad5446_i2c_probe, + .id_table = ad5446_i2c_ids, +}; +module_i2c_driver(ad5446_i2c_driver); + +MODULE_AUTHOR("Nuno Sá "); +MODULE_DESCRIPTION("Analog Devices AD5622 and similar I2C DACs"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD5446"); diff --git a/drivers/iio/dac/ad5446-spi.c b/drivers/iio/dac/ad5446-spi.c new file mode 100644 index 000000000000..dfba1972d1e0 --- /dev/null +++ b/drivers/iio/dac/ad5446-spi.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AD5446 SPI DAC driver + * + * Copyright 2025 Analog Devices Inc. + */ +#include +#include +#include +#include +#include + +#include + +#include "ad5446.h" + +static int ad5446_write(struct ad5446_state *st, unsigned int val) +{ + struct spi_device *spi = to_spi_device(st->dev); + + st->d16 = cpu_to_be16(val); + + return spi_write(spi, &st->d16, sizeof(st->d16)); +} + +static int ad5660_write(struct ad5446_state *st, unsigned int val) +{ + struct spi_device *spi = to_spi_device(st->dev); + + put_unaligned_be24(val, st->d24); + + return spi_write(spi, st->d24, sizeof(st->d24)); +} + +static int ad5446_spi_probe(struct spi_device *spi) +{ + const struct spi_device_id *id = spi_get_device_id(spi); + const struct ad5446_chip_info *chip_info; + + chip_info = spi_get_device_match_data(spi); + if (!chip_info) + return -ENODEV; + + return ad5446_probe(&spi->dev, id->name, chip_info); +} + +/* + * ad5446_supported_spi_device_ids: + * The AD5620/40/60 parts are available in different fixed internal reference + * voltage options. The actual part numbers may look differently + * (and a bit cryptic), however this style is used to make clear which + * parts are supported here. + */ + +static const struct ad5446_chip_info ad5300_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5310_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5320_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5444_chip_info = { + .channel = AD5446_CHANNEL(12, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5446_chip_info = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5450_chip_info = { + .channel = AD5446_CHANNEL(8, 16, 6), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5451_chip_info = { + .channel = AD5446_CHANNEL(10, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5541a_chip_info = { + .channel = AD5446_CHANNEL(16, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5512a_chip_info = { + .channel = AD5446_CHANNEL(12, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5553_chip_info = { + .channel = AD5446_CHANNEL(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5601_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5611_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5621_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5641_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5620_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 2500, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5620_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), + .int_vref_mv = 1250, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5640_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 2500, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5640_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), + .int_vref_mv = 1250, + .write = ad5446_write, +}; + +static const struct ad5446_chip_info ad5660_2500_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 2500, + .write = ad5660_write, +}; + +static const struct ad5446_chip_info ad5660_1250_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .int_vref_mv = 1250, + .write = ad5660_write, +}; + +static const struct ad5446_chip_info ad5662_chip_info = { + .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), + .write = ad5660_write, +}; + +static const struct spi_device_id ad5446_spi_ids[] = { + {"ad5300", (kernel_ulong_t)&ad5300_chip_info}, + {"ad5310", (kernel_ulong_t)&ad5310_chip_info}, + {"ad5320", (kernel_ulong_t)&ad5320_chip_info}, + {"ad5444", (kernel_ulong_t)&ad5444_chip_info}, + {"ad5446", (kernel_ulong_t)&ad5446_chip_info}, + {"ad5450", (kernel_ulong_t)&ad5450_chip_info}, + {"ad5451", (kernel_ulong_t)&ad5451_chip_info}, + {"ad5452", (kernel_ulong_t)&ad5444_chip_info}, /* ad5452 is compatible to the ad5444 */ + {"ad5453", (kernel_ulong_t)&ad5446_chip_info}, /* ad5453 is compatible to the ad5446 */ + {"ad5512a", (kernel_ulong_t)&ad5512a_chip_info}, + {"ad5541a", (kernel_ulong_t)&ad5541a_chip_info}, + {"ad5542a", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5542a are compatible */ + {"ad5543", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5543 are compatible */ + {"ad5553", (kernel_ulong_t)&ad5553_chip_info}, + {"ad5600", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5600 are compatible */ + {"ad5601", (kernel_ulong_t)&ad5601_chip_info}, + {"ad5611", (kernel_ulong_t)&ad5611_chip_info}, + {"ad5621", (kernel_ulong_t)&ad5621_chip_info}, + {"ad5641", (kernel_ulong_t)&ad5641_chip_info}, + {"ad5620-2500", (kernel_ulong_t)&ad5620_2500_chip_info}, /* AD5620/40/60: */ + /* part numbers may look differently */ + {"ad5620-1250", (kernel_ulong_t)&ad5620_1250_chip_info}, + {"ad5640-2500", (kernel_ulong_t)&ad5640_2500_chip_info}, + {"ad5640-1250", (kernel_ulong_t)&ad5640_1250_chip_info}, + {"ad5660-2500", (kernel_ulong_t)&ad5660_2500_chip_info}, + {"ad5660-1250", (kernel_ulong_t)&ad5660_1250_chip_info}, + {"ad5662", (kernel_ulong_t)&ad5662_chip_info}, + {"dac081s101", (kernel_ulong_t)&ad5300_chip_info}, /* compatible Texas Instruments chips */ + {"dac101s101", (kernel_ulong_t)&ad5310_chip_info}, + {"dac121s101", (kernel_ulong_t)&ad5320_chip_info}, + {"dac7512", (kernel_ulong_t)&ad5320_chip_info}, + { } +}; +MODULE_DEVICE_TABLE(spi, ad5446_spi_ids); + +static const struct of_device_id ad5446_of_ids[] = { + { .compatible = "adi,ad5300", .data = &ad5300_chip_info }, + { .compatible = "adi,ad5310", .data = &ad5310_chip_info }, + { .compatible = "adi,ad5320", .data = &ad5320_chip_info }, + { .compatible = "adi,ad5444", .data = &ad5444_chip_info }, + { .compatible = "adi,ad5446", .data = &ad5446_chip_info }, + { .compatible = "adi,ad5450", .data = &ad5450_chip_info }, + { .compatible = "adi,ad5451", .data = &ad5451_chip_info }, + { .compatible = "adi,ad5452", .data = &ad5444_chip_info }, + { .compatible = "adi,ad5453", .data = &ad5446_chip_info }, + { .compatible = "adi,ad5512a", .data = &ad5512a_chip_info }, + { .compatible = "adi,ad5541a", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5542a", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5543", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5553", .data = &ad5553_chip_info }, + { .compatible = "adi,ad5600", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5601", .data = &ad5601_chip_info }, + { .compatible = "adi,ad5611", .data = &ad5611_chip_info }, + { .compatible = "adi,ad5621", .data = &ad5621_chip_info }, + { .compatible = "adi,ad5641", .data = &ad5641_chip_info }, + { .compatible = "adi,ad5620-2500", .data = &ad5620_2500_chip_info }, + { .compatible = "adi,ad5620-1250", .data = &ad5620_1250_chip_info }, + { .compatible = "adi,ad5640-2500", .data = &ad5640_2500_chip_info }, + { .compatible = "adi,ad5640-1250", .data = &ad5640_1250_chip_info }, + { .compatible = "adi,ad5660-2500", .data = &ad5660_2500_chip_info }, + { .compatible = "adi,ad5660-1250", .data = &ad5660_1250_chip_info }, + { .compatible = "adi,ad5662", .data = &ad5662_chip_info }, + { .compatible = "ti,dac081s101", .data = &ad5300_chip_info }, + { .compatible = "ti,dac101s101", .data = &ad5310_chip_info }, + { .compatible = "ti,dac121s101", .data = &ad5320_chip_info }, + { .compatible = "ti,dac7512", .data = &ad5320_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad5446_of_ids); + +static struct spi_driver ad5446_spi_driver = { + .driver = { + .name = "ad5446", + .of_match_table = ad5446_of_ids, + }, + .probe = ad5446_spi_probe, + .id_table = ad5446_spi_ids, +}; +module_spi_driver(ad5446_spi_driver); + +MODULE_AUTHOR("Nuno Sá "); +MODULE_DESCRIPTION("Analog Devices AD5446 and similar SPI DACs"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_AD5446"); diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index 37e17b7eb2fa..c7876217c7ec 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0-or-later /* - * AD5446 SPI DAC driver + * AD5446 CORE DAC driver * * Copyright 2010 Analog Devices Inc. */ +#include #include #include #include @@ -12,62 +13,19 @@ #include #include #include -#include -#include #include #include #include -#include #include #include -#include +#include "ad5446.h" #define MODE_PWRDWN_1k 0x1 #define MODE_PWRDWN_100k 0x2 #define MODE_PWRDWN_TRISTATE 0x3 -/** - * struct ad5446_state - driver instance specific data - * @dev: this device - * @chip_info: chip model specific constants, available modes etc - * @vref_mv: actual reference voltage used - * @cached_val: store/retrieve values during power down - * @pwr_down_mode: power down mode (1k, 100k or tristate) - * @pwr_down: true if the device is in power down - * @lock: lock to protect the data buffer during write ops - * @d16: 16bit DMA safe buffer - * @d24: 24bit DMA safe buffer - */ - -struct ad5446_state { - struct device *dev; - const struct ad5446_chip_info *chip_info; - unsigned short vref_mv; - unsigned cached_val; - unsigned pwr_down_mode; - unsigned pwr_down; - struct mutex lock; - union { - __be16 d16; - u8 d24[3]; - } __aligned(IIO_DMA_MINALIGN); -}; - -/** - * struct ad5446_chip_info - chip specific information - * @channel: channel spec for the DAC - * @int_vref_mv: AD5620/40/60: the internal reference voltage - * @write: chip specific helper function to write to the register - */ - -struct ad5446_chip_info { - struct iio_chan_spec channel; - u16 int_vref_mv; - int (*write)(struct ad5446_state *st, unsigned val); -}; - static const char * const ad5446_powerdown_modes[] = { "1kohm_to_gnd", "100kohm_to_gnd", "three_state" }; @@ -138,7 +96,7 @@ static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev, return ret ? ret : len; } -static const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = { +const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = { { .name = "powerdown", .read = ad5446_read_dac_powerdown, @@ -149,28 +107,7 @@ static const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = { IIO_ENUM_AVAILABLE("powerdown_mode", IIO_SHARED_BY_TYPE, &ad5446_powerdown_mode_enum), { } }; - -#define _AD5446_CHANNEL(bits, storage, _shift, ext) { \ - .type = IIO_VOLTAGE, \ - .indexed = 1, \ - .output = 1, \ - .channel = 0, \ - .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ - .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ - .scan_type = { \ - .sign = 'u', \ - .realbits = (bits), \ - .storagebits = (storage), \ - .shift = (_shift), \ - }, \ - .ext_info = (ext), \ -} - -#define AD5446_CHANNEL(bits, storage, shift) \ - _AD5446_CHANNEL(bits, storage, shift, NULL) - -#define AD5446_CHANNEL_POWERDOWN(bits, storage, shift) \ - _AD5446_CHANNEL(bits, storage, shift, ad5446_ext_info_powerdown) +EXPORT_SYMBOL_NS_GPL(ad5446_ext_info_powerdown, "IIO_AD5446"); static int ad5446_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, @@ -225,8 +162,8 @@ static const struct iio_info ad5446_info = { .write_raw = ad5446_write_raw, }; -static int ad5446_probe(struct device *dev, const char *name, - const struct ad5446_chip_info *chip_info) +int ad5446_probe(struct device *dev, const char *name, + const struct ad5446_chip_info *chip_info) { struct ad5446_state *st; struct iio_dev *indio_dev; @@ -266,379 +203,8 @@ static int ad5446_probe(struct device *dev, const char *name, return devm_iio_device_register(dev, indio_dev); } - -#if IS_ENABLED(CONFIG_SPI_MASTER) - -static int ad5446_write(struct ad5446_state *st, unsigned val) -{ - struct spi_device *spi = to_spi_device(st->dev); - st->d16 = cpu_to_be16(val); - - return spi_write(spi, &st->d16, sizeof(st->d16)); -} - -static int ad5660_write(struct ad5446_state *st, unsigned val) -{ - struct spi_device *spi = to_spi_device(st->dev); - - put_unaligned_be24(val, st->d24); - - return spi_write(spi, st->d24, sizeof(st->d24)); -} - -/* - * ad5446_supported_spi_device_ids: - * The AD5620/40/60 parts are available in different fixed internal reference - * voltage options. The actual part numbers may look differently - * (and a bit cryptic), however this style is used to make clear which - * parts are supported here. - */ - -static const struct ad5446_chip_info ad5300_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5310_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5320_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5444_chip_info = { - .channel = AD5446_CHANNEL(12, 16, 2), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5446_chip_info = { - .channel = AD5446_CHANNEL(14, 16, 0), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5450_chip_info = { - .channel = AD5446_CHANNEL(8, 16, 6), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5451_chip_info = { - .channel = AD5446_CHANNEL(10, 16, 4), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5541a_chip_info = { - .channel = AD5446_CHANNEL(16, 16, 0), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5512a_chip_info = { - .channel = AD5446_CHANNEL(12, 16, 4), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5553_chip_info = { - .channel = AD5446_CHANNEL(14, 16, 0), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5601_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 6), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5611_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 4), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5621_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5641_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5620_2500_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), - .int_vref_mv = 2500, - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5620_1250_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 2), - .int_vref_mv = 1250, - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5640_2500_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), - .int_vref_mv = 2500, - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5640_1250_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(14, 16, 0), - .int_vref_mv = 1250, - .write = ad5446_write, -}; - -static const struct ad5446_chip_info ad5660_2500_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), - .int_vref_mv = 2500, - .write = ad5660_write, -}; - -static const struct ad5446_chip_info ad5660_1250_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), - .int_vref_mv = 1250, - .write = ad5660_write, -}; - -static const struct ad5446_chip_info ad5662_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(16, 16, 0), - .write = ad5660_write, -}; - -static const struct spi_device_id ad5446_spi_ids[] = { - {"ad5300", (kernel_ulong_t)&ad5300_chip_info}, - {"ad5310", (kernel_ulong_t)&ad5310_chip_info}, - {"ad5320", (kernel_ulong_t)&ad5320_chip_info}, - {"ad5444", (kernel_ulong_t)&ad5444_chip_info}, - {"ad5446", (kernel_ulong_t)&ad5446_chip_info}, - {"ad5450", (kernel_ulong_t)&ad5450_chip_info}, - {"ad5451", (kernel_ulong_t)&ad5451_chip_info}, - {"ad5452", (kernel_ulong_t)&ad5444_chip_info}, /* ad5452 is compatible to the ad5444 */ - {"ad5453", (kernel_ulong_t)&ad5446_chip_info}, /* ad5453 is compatible to the ad5446 */ - {"ad5512a", (kernel_ulong_t)&ad5512a_chip_info}, - {"ad5541a", (kernel_ulong_t)&ad5541a_chip_info}, - {"ad5542a", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5542a are compatible */ - {"ad5543", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5543 are compatible */ - {"ad5553", (kernel_ulong_t)&ad5553_chip_info}, - {"ad5600", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5600 are compatible */ - {"ad5601", (kernel_ulong_t)&ad5601_chip_info}, - {"ad5611", (kernel_ulong_t)&ad5611_chip_info}, - {"ad5621", (kernel_ulong_t)&ad5621_chip_info}, - {"ad5641", (kernel_ulong_t)&ad5641_chip_info}, - {"ad5620-2500", (kernel_ulong_t)&ad5620_2500_chip_info}, /* AD5620/40/60: */ - /* part numbers may look differently */ - {"ad5620-1250", (kernel_ulong_t)&ad5620_1250_chip_info}, - {"ad5640-2500", (kernel_ulong_t)&ad5640_2500_chip_info}, - {"ad5640-1250", (kernel_ulong_t)&ad5640_1250_chip_info}, - {"ad5660-2500", (kernel_ulong_t)&ad5660_2500_chip_info}, - {"ad5660-1250", (kernel_ulong_t)&ad5660_1250_chip_info}, - {"ad5662", (kernel_ulong_t)&ad5662_chip_info}, - {"dac081s101", (kernel_ulong_t)&ad5300_chip_info}, /* compatible Texas Instruments chips */ - {"dac101s101", (kernel_ulong_t)&ad5310_chip_info}, - {"dac121s101", (kernel_ulong_t)&ad5320_chip_info}, - {"dac7512", (kernel_ulong_t)&ad5320_chip_info}, - { } -}; -MODULE_DEVICE_TABLE(spi, ad5446_spi_ids); - -static const struct of_device_id ad5446_of_ids[] = { - { .compatible = "adi,ad5300", .data = &ad5300_chip_info }, - { .compatible = "adi,ad5310", .data = &ad5310_chip_info }, - { .compatible = "adi,ad5320", .data = &ad5320_chip_info }, - { .compatible = "adi,ad5444", .data = &ad5444_chip_info }, - { .compatible = "adi,ad5446", .data = &ad5446_chip_info }, - { .compatible = "adi,ad5450", .data = &ad5450_chip_info }, - { .compatible = "adi,ad5451", .data = &ad5451_chip_info }, - { .compatible = "adi,ad5452", .data = &ad5444_chip_info }, - { .compatible = "adi,ad5453", .data = &ad5446_chip_info }, - { .compatible = "adi,ad5512a", .data = &ad5512a_chip_info }, - { .compatible = "adi,ad5541a", .data = &ad5541a_chip_info }, - { .compatible = "adi,ad5542a", .data = &ad5541a_chip_info }, - { .compatible = "adi,ad5543", .data = &ad5541a_chip_info }, - { .compatible = "adi,ad5553", .data = &ad5553_chip_info }, - { .compatible = "adi,ad5600", .data = &ad5541a_chip_info }, - { .compatible = "adi,ad5601", .data = &ad5601_chip_info }, - { .compatible = "adi,ad5611", .data = &ad5611_chip_info }, - { .compatible = "adi,ad5621", .data = &ad5621_chip_info }, - { .compatible = "adi,ad5641", .data = &ad5641_chip_info }, - { .compatible = "adi,ad5620-2500", .data = &ad5620_2500_chip_info }, - { .compatible = "adi,ad5620-1250", .data = &ad5620_1250_chip_info }, - { .compatible = "adi,ad5640-2500", .data = &ad5640_2500_chip_info }, - { .compatible = "adi,ad5640-1250", .data = &ad5640_1250_chip_info }, - { .compatible = "adi,ad5660-2500", .data = &ad5660_2500_chip_info }, - { .compatible = "adi,ad5660-1250", .data = &ad5660_1250_chip_info }, - { .compatible = "adi,ad5662", .data = &ad5662_chip_info }, - { .compatible = "ti,dac081s101", .data = &ad5300_chip_info }, - { .compatible = "ti,dac101s101", .data = &ad5310_chip_info }, - { .compatible = "ti,dac121s101", .data = &ad5320_chip_info }, - { .compatible = "ti,dac7512" }, - { } -}; -MODULE_DEVICE_TABLE(of, ad5446_of_ids); - -static int ad5446_spi_probe(struct spi_device *spi) -{ - const struct spi_device_id *id = spi_get_device_id(spi); - const struct ad5446_chip_info *chip_info; - - chip_info = spi_get_device_match_data(spi); - if (!chip_info) - return -ENODEV; - - return ad5446_probe(&spi->dev, id->name, chip_info); -} - -static struct spi_driver ad5446_spi_driver = { - .driver = { - .name = "ad5446", - .of_match_table = ad5446_of_ids, - }, - .probe = ad5446_spi_probe, - .id_table = ad5446_spi_ids, -}; - -static int __init ad5446_spi_register_driver(void) -{ - return spi_register_driver(&ad5446_spi_driver); -} - -static void ad5446_spi_unregister_driver(void) -{ - spi_unregister_driver(&ad5446_spi_driver); -} - -#else - -static inline int ad5446_spi_register_driver(void) { return 0; } -static inline void ad5446_spi_unregister_driver(void) { } - -#endif - -#if IS_ENABLED(CONFIG_I2C) - -static int ad5622_write(struct ad5446_state *st, unsigned val) -{ - struct i2c_client *client = to_i2c_client(st->dev); - st->d16 = cpu_to_be16(val); - int ret; - - ret = i2c_master_send_dmasafe(client, (char *)&st->d16, sizeof(st->d16)); - if (ret < 0) - return ret; - if (ret != sizeof(st->d16)) - return -EIO; - - return 0; -} - -/* - * ad5446_supported_i2c_device_ids: - * The AD5620/40/60 parts are available in different fixed internal reference - * voltage options. The actual part numbers may look differently - * (and a bit cryptic), however this style is used to make clear which - * parts are supported here. - */ - -static const struct ad5446_chip_info ad5602_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(8, 16, 4), - .write = ad5622_write, -}; - -static const struct ad5446_chip_info ad5612_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(10, 16, 2), - .write = ad5622_write, -}; - -static const struct ad5446_chip_info ad5622_chip_info = { - .channel = AD5446_CHANNEL_POWERDOWN(12, 16, 0), - .write = ad5622_write, -}; - -static int ad5446_i2c_probe(struct i2c_client *i2c) -{ - const struct i2c_device_id *id = i2c_client_get_device_id(i2c); - const struct ad5446_chip_info *chip_info; - - chip_info = i2c_get_match_data(i2c); - if (!chip_info) - return -ENODEV; - - return ad5446_probe(&i2c->dev, id->name, chip_info); -} - -static const struct i2c_device_id ad5446_i2c_ids[] = { - {"ad5301", (kernel_ulong_t)&ad5602_chip_info}, - {"ad5311", (kernel_ulong_t)&ad5612_chip_info}, - {"ad5321", (kernel_ulong_t)&ad5622_chip_info}, - {"ad5602", (kernel_ulong_t)&ad5602_chip_info}, - {"ad5612", (kernel_ulong_t)&ad5612_chip_info}, - {"ad5622", (kernel_ulong_t)&ad5622_chip_info}, - { } -}; -MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids); - -static const struct of_device_id ad5446_i2c_of_ids[] = { - { .compatible = "adi,ad5301", .data = &ad5602_chip_info }, - { .compatible = "adi,ad5311", .data = &ad5612_chip_info }, - { .compatible = "adi,ad5321", .data = &ad5622_chip_info }, - { .compatible = "adi,ad5602", .data = &ad5602_chip_info }, - { .compatible = "adi,ad5612", .data = &ad5612_chip_info }, - { .compatible = "adi,ad5622", .data = &ad5622_chip_info }, - { } -}; -MODULE_DEVICE_TABLE(of, ad5446_i2c_of_ids); - -static struct i2c_driver ad5446_i2c_driver = { - .driver = { - .name = "ad5446", - .of_match_table = ad5446_i2c_of_ids, - }, - .probe = ad5446_i2c_probe, - .id_table = ad5446_i2c_ids, -}; - -static int __init ad5446_i2c_register_driver(void) -{ - return i2c_add_driver(&ad5446_i2c_driver); -} - -static void __exit ad5446_i2c_unregister_driver(void) -{ - i2c_del_driver(&ad5446_i2c_driver); -} - -#else - -static inline int ad5446_i2c_register_driver(void) { return 0; } -static inline void ad5446_i2c_unregister_driver(void) { } - -#endif - -static int __init ad5446_init(void) -{ - int ret; - - ret = ad5446_spi_register_driver(); - if (ret) - return ret; - - ret = ad5446_i2c_register_driver(); - if (ret) { - ad5446_spi_unregister_driver(); - return ret; - } - - return 0; -} -module_init(ad5446_init); - -static void __exit ad5446_exit(void) -{ - ad5446_i2c_unregister_driver(); - ad5446_spi_unregister_driver(); -} -module_exit(ad5446_exit); +EXPORT_SYMBOL_NS_GPL(ad5446_probe, "IIO_AD5446"); MODULE_AUTHOR("Michael Hennerich "); -MODULE_DESCRIPTION("Analog Devices AD5444/AD5446 DAC"); +MODULE_DESCRIPTION("Analog Devices CORE AD5446 DAC and similar devices"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/iio/dac/ad5446.h b/drivers/iio/dac/ad5446.h new file mode 100644 index 000000000000..ee3d2c7d1764 --- /dev/null +++ b/drivers/iio/dac/ad5446.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_AD5446_H +#define _LINUX_AD5446_H + +#include +#include +#include +#include + +struct device; + +extern const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[]; + +#define _AD5446_CHANNEL(bits, storage, _shift, ext) { \ + .type = IIO_VOLTAGE, \ + .indexed = 1, \ + .output = 1, \ + .channel = 0, \ + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ + .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \ + .scan_type = { \ + .sign = 'u', \ + .realbits = (bits), \ + .storagebits = (storage), \ + .shift = (_shift), \ + }, \ + .ext_info = (ext), \ +} + +#define AD5446_CHANNEL(bits, storage, shift) \ + _AD5446_CHANNEL(bits, storage, shift, NULL) + +#define AD5446_CHANNEL_POWERDOWN(bits, storage, shift) \ + _AD5446_CHANNEL(bits, storage, shift, ad5446_ext_info_powerdown) + +/** + * struct ad5446_state - driver instance specific data + * @dev: this device + * @chip_info: chip model specific constants, available modes etc + * @vref_mv: actual reference voltage used + * @cached_val: store/retrieve values during power down + * @pwr_down_mode: power down mode (1k, 100k or tristate) + * @pwr_down: true if the device is in power down + * @lock: lock to protect the data buffer during write ops + */ +struct ad5446_state { + struct device *dev; + const struct ad5446_chip_info *chip_info; + unsigned short vref_mv; + unsigned int cached_val; + unsigned int pwr_down_mode; + unsigned int pwr_down; + /* mutex to protect device shared data */ + struct mutex lock; + union { + __be16 d16; + u8 d24[3]; + } __aligned(IIO_DMA_MINALIGN); +}; + +/** + * struct ad5446_chip_info - chip specific information + * @channel: channel spec for the DAC + * @int_vref_mv: AD5620/40/60: the internal reference voltage + * @write: chip specific helper function to write to the register + */ +struct ad5446_chip_info { + struct iio_chan_spec channel; + u16 int_vref_mv; + int (*write)(struct ad5446_state *st, unsigned int val); +}; + +int ad5446_probe(struct device *dev, const char *name, + const struct ad5446_chip_info *chip_info); + +#endif From 6e43c10675d876a5fb71e57a9bcca9533fccccd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:13 +0000 Subject: [PATCH 133/150] iio: dac: ad5446: Make use of devm_mutex_init() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use devm_mutex_init() which is helpful with CONFIG_DEBUG_MUTEXES. Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index c7876217c7ec..59e1f67ef334 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -184,7 +184,9 @@ int ad5446_probe(struct device *dev, const char *name, indio_dev->channels = &st->chip_info->channel; indio_dev->num_channels = 1; - mutex_init(&st->lock); + ret = devm_mutex_init(dev, &st->lock); + if (ret) + return ret; st->pwr_down_mode = MODE_PWRDWN_1k; From 308d4474cfa298dbf4a7e4f18314a86c38a51da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:14 +0000 Subject: [PATCH 134/150] iio: dac: ad5446: Make use of the cleanup helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the auto unlocking helpers from cleanup.h. Allows for some code simplification. While at it, don't use the ternary operator in ad5446_write_dac_powerdown() and add an helper function to write the DAC code. The reason for the function was purely to avoid having to use unreachable(). Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 45 +++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index 59e1f67ef334..1943e8014990 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -5,6 +5,7 @@ * Copyright 2010 Analog Devices Inc. */ +#include #include #include #include @@ -80,7 +81,7 @@ static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev, if (ret) return ret; - mutex_lock(&st->lock); + guard(mutex)(&st->lock); st->pwr_down = powerdown; if (st->pwr_down) { @@ -91,9 +92,10 @@ static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev, } ret = st->chip_info->write(st, val); - mutex_unlock(&st->lock); + if (ret) + return ret; - return ret ? ret : len; + return len; } const struct iio_chan_spec_ext_info ad5446_ext_info_powerdown[] = { @@ -129,32 +131,37 @@ static int ad5446_read_raw(struct iio_dev *indio_dev, return -EINVAL; } +static int ad5446_write_dac_raw(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + int val) +{ + struct ad5446_state *st = iio_priv(indio_dev); + + if (val >= (1 << chan->scan_type.realbits) || val < 0) + return -EINVAL; + + val <<= chan->scan_type.shift; + guard(mutex)(&st->lock); + + st->cached_val = val; + if (st->pwr_down) + return 0; + + return st->chip_info->write(st, val); +} + static int ad5446_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { - struct ad5446_state *st = iio_priv(indio_dev); - int ret = 0; - switch (mask) { case IIO_CHAN_INFO_RAW: - if (val >= (1 << chan->scan_type.realbits) || val < 0) - return -EINVAL; - - val <<= chan->scan_type.shift; - mutex_lock(&st->lock); - st->cached_val = val; - if (!st->pwr_down) - ret = st->chip_info->write(st, val); - mutex_unlock(&st->lock); - break; + return ad5446_write_dac_raw(indio_dev, chan, val); default: - ret = -EINVAL; + return -EINVAL; } - - return ret; } static const struct iio_info ad5446_info = { From bb5565a98660035370d8492085ed442151014954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:15 +0000 Subject: [PATCH 135/150] iio: dac: ad5446: Refactor header inclusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make sure include files are given in alphabetical order and that we include the ones that were missing and remove the ones we don't really use. Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 18 +++++++----------- drivers/iio/dac/ad5446.h | 1 + 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index 1943e8014990..0556ce78c2f6 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -5,21 +5,17 @@ * Copyright 2010 Analog Devices Inc. */ +#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include #include -#include - +#include #include -#include +#include +#include +#include +#include +#include #include "ad5446.h" diff --git a/drivers/iio/dac/ad5446.h b/drivers/iio/dac/ad5446.h index ee3d2c7d1764..6ba31d98f415 100644 --- a/drivers/iio/dac/ad5446.h +++ b/drivers/iio/dac/ad5446.h @@ -3,6 +3,7 @@ #define _LINUX_AD5446_H #include +#include #include #include #include From 8265cc284dc619aefb49fb07dde436bb3e8b52e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20S=C3=A1?= Date: Tue, 4 Nov 2025 15:35:16 +0000 Subject: [PATCH 136/150] iio: dac: ad5446: Fix coding style issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix style issues as reported by checkpatch. Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/drivers/iio/dac/ad5446.c b/drivers/iio/dac/ad5446.c index 0556ce78c2f6..46a2eadb1d9b 100644 --- a/drivers/iio/dac/ad5446.c +++ b/drivers/iio/dac/ad5446.c @@ -28,7 +28,8 @@ static const char * const ad5446_powerdown_modes[] = { }; static int ad5446_set_powerdown_mode(struct iio_dev *indio_dev, - const struct iio_chan_spec *chan, unsigned int mode) + const struct iio_chan_spec *chan, + unsigned int mode) { struct ad5446_state *st = iio_priv(indio_dev); @@ -38,7 +39,7 @@ static int ad5446_set_powerdown_mode(struct iio_dev *indio_dev, } static int ad5446_get_powerdown_mode(struct iio_dev *indio_dev, - const struct iio_chan_spec *chan) + const struct iio_chan_spec *chan) { struct ad5446_state *st = iio_priv(indio_dev); @@ -53,9 +54,9 @@ static const struct iio_enum ad5446_powerdown_mode_enum = { }; static ssize_t ad5446_read_dac_powerdown(struct iio_dev *indio_dev, - uintptr_t private, - const struct iio_chan_spec *chan, - char *buf) + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) { struct ad5446_state *st = iio_priv(indio_dev); @@ -63,9 +64,9 @@ static ssize_t ad5446_read_dac_powerdown(struct iio_dev *indio_dev, } static ssize_t ad5446_write_dac_powerdown(struct iio_dev *indio_dev, - uintptr_t private, - const struct iio_chan_spec *chan, - const char *buf, size_t len) + uintptr_t private, + const struct iio_chan_spec *chan, + const char *buf, size_t len) { struct ad5446_state *st = iio_priv(indio_dev); unsigned int shift; @@ -147,10 +148,8 @@ static int ad5446_write_dac_raw(struct iio_dev *indio_dev, } static int ad5446_write_raw(struct iio_dev *indio_dev, - struct iio_chan_spec const *chan, - int val, - int val2, - long mask) + struct iio_chan_spec const *chan, int val, + int val2, long mask) { switch (mask) { case IIO_CHAN_INFO_RAW: From 826ccaecbe06b9626bbc1863f892485e62af5813 Mon Sep 17 00:00:00 2001 From: Michael Hennerich Date: Tue, 4 Nov 2025 15:35:17 +0000 Subject: [PATCH 137/150] iio: dac: ad5446: Add AD5542 to the spi id table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for the AD5542 single channel Current Source and Voltage Output DACs. It is similar to the AD5542A model so just use the same id. Signed-off-by: Michael Hennerich Co-developed-by: Nuno Sá Signed-off-by: Nuno Sá Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/dac/ad5446-spi.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/iio/dac/ad5446-spi.c b/drivers/iio/dac/ad5446-spi.c index dfba1972d1e0..e29d77f21482 100644 --- a/drivers/iio/dac/ad5446-spi.c +++ b/drivers/iio/dac/ad5446-spi.c @@ -175,6 +175,7 @@ static const struct spi_device_id ad5446_spi_ids[] = { {"ad5453", (kernel_ulong_t)&ad5446_chip_info}, /* ad5453 is compatible to the ad5446 */ {"ad5512a", (kernel_ulong_t)&ad5512a_chip_info}, {"ad5541a", (kernel_ulong_t)&ad5541a_chip_info}, + {"ad5542", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5542 are compatible */ {"ad5542a", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5542a are compatible */ {"ad5543", (kernel_ulong_t)&ad5541a_chip_info}, /* ad5541a and ad5543 are compatible */ {"ad5553", (kernel_ulong_t)&ad5553_chip_info}, @@ -211,6 +212,7 @@ static const struct of_device_id ad5446_of_ids[] = { { .compatible = "adi,ad5453", .data = &ad5446_chip_info }, { .compatible = "adi,ad5512a", .data = &ad5512a_chip_info }, { .compatible = "adi,ad5541a", .data = &ad5541a_chip_info }, + { .compatible = "adi,ad5542", .data = &ad5541a_chip_info }, { .compatible = "adi,ad5542a", .data = &ad5541a_chip_info }, { .compatible = "adi,ad5543", .data = &ad5541a_chip_info }, { .compatible = "adi,ad5553", .data = &ad5553_chip_info }, From 9eb98a05f050be20e60852f8f0ddf34c7e49740d Mon Sep 17 00:00:00 2001 From: Marco Crivellari Date: Wed, 5 Nov 2025 11:43:30 +0100 Subject: [PATCH 138/150] iio: adc: pac1934: replace use of system_wq with system_percpu_wq Currently if a user enqueues a work item using schedule_delayed_work() the used wq is "system_wq" (per-cpu wq) while queue_delayed_work() use WORK_CPU_UNBOUND (used when a cpu is not specified). The same applies to schedule_work() that is using system_wq and queue_work(), that makes use again of WORK_CPU_UNBOUND. This lack of consistency cannot be addressed without refactoring the API. This patch continues the effort to refactor worqueue APIs, which has begun with the change introducing new workqueues and a new alloc_workqueue flag: commit 128ea9f6ccfb ("workqueue: Add system_percpu_wq and system_dfl_wq") commit 930c2ea566af ("workqueue: Add new WQ_PERCPU flag") system_percpu_wq replaced system_wq, so change the wq in iio/adc/pac1934. The old wq (system_wq) will be kept for a few release cycles. Suggested-by: Tejun Heo Signed-off-by: Marco Crivellari Signed-off-by: Jonathan Cameron --- drivers/iio/adc/pac1934.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/adc/pac1934.c b/drivers/iio/adc/pac1934.c index 48df16509260..ec96bb0f2ed6 100644 --- a/drivers/iio/adc/pac1934.c +++ b/drivers/iio/adc/pac1934.c @@ -768,7 +768,7 @@ static int pac1934_retrieve_data(struct pac1934_chip_info *info, * Re-schedule the work for the read registers on timeout * (to prevent chip registers saturation) */ - mod_delayed_work(system_wq, &info->work_chip_rfsh, + mod_delayed_work(system_percpu_wq, &info->work_chip_rfsh, msecs_to_jiffies(PAC1934_MAX_RFSH_LIMIT_MS)); } From d16d1c2553248f9b859b86c94344d8b81f0297cd Mon Sep 17 00:00:00 2001 From: Remi Buisson Date: Thu, 6 Nov 2025 15:31:07 +0000 Subject: [PATCH 139/150] iio: imu: inv_icm45600: Initializes inv_icm45600_buffer_postdisable() sleep The sleep variable in inv_icm45600_buffer_postdisable() could be used without being assigned in case of error. It must be initialized to 0 by default. Fixes: 06674a72cf7a ("iio: imu: inv_icm45600: add buffer support in iio devices") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-iio/aPi6Xw-ZoUkW76zR@stanley.mountain/ Signed-off-by: Remi Buisson Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c index 2efcc177f9d6..2b9ea317385c 100644 --- a/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c +++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c @@ -370,6 +370,7 @@ static int inv_icm45600_buffer_postdisable(struct iio_dev *indio_dev) return -EINVAL; } + sleep = 0; scoped_guard(mutex, &st->lock) ret = _inv_icm45600_buffer_postdisable(st, sensor, watermark, &sleep); From 8775ebd25abcdedb6f3ddf1c3ad69277f9b76081 Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Fri, 14 Nov 2025 19:13:55 -0300 Subject: [PATCH 140/150] dt-bindings: iio: accel: adxl380: add new supported parts Include ADXL318 and ADXL319 accelerometers to the documentation. The ADXL318 is based on the ADXL380, while the ADXL319 is based on the ADXL382. However, the ADXL318/319 do not support some built-in features like single tap, double tap and triple tap detection, and also activity and inactivity detection. Signed-off-by: Jonathan Santos Acked-by: Krzysztof Kozlowski Signed-off-by: Jonathan Cameron --- .../devicetree/bindings/iio/accel/adi,adxl380.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml b/Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml index f1ff5ff4f478..ab517720a6a7 100644 --- a/Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml +++ b/Documentation/devicetree/bindings/iio/accel/adi,adxl380.yaml @@ -11,16 +11,19 @@ maintainers: - Antoniu Miclaus description: | - The ADXL380/ADXL382 is a low noise density, low power, 3-axis - accelerometer with selectable measurement ranges. The ADXL380 - supports the ±4 g, ±8 g, and ±16 g ranges, and the ADXL382 supports - ±15 g, ±30 g, and ±60 g ranges. + The ADXL380/ADXL382 and ADXL318/ADXL319 are low noise density, + low power, 3-axis accelerometers with selectable measurement ranges. + The ADXL380 and ADXL318 support the ±4 g, ±8 g, and ±16 g ranges, + while the ADXL382 and ADXL319 support ±15 g, ±30 g, and ±60 g ranges. + https://www.analog.com/en/products/adxl318.html https://www.analog.com/en/products/adxl380.html properties: compatible: enum: + - adi,adxl318 + - adi,adxl319 - adi,adxl380 - adi,adxl382 From 0ecad1964315bf171d4eb0874da65e3a7aeb1b3d Mon Sep 17 00:00:00 2001 From: Jonathan Santos Date: Fri, 14 Nov 2025 19:14:02 -0300 Subject: [PATCH 141/150] iio: accel: adxl380: add support for ADXL318 and ADXL319 The ADXL318 and ADXL319 are low noise density, low power, 3-axis accelerometers based on ADXL380 and ADXL382, respectively. The main difference between the new parts and the existing ones are the absence of interrupts and events like tap detection, activity/inactivity, and free-fall detection. Other differences in the new parts are fewer power modes, basically allowing only idle and measurement modes, and the removal of the 12-bit SAR ADC path for the 3-axis signals (known as lower signal chain), being excluisive for the temperature sensor in the ADXL318/319. Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Santos Signed-off-by: Jonathan Cameron --- drivers/iio/accel/adxl380.c | 134 ++++++++++++++++++++++---------- drivers/iio/accel/adxl380.h | 4 + drivers/iio/accel/adxl380_i2c.c | 4 + drivers/iio/accel/adxl380_spi.c | 4 + 4 files changed, 107 insertions(+), 39 deletions(-) diff --git a/drivers/iio/accel/adxl380.c b/drivers/iio/accel/adxl380.c index 0cf3c6815829..6d5f1a0d51e9 100644 --- a/drivers/iio/accel/adxl380.c +++ b/drivers/iio/accel/adxl380.c @@ -26,7 +26,9 @@ #include "adxl380.h" #define ADXL380_ID_VAL 380 +#define ADXL318_ID_VAL 380 #define ADXL382_ID_VAL 382 +#define ADXL319_ID_VAL 382 #define ADXL380_DEVID_AD_REG 0x00 #define ADLX380_PART_ID_REG 0x02 @@ -178,41 +180,6 @@ enum adxl380_tap_time_type { static const int adxl380_range_scale_factor_tbl[] = { 1, 2, 4 }; -const struct adxl380_chip_info adxl380_chip_info = { - .name = "adxl380", - .chip_id = ADXL380_ID_VAL, - .scale_tbl = { - [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 }, - [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, - [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, - }, - .samp_freq_tbl = { 8000, 16000, 32000 }, - /* - * The datasheet defines an intercept of 470 LSB at 25 degC - * and a sensitivity of 10.2 LSB/C. - */ - .temp_offset = 25 * 102 / 10 - 470, - -}; -EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, "IIO_ADXL380"); - -const struct adxl380_chip_info adxl382_chip_info = { - .name = "adxl382", - .chip_id = ADXL382_ID_VAL, - .scale_tbl = { - [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 }, - [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, - [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, - }, - .samp_freq_tbl = { 16000, 32000, 64000 }, - /* - * The datasheet defines an intercept of 570 LSB at 25 degC - * and a sensitivity of 10.2 LSB/C. - */ - .temp_offset = 25 * 102 / 10 - 570, -}; -EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, "IIO_ADXL380"); - static const unsigned int adxl380_th_reg_high_addr[2] = { [ADXL380_ACTIVITY] = ADXL380_THRESH_ACT_H_REG, [ADXL380_INACTIVITY] = ADXL380_THRESH_INACT_H_REG, @@ -276,9 +243,14 @@ static int adxl380_set_measure_en(struct adxl380_state *st, bool en) if (ret) return ret; - /* Activity/ Inactivity detection available only in VLP/ULP mode */ - if (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) || - FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl)) + /* + * Activity/Inactivity detection available only in VLP/ULP + * mode and for devices that support low power modes. Otherwise + * go straight to measure mode (same bits as ADXL380_OP_MODE_HP). + */ + if (st->chip_info->has_low_power && + (FIELD_GET(ADXL380_ACT_EN_MSK, act_inact_ctl) || + FIELD_GET(ADXL380_INACT_EN_MSK, act_inact_ctl))) op_mode = ADXL380_OP_MODE_VLP; else op_mode = ADXL380_OP_MODE_HP; @@ -1618,6 +1590,15 @@ static int adxl380_set_watermark(struct iio_dev *indio_dev, unsigned int val) return 0; } +static const struct iio_info adxl318_info = { + .read_raw = adxl380_read_raw, + .read_avail = &adxl380_read_avail, + .write_raw = adxl380_write_raw, + .write_raw_get_fmt = adxl380_write_raw_get_fmt, + .debugfs_reg_access = &adxl380_reg_access, + .hwfifo_set_watermark = adxl380_set_watermark, +}; + static const struct iio_info adxl380_info = { .read_raw = adxl380_read_raw, .read_avail = &adxl380_read_avail, @@ -1632,6 +1613,81 @@ static const struct iio_info adxl380_info = { .hwfifo_set_watermark = adxl380_set_watermark, }; +const struct adxl380_chip_info adxl318_chip_info = { + .name = "adxl318", + .chip_id = ADXL318_ID_VAL, + .scale_tbl = { + [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 }, + [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, + [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, + }, + .samp_freq_tbl = { 8000, 16000, 32000 }, + /* + * The datasheet defines an intercept of 550 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 550, + .info = &adxl318_info, +}; +EXPORT_SYMBOL_NS_GPL(adxl318_chip_info, "IIO_ADXL380"); + +const struct adxl380_chip_info adxl319_chip_info = { + .name = "adxl319", + .chip_id = ADXL319_ID_VAL, + .scale_tbl = { + [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 }, + [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, + [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, + }, + .samp_freq_tbl = { 16000, 32000, 64000 }, + /* + * The datasheet defines an intercept of 550 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 550, + .info = &adxl318_info, +}; +EXPORT_SYMBOL_NS_GPL(adxl319_chip_info, "IIO_ADXL380"); + +const struct adxl380_chip_info adxl380_chip_info = { + .name = "adxl380", + .chip_id = ADXL380_ID_VAL, + .scale_tbl = { + [ADXL380_OP_MODE_4G_RANGE] = { 0, 1307226 }, + [ADXL380_OP_MODE_8G_RANGE] = { 0, 2615434 }, + [ADXL380_OP_MODE_16G_RANGE] = { 0, 5229886 }, + }, + .samp_freq_tbl = { 8000, 16000, 32000 }, + /* + * The datasheet defines an intercept of 470 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 470, + .has_low_power = true, + .info = &adxl380_info, + +}; +EXPORT_SYMBOL_NS_GPL(adxl380_chip_info, "IIO_ADXL380"); + +const struct adxl380_chip_info adxl382_chip_info = { + .name = "adxl382", + .chip_id = ADXL382_ID_VAL, + .scale_tbl = { + [ADXL382_OP_MODE_15G_RANGE] = { 0, 4903325 }, + [ADXL382_OP_MODE_30G_RANGE] = { 0, 9806650 }, + [ADXL382_OP_MODE_60G_RANGE] = { 0, 19613300 }, + }, + .samp_freq_tbl = { 16000, 32000, 64000 }, + /* + * The datasheet defines an intercept of 570 LSB at 25 degC + * and a sensitivity of 10.2 LSB/C. + */ + .temp_offset = 25 * 102 / 10 - 570, + .has_low_power = true, + .info = &adxl380_info, +}; +EXPORT_SYMBOL_NS_GPL(adxl382_chip_info, "IIO_ADXL380"); + static const struct iio_event_spec adxl380_events[] = { { .type = IIO_EV_TYPE_THRESH, @@ -1866,7 +1922,7 @@ int adxl380_probe(struct device *dev, struct regmap *regmap, indio_dev->channels = adxl380_channels; indio_dev->num_channels = ARRAY_SIZE(adxl380_channels); indio_dev->name = chip_info->name; - indio_dev->info = &adxl380_info; + indio_dev->info = chip_info->info; indio_dev->modes = INDIO_DIRECT_MODE; ret = devm_regulator_get_enable(dev, "vddio"); diff --git a/drivers/iio/accel/adxl380.h b/drivers/iio/accel/adxl380.h index a683625d897a..e67c5aab8efc 100644 --- a/drivers/iio/accel/adxl380.h +++ b/drivers/iio/accel/adxl380.h @@ -12,10 +12,14 @@ struct adxl380_chip_info { const char *name; const int scale_tbl[3][2]; const int samp_freq_tbl[3]; + const struct iio_info *info; const int temp_offset; const u16 chip_id; + const bool has_low_power; }; +extern const struct adxl380_chip_info adxl318_chip_info; +extern const struct adxl380_chip_info adxl319_chip_info; extern const struct adxl380_chip_info adxl380_chip_info; extern const struct adxl380_chip_info adxl382_chip_info; diff --git a/drivers/iio/accel/adxl380_i2c.c b/drivers/iio/accel/adxl380_i2c.c index b4f86f972361..bd8782d08c7d 100644 --- a/drivers/iio/accel/adxl380_i2c.c +++ b/drivers/iio/accel/adxl380_i2c.c @@ -33,6 +33,8 @@ static int adxl380_i2c_probe(struct i2c_client *client) } static const struct i2c_device_id adxl380_i2c_id[] = { + { "adxl318", (kernel_ulong_t)&adxl318_chip_info }, + { "adxl319", (kernel_ulong_t)&adxl319_chip_info }, { "adxl380", (kernel_ulong_t)&adxl380_chip_info }, { "adxl382", (kernel_ulong_t)&adxl382_chip_info }, { } @@ -40,6 +42,8 @@ static const struct i2c_device_id adxl380_i2c_id[] = { MODULE_DEVICE_TABLE(i2c, adxl380_i2c_id); static const struct of_device_id adxl380_of_match[] = { + { .compatible = "adi,adxl318", .data = &adxl318_chip_info }, + { .compatible = "adi,adxl319", .data = &adxl319_chip_info }, { .compatible = "adi,adxl380", .data = &adxl380_chip_info }, { .compatible = "adi,adxl382", .data = &adxl382_chip_info }, { } diff --git a/drivers/iio/accel/adxl380_spi.c b/drivers/iio/accel/adxl380_spi.c index 6edd0d211ffa..4ead949b24f1 100644 --- a/drivers/iio/accel/adxl380_spi.c +++ b/drivers/iio/accel/adxl380_spi.c @@ -35,6 +35,8 @@ static int adxl380_spi_probe(struct spi_device *spi) } static const struct spi_device_id adxl380_spi_id[] = { + { "adxl318", (kernel_ulong_t)&adxl318_chip_info }, + { "adxl319", (kernel_ulong_t)&adxl319_chip_info }, { "adxl380", (kernel_ulong_t)&adxl380_chip_info }, { "adxl382", (kernel_ulong_t)&adxl382_chip_info }, { } @@ -42,6 +44,8 @@ static const struct spi_device_id adxl380_spi_id[] = { MODULE_DEVICE_TABLE(spi, adxl380_spi_id); static const struct of_device_id adxl380_of_match[] = { + { .compatible = "adi,adxl318", .data = &adxl318_chip_info }, + { .compatible = "adi,adxl319", .data = &adxl319_chip_info }, { .compatible = "adi,adxl380", .data = &adxl380_chip_info }, { .compatible = "adi,adxl382", .data = &adxl382_chip_info }, { } From f5d203467a31798191365efeb16cd619d2c8f23a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 12 Nov 2025 15:55:08 +0100 Subject: [PATCH 142/150] iio: core: add missing mutex_destroy in iio_dev_release() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing mutex_destroy() call in iio_dev_release() to properly clean up the mutex initialized in iio_device_alloc(). Ensure proper resource cleanup and follows kernel practices. Found by code review. While at it, create a lockdep key before mutex initialisation. This will help with converting it to the better API in the future. Fixes: 847ec80bbaa7 ("Staging: IIO: core support for device registration and management") Fixes: ac917a81117c ("staging:iio:core set the iio_dev.info pointer to null on unregister under lock.") Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 88c3d585a1bd..93d6e5b101cf 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1654,6 +1654,9 @@ static void iio_dev_release(struct device *device) iio_device_detach_buffers(indio_dev); + mutex_destroy(&iio_dev_opaque->info_exist_lock); + mutex_destroy(&iio_dev_opaque->mlock); + lockdep_unregister_key(&iio_dev_opaque->mlock_key); ida_free(&iio_ida, iio_dev_opaque->id); @@ -1698,8 +1701,7 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) indio_dev->dev.type = &iio_device_type; indio_dev->dev.bus = &iio_bus_type; device_initialize(&indio_dev->dev); - mutex_init(&iio_dev_opaque->mlock); - mutex_init(&iio_dev_opaque->info_exist_lock); + INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); iio_dev_opaque->id = ida_alloc(&iio_ida, GFP_KERNEL); @@ -1722,6 +1724,9 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) lockdep_register_key(&iio_dev_opaque->mlock_key); lockdep_set_class(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); + mutex_init(&iio_dev_opaque->mlock); + mutex_init(&iio_dev_opaque->info_exist_lock); + return indio_dev; } EXPORT_SYMBOL(iio_device_alloc); From b0e6871415b25f5e84a79621834e3d0c9d4627a6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 12 Nov 2025 15:55:09 +0100 Subject: [PATCH 143/150] iio: core: Clean up device correctly on iio_device_alloc() failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once we called device_initialize() we have to call put_device() on it. Refactor the code to make it in the right order. Fixes: fe6f45f6ba22 ("iio: core: check return value when calling dev_set_name()") Fixes: 847ec80bbaa7 ("Staging: IIO: core support for device registration and management") Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 93d6e5b101cf..5d2f35cf18bc 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1697,11 +1697,6 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) ACCESS_PRIVATE(indio_dev, priv) = (char *)iio_dev_opaque + ALIGN(sizeof(*iio_dev_opaque), IIO_DMA_MINALIGN); - indio_dev->dev.parent = parent; - indio_dev->dev.type = &iio_device_type; - indio_dev->dev.bus = &iio_bus_type; - device_initialize(&indio_dev->dev); - INIT_LIST_HEAD(&iio_dev_opaque->channel_attr_list); iio_dev_opaque->id = ida_alloc(&iio_ida, GFP_KERNEL); @@ -1727,6 +1722,11 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) mutex_init(&iio_dev_opaque->mlock); mutex_init(&iio_dev_opaque->info_exist_lock); + indio_dev->dev.parent = parent; + indio_dev->dev.type = &iio_device_type; + indio_dev->dev.bus = &iio_bus_type; + device_initialize(&indio_dev->dev); + return indio_dev; } EXPORT_SYMBOL(iio_device_alloc); From c76ba4b2644424b8dbacee80bb40991eac29d39e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 12 Nov 2025 15:55:10 +0100 Subject: [PATCH 144/150] iio: core: Replace lockdep_set_class() + mutex_init() by combined call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace lockdep_set_class() + mutex_init() by combined call mutex_init_with_key(). Signed-off-by: Andy Shevchenko Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c index 5d2f35cf18bc..f69deefcfb6f 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -1717,9 +1717,8 @@ struct iio_dev *iio_device_alloc(struct device *parent, int sizeof_priv) INIT_LIST_HEAD(&iio_dev_opaque->ioctl_handlers); lockdep_register_key(&iio_dev_opaque->mlock_key); - lockdep_set_class(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); - mutex_init(&iio_dev_opaque->mlock); + mutex_init_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key); mutex_init(&iio_dev_opaque->info_exist_lock); indio_dev->dev.parent = parent; From 0de73abe5f5c2b58d66d6dcb7d44df05b0f73684 Mon Sep 17 00:00:00 2001 From: Liang Jie Date: Fri, 14 Nov 2025 16:47:25 +0800 Subject: [PATCH 145/150] iio: buffer: use dma_buf_unmap_attachment_unlocked() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace open-coded dma_resv_lock()/dma_resv_unlock() around dma_buf_unmap_attachment() in iio_buffer_dmabuf_release() with the dma_buf_unmap_attachment_unlocked() helper. This aligns with the standard DMA-BUF API, avoids duplicating locking logic and eases future maintenance. No functional change. Reviewed-by: fanggeng Signed-off-by: Liang Jie Reviewed-by: Nuno Sá Signed-off-by: Jonathan Cameron --- drivers/iio/industrialio-buffer.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c index f1448ae1b843..b9ebb6f43404 100644 --- a/drivers/iio/industrialio-buffer.c +++ b/drivers/iio/industrialio-buffer.c @@ -1563,9 +1563,7 @@ static void iio_buffer_dmabuf_release(struct kref *ref) struct iio_buffer *buffer = priv->buffer; struct dma_buf *dmabuf = attach->dmabuf; - dma_resv_lock(dmabuf->resv, NULL); - dma_buf_unmap_attachment(attach, priv->sgt, priv->dir); - dma_resv_unlock(dmabuf->resv); + dma_buf_unmap_attachment_unlocked(attach, priv->sgt, priv->dir); buffer->access->detach_dmabuf(buffer, priv->block); From 47e4b1ca441cc4f1d9db13ff5e9b89e53aae0198 Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 12 Nov 2025 23:56:59 +0100 Subject: [PATCH 146/150] iio: mpl3115: use get_unaligned_be24() to retrieve pressure data The pressure measurement result is arranged as 20-bit unsigned value residing in three 8-bit registers. Hence, it can be retrieved using get_unaligned_be24() and by applying 4-bit shift. Reviewed-by: Marcelo Schmitt Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mpl3115.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index c212dfdf59ff..5594256fffbd 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -125,7 +126,7 @@ static int mpl3115_read_info_raw(struct mpl3115_data *data, switch (chan->type) { case IIO_PRESSURE: { /* in 0.25 pascal / LSB */ - __be32 tmp = 0; + u8 press_be24[3]; guard(mutex)(&data->lock); ret = mpl3115_request(data); @@ -134,11 +135,17 @@ static int mpl3115_read_info_raw(struct mpl3115_data *data, ret = i2c_smbus_read_i2c_block_data(data->client, MPL3115_OUT_PRESS, - 3, (u8 *) &tmp); + sizeof(press_be24), + press_be24); if (ret < 0) return ret; - *val = be32_to_cpu(tmp) >> chan->scan_type.shift; + /* + * The pressure channel shift is applied in the case where the + * data (24-bit big endian) is read into a 32-bit buffer. Here + * the data is stored in a 24-bit buffer, so the shift is 4. + */ + *val = get_unaligned_be24(press_be24) >> 4; return IIO_VAL_INT; } case IIO_TEMP: { /* in 0.0625 celsius / LSB */ From 6062cd20cbea6006d30af50e6f7d2a8722baa81b Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 12 Nov 2025 23:57:00 +0100 Subject: [PATCH 147/150] iio: mpl3115: add threshold events support Add support for pressure and temperature rising threshold events. For both channels *_en and *_value (in raw units) attributes are exposed. Since in write_event_config() the ctrl_reg1.active and ctrl_reg4 are modified, accessing the data->ctrl_reg{1,4} in set_trigger_state() and write_event_config() needs to be now guarded by data->lock. Otherwise, it would be possible that 2 concurrent threads executing these functions would access the data->ctrl_reg{1,4} at the same time and then one would overwrite the other's result. Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/mpl3115.c | 223 +++++++++++++++++++++++++++++++-- 1 file changed, 212 insertions(+), 11 deletions(-) diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c index 5594256fffbd..aeac1586f12e 100644 --- a/drivers/iio/pressure/mpl3115.c +++ b/drivers/iio/pressure/mpl3115.c @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -31,6 +33,8 @@ #define MPL3115_WHO_AM_I 0x0c #define MPL3115_INT_SOURCE 0x12 #define MPL3115_PT_DATA_CFG 0x13 +#define MPL3115_PRESS_TGT 0x16 /* MSB first, 16 bit */ +#define MPL3115_TEMP_TGT 0x18 #define MPL3115_CTRL_REG1 0x26 #define MPL3115_CTRL_REG2 0x27 #define MPL3115_CTRL_REG3 0x28 @@ -43,6 +47,8 @@ #define MPL3115_STATUS_TEMP_RDY BIT(1) #define MPL3115_INT_SRC_DRDY BIT(7) +#define MPL3115_INT_SRC_PTH BIT(3) +#define MPL3115_INT_SRC_TTH BIT(2) #define MPL3115_PT_DATA_EVENT_ALL GENMASK(2, 0) @@ -57,6 +63,8 @@ #define MPL3115_CTRL3_IPOL2 BIT(1) #define MPL3115_CTRL4_INT_EN_DRDY BIT(7) +#define MPL3115_CTRL4_INT_EN_PTH BIT(3) +#define MPL3115_CTRL4_INT_EN_TTH BIT(2) #define MPL3115_CTRL5_INT_CFG_DRDY BIT(7) @@ -84,6 +92,7 @@ struct mpl3115_data { struct iio_trigger *drdy_trig; struct mutex lock; u8 ctrl_reg1; + u8 ctrl_reg4; }; enum mpl3115_irq_pin { @@ -313,6 +322,15 @@ done: return IRQ_HANDLED; } +static const struct iio_event_spec mpl3115_temp_press_event[] = { + { + .type = IIO_EV_TYPE_THRESH, + .dir = IIO_EV_DIR_RISING, + .mask_separate = BIT(IIO_EV_INFO_ENABLE) | + BIT(IIO_EV_INFO_VALUE), + }, +}; + static const struct iio_chan_spec mpl3115_channels[] = { { .type = IIO_PRESSURE, @@ -328,7 +346,9 @@ static const struct iio_chan_spec mpl3115_channels[] = { .storagebits = 32, .shift = 12, .endianness = IIO_BE, - } + }, + .event_spec = mpl3115_temp_press_event, + .num_event_specs = ARRAY_SIZE(mpl3115_temp_press_event), }, { .type = IIO_TEMP, @@ -344,7 +364,9 @@ static const struct iio_chan_spec mpl3115_channels[] = { .storagebits = 16, .shift = 4, .endianness = IIO_BE, - } + }, + .event_spec = mpl3115_temp_press_event, + .num_event_specs = ARRAY_SIZE(mpl3115_temp_press_event), }, IIO_CHAN_SOFT_TIMESTAMP(2), }; @@ -354,15 +376,46 @@ static irqreturn_t mpl3115_interrupt_handler(int irq, void *private) struct iio_dev *indio_dev = private; struct mpl3115_data *data = iio_priv(indio_dev); int ret; + u8 val_press[3]; + __be16 val_temp; ret = i2c_smbus_read_byte_data(data->client, MPL3115_INT_SOURCE); if (ret < 0) return IRQ_HANDLED; - if (!(ret & MPL3115_INT_SRC_DRDY)) + if (!(ret & (MPL3115_INT_SRC_TTH | MPL3115_INT_SRC_PTH | + MPL3115_INT_SRC_DRDY))) return IRQ_NONE; - iio_trigger_poll_nested(data->drdy_trig); + if (ret & MPL3115_INT_SRC_DRDY) + iio_trigger_poll_nested(data->drdy_trig); + + if (ret & MPL3115_INT_SRC_PTH) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_PRESSURE, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + /* Reset the SRC_PTH bit in INT_SOURCE */ + i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_PRESS, + sizeof(val_press), val_press); + } + + if (ret & MPL3115_INT_SRC_TTH) { + iio_push_event(indio_dev, + IIO_UNMOD_EVENT_CODE(IIO_TEMP, 0, + IIO_EV_TYPE_THRESH, + IIO_EV_DIR_RISING), + iio_get_time_ns(indio_dev)); + + /* Reset the SRC_TTH bit in INT_SOURCE */ + i2c_smbus_read_i2c_block_data(data->client, + MPL3115_OUT_TEMP, + sizeof(val_temp), + (u8 *)&val_temp); + } return IRQ_HANDLED; } @@ -383,6 +436,7 @@ static int mpl3115_config_interrupt(struct mpl3115_data *data, goto reg1_cleanup; data->ctrl_reg1 = ctrl_reg1; + data->ctrl_reg4 = ctrl_reg4; return 0; @@ -396,16 +450,23 @@ static int mpl3115_set_trigger_state(struct iio_trigger *trig, bool state) { struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig); struct mpl3115_data *data = iio_priv(indio_dev); - u8 ctrl_reg1 = data->ctrl_reg1; - u8 ctrl_reg4 = state ? MPL3115_CTRL4_INT_EN_DRDY : 0; - - if (state) - ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; - else - ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + u8 ctrl_reg1, ctrl_reg4; guard(mutex)(&data->lock); + ctrl_reg1 = data->ctrl_reg1; + ctrl_reg4 = data->ctrl_reg4; + + if (state) { + ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; + ctrl_reg4 |= MPL3115_CTRL4_INT_EN_DRDY; + } else { + ctrl_reg4 &= ~MPL3115_CTRL4_INT_EN_DRDY; + + if (!ctrl_reg4) + ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + } + return mpl3115_config_interrupt(data, ctrl_reg1, ctrl_reg4); } @@ -413,10 +474,150 @@ static const struct iio_trigger_ops mpl3115_trigger_ops = { .set_trigger_state = mpl3115_set_trigger_state, }; +static int mpl3115_read_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + + if (chan->type == IIO_PRESSURE) + return !!(data->ctrl_reg4 & MPL3115_CTRL4_INT_EN_PTH); + + if (chan->type == IIO_TEMP) + return !!(data->ctrl_reg4 & MPL3115_CTRL4_INT_EN_TTH); + + return -EINVAL; +} + +static int mpl3115_write_event_config(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + bool state) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + u8 int_en_mask; + u8 ctrl_reg1, ctrl_reg4; + + switch (chan->type) { + case IIO_PRESSURE: + int_en_mask = MPL3115_CTRL4_INT_EN_PTH; + break; + case IIO_TEMP: + int_en_mask = MPL3115_CTRL4_INT_EN_TTH; + break; + default: + return -EINVAL; + } + + guard(mutex)(&data->lock); + + ctrl_reg1 = data->ctrl_reg1; + ctrl_reg4 = data->ctrl_reg4; + + if (state) { + ctrl_reg1 |= MPL3115_CTRL1_ACTIVE; + ctrl_reg4 |= int_en_mask; + } else { + ctrl_reg4 &= ~int_en_mask; + + if (!ctrl_reg4) + ctrl_reg1 &= ~MPL3115_CTRL1_ACTIVE; + } + + return mpl3115_config_interrupt(data, ctrl_reg1, ctrl_reg4); +} + +static int mpl3115_read_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int *val, int *val2) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + int ret; + __be16 press_tgt; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + switch (chan->type) { + case IIO_PRESSURE: + ret = i2c_smbus_read_i2c_block_data(data->client, + MPL3115_PRESS_TGT, + sizeof(press_tgt), + (u8 *)&press_tgt); + if (ret < 0) + return ret; + + /* + * Target value for the pressure is 16-bit unsigned value, + * expressed in 2 Pa units + */ + *val = be16_to_cpu(press_tgt) << 1; + + return IIO_VAL_INT; + case IIO_TEMP: + ret = i2c_smbus_read_byte_data(data->client, MPL3115_TEMP_TGT); + if (ret < 0) + return ret; + + /* Target value for the temperature is 8-bit 2's complement */ + *val = sign_extend32(ret, 7); + + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int mpl3115_write_thresh(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + enum iio_event_type type, + enum iio_event_direction dir, + enum iio_event_info info, + int val, int val2) +{ + struct mpl3115_data *data = iio_priv(indio_dev); + __be16 press_tgt; + + if (info != IIO_EV_INFO_VALUE) + return -EINVAL; + + switch (chan->type) { + case IIO_PRESSURE: + val >>= 1; + + if (val < 0 || val > U16_MAX) + return -EINVAL; + + press_tgt = cpu_to_be16(val); + + return i2c_smbus_write_i2c_block_data(data->client, + MPL3115_PRESS_TGT, + sizeof(press_tgt), + (u8 *)&press_tgt); + case IIO_TEMP: + if (val < S8_MIN || val > S8_MAX) + return -EINVAL; + + return i2c_smbus_write_byte_data(data->client, + MPL3115_TEMP_TGT, val); + default: + return -EINVAL; + } +} + static const struct iio_info mpl3115_info = { .read_raw = &mpl3115_read_raw, .read_avail = &mpl3115_read_avail, .write_raw = &mpl3115_write_raw, + .read_event_config = mpl3115_read_event_config, + .write_event_config = mpl3115_write_event_config, + .read_event_value = mpl3115_read_thresh, + .write_event_value = mpl3115_write_thresh, }; static int mpl3115_trigger_probe(struct mpl3115_data *data, From 28b53b35c037c05bfedd28ef027e80a1b505f4aa Mon Sep 17 00:00:00 2001 From: Antoni Pokusinski Date: Wed, 12 Nov 2025 23:57:01 +0100 Subject: [PATCH 148/150] iio: ABI: document pressure event attributes Add sysfs pressure event attributes exposed by the mpl3115 driver. These allow controlling the threshold value and the enable state. Reviewed-by: Marcelo Schmitt Signed-off-by: Antoni Pokusinski Signed-off-by: Jonathan Cameron --- Documentation/ABI/testing/sysfs-bus-iio | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/testing/sysfs-bus-iio index 352ab7b8476c..5f87dcee78f7 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -898,6 +898,7 @@ What: /sys/.../iio:deviceX/events/in_tempY_thresh_rising_en What: /sys/.../iio:deviceX/events/in_tempY_thresh_falling_en What: /sys/.../iio:deviceX/events/in_capacitanceY_thresh_rising_en What: /sys/.../iio:deviceX/events/in_capacitanceY_thresh_falling_en +What: /sys/.../iio:deviceX/events/in_pressure_thresh_rising_en KernelVersion: 2.6.37 Contact: linux-iio@vger.kernel.org Description: @@ -1047,6 +1048,7 @@ What: /sys/.../events/in_capacitanceY_thresh_rising_value What: /sys/.../events/in_capacitanceY_thresh_falling_value What: /sys/.../events/in_capacitanceY_thresh_adaptive_rising_value What: /sys/.../events/in_capacitanceY_thresh_falling_rising_value +What: /sys/.../events/in_pressure_thresh_rising_value KernelVersion: 2.6.37 Contact: linux-iio@vger.kernel.org Description: From 02d44a1b64f11cdbcd5349063f149309c42a9fa5 Mon Sep 17 00:00:00 2001 From: Akhilesh Patil Date: Sun, 16 Nov 2025 11:48:49 +0530 Subject: [PATCH 149/150] iio: pressure: Arrange Makefile alphabetically Fix hp206c and st_pressure_* entries in pressure Makefiles to follow alphabetical order as per guideline mentioned in iio/pressure/Makefile. Signed-off-by: Akhilesh Patil Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/iio/pressure/Makefile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile index 47bf7656f975..a21443e992b9 100644 --- a/drivers/iio/pressure/Makefile +++ b/drivers/iio/pressure/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_DPS310) += dps310.o obj-$(CONFIG_IIO_CROS_EC_BARO) += cros_ec_baro.o obj-$(CONFIG_HID_SENSOR_PRESS) += hid-sensor-press.o obj-$(CONFIG_HP03) += hp03.o +obj-$(CONFIG_HP206C) += hp206c.o obj-$(CONFIG_HSC030PA) += hsc030pa.o obj-$(CONFIG_HSC030PA_I2C) += hsc030pa_i2c.o obj-$(CONFIG_HSC030PA_SPI) += hsc030pa_spi.o @@ -35,11 +36,9 @@ obj-$(CONFIG_SDP500) += sdp500.o obj-$(CONFIG_IIO_ST_PRESS) += st_pressure.o st_pressure-y := st_pressure_core.o st_pressure-$(CONFIG_IIO_BUFFER) += st_pressure_buffer.o +obj-$(CONFIG_IIO_ST_PRESS_I2C) += st_pressure_i2c.o +obj-$(CONFIG_IIO_ST_PRESS_SPI) += st_pressure_spi.o obj-$(CONFIG_T5403) += t5403.o -obj-$(CONFIG_HP206C) += hp206c.o obj-$(CONFIG_ZPA2326) += zpa2326.o obj-$(CONFIG_ZPA2326_I2C) += zpa2326_i2c.o obj-$(CONFIG_ZPA2326_SPI) += zpa2326_spi.o - -obj-$(CONFIG_IIO_ST_PRESS_I2C) += st_pressure_i2c.o -obj-$(CONFIG_IIO_ST_PRESS_SPI) += st_pressure_spi.o From f9e05791642810a0cf6237d39fafd6fec5e0b4bb Mon Sep 17 00:00:00 2001 From: Shi Hao Date: Sun, 16 Nov 2025 15:46:20 +0530 Subject: [PATCH 150/150] staging: iio: adt7316: replace sprintf() with sysfs_emit() Convert several sprintf() calls to sysfs_emit() in the sysfs show functions, as it is the preferred helper and prevents potential buffer overruns. No functional changes intended. Signed-off-by: Shi Hao Reviewed-by: Andy Shevchenko Signed-off-by: Jonathan Cameron --- drivers/staging/iio/addac/adt7316.c | 102 ++++++++++++++-------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/drivers/staging/iio/addac/adt7316.c b/drivers/staging/iio/addac/adt7316.c index 16f30c4f1aa0..8a9a8262c2be 100644 --- a/drivers/staging/iio/addac/adt7316.c +++ b/drivers/staging/iio/addac/adt7316.c @@ -216,7 +216,7 @@ static ssize_t adt7316_show_enabled(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_EN)); + return sysfs_emit(buf, "%d\n", !!(chip->config1 & ADT7316_EN)); } static ssize_t _adt7316_store_enabled(struct adt7316_chip_info *chip, @@ -274,7 +274,7 @@ static ssize_t adt7316_show_select_ex_temp(struct device *dev, if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) return -EPERM; - return sprintf(buf, "%d\n", !!(chip->config1 & ADT7516_SEL_EX_TEMP)); + return sysfs_emit(buf, "%d\n", !!(chip->config1 & ADT7516_SEL_EX_TEMP)); } static ssize_t adt7316_store_select_ex_temp(struct device *dev, @@ -316,9 +316,9 @@ static ssize_t adt7316_show_mode(struct device *dev, struct adt7316_chip_info *chip = iio_priv(dev_info); if (chip->config2 & ADT7316_AD_SINGLE_CH_MODE) - return sprintf(buf, "single_channel\n"); + return sysfs_emit(buf, "single_channel\n"); - return sprintf(buf, "round_robin\n"); + return sysfs_emit(buf, "round_robin\n"); } static ssize_t adt7316_store_mode(struct device *dev, @@ -353,7 +353,7 @@ static ssize_t adt7316_show_all_modes(struct device *dev, struct device_attribute *attr, char *buf) { - return sprintf(buf, "single_channel\nround_robin\n"); + return sysfs_emit(buf, "single_channel\nround_robin\n"); } static IIO_DEVICE_ATTR(all_modes, 0444, adt7316_show_all_modes, NULL, 0); @@ -370,29 +370,29 @@ static ssize_t adt7316_show_ad_channel(struct device *dev, switch (chip->config2 & ADT7516_AD_SINGLE_CH_MASK) { case ADT7316_AD_SINGLE_CH_VDD: - return sprintf(buf, "0 - VDD\n"); + return sysfs_emit(buf, "0 - VDD\n"); case ADT7316_AD_SINGLE_CH_IN: - return sprintf(buf, "1 - Internal Temperature\n"); + return sysfs_emit(buf, "1 - Internal Temperature\n"); case ADT7316_AD_SINGLE_CH_EX: if (((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) && (chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) - return sprintf(buf, "2 - AIN1\n"); + return sysfs_emit(buf, "2 - AIN1\n"); - return sprintf(buf, "2 - External Temperature\n"); + return sysfs_emit(buf, "2 - External Temperature\n"); case ADT7516_AD_SINGLE_CH_AIN2: if ((chip->config1 & ADT7516_SEL_AIN1_2_EX_TEMP_MASK) == 0) - return sprintf(buf, "3 - AIN2\n"); + return sysfs_emit(buf, "3 - AIN2\n"); - return sprintf(buf, "N/A\n"); + return sysfs_emit(buf, "N/A\n"); case ADT7516_AD_SINGLE_CH_AIN3: if (chip->config1 & ADT7516_SEL_AIN3) - return sprintf(buf, "4 - AIN3\n"); + return sysfs_emit(buf, "4 - AIN3\n"); - return sprintf(buf, "N/A\n"); + return sysfs_emit(buf, "N/A\n"); case ADT7516_AD_SINGLE_CH_AIN4: - return sprintf(buf, "5 - AIN4\n"); + return sysfs_emit(buf, "5 - AIN4\n"); default: - return sprintf(buf, "N/A\n"); + return sysfs_emit(buf, "N/A\n"); } } @@ -453,10 +453,10 @@ static ssize_t adt7316_show_all_ad_channels(struct device *dev, return -EPERM; if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) - return sprintf(buf, "0 - VDD\n1 - Internal Temperature\n" + return sysfs_emit(buf, "0 - VDD\n1 - Internal Temperature\n" "2 - External Temperature or AIN1\n" "3 - AIN2\n4 - AIN3\n5 - AIN4\n"); - return sprintf(buf, "0 - VDD\n1 - Internal Temperature\n" + return sysfs_emit(buf, "0 - VDD\n1 - Internal Temperature\n" "2 - External Temperature\n"); } @@ -470,7 +470,7 @@ static ssize_t adt7316_show_disable_averaging(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->config2 & ADT7316_DISABLE_AVERAGING)); } @@ -509,7 +509,7 @@ static ssize_t adt7316_show_enable_smbus_timeout(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->config2 & ADT7316_EN_SMBUS_TIMEOUT)); } @@ -548,7 +548,7 @@ static ssize_t adt7316_show_powerdown(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_PD)); + return sysfs_emit(buf, "%d\n", !!(chip->config1 & ADT7316_PD)); } static ssize_t adt7316_store_powerdown(struct device *dev, @@ -586,7 +586,7 @@ static ssize_t adt7316_show_fast_ad_clock(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", !!(chip->config3 & ADT7316_ADCLK_22_5)); + return sysfs_emit(buf, "%d\n", !!(chip->config3 & ADT7316_ADCLK_22_5)); } static ssize_t adt7316_store_fast_ad_clock(struct device *dev, @@ -626,10 +626,10 @@ static ssize_t adt7316_show_da_high_resolution(struct device *dev, if (chip->config3 & ADT7316_DA_HIGH_RESOLUTION) { if (chip->id != ID_ADT7318 && chip->id != ID_ADT7519) - return sprintf(buf, "1 (10 bits)\n"); + return sysfs_emit(buf, "1 (10 bits)\n"); } - return sprintf(buf, "0 (8 bits)\n"); + return sysfs_emit(buf, "0 (8 bits)\n"); } static ssize_t adt7316_store_da_high_resolution(struct device *dev, @@ -673,7 +673,7 @@ static ssize_t adt7316_show_AIN_internal_Vref(struct device *dev, if ((chip->id & ID_FAMILY_MASK) != ID_ADT75XX) return -EPERM; - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->config3 & ADT7516_AIN_IN_VREF)); } @@ -716,7 +716,7 @@ static ssize_t adt7316_show_enable_prop_DACA(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->config3 & ADT7316_EN_IN_TEMP_PROP_DACA)); } @@ -755,7 +755,7 @@ static ssize_t adt7316_show_enable_prop_DACB(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->config3 & ADT7316_EN_EX_TEMP_PROP_DACB)); } @@ -794,7 +794,7 @@ static ssize_t adt7316_show_DAC_2Vref_ch_mask(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "0x%x\n", + return sysfs_emit(buf, "0x%x\n", chip->dac_config & ADT7316_DA_2VREF_CH_MASK); } @@ -838,20 +838,20 @@ static ssize_t adt7316_show_DAC_update_mode(struct device *dev, struct adt7316_chip_info *chip = iio_priv(dev_info); if (!(chip->config3 & ADT7316_DA_EN_VIA_DAC_LDAC)) - return sprintf(buf, "manual\n"); + return sysfs_emit(buf, "manual\n"); switch (chip->dac_config & ADT7316_DA_EN_MODE_MASK) { case ADT7316_DA_EN_MODE_SINGLE: - return sprintf(buf, + return sysfs_emit(buf, "0 - auto at any MSB DAC writing\n"); case ADT7316_DA_EN_MODE_AB_CD: - return sprintf(buf, + return sysfs_emit(buf, "1 - auto at MSB DAC AB and CD writing\n"); case ADT7316_DA_EN_MODE_ABCD: - return sprintf(buf, + return sysfs_emit(buf, "2 - auto at MSB DAC ABCD writing\n"); default: /* ADT7316_DA_EN_MODE_LDAC */ - return sprintf(buf, "3 - manual\n"); + return sysfs_emit(buf, "3 - manual\n"); } } @@ -898,11 +898,11 @@ static ssize_t adt7316_show_all_DAC_update_modes(struct device *dev, struct adt7316_chip_info *chip = iio_priv(dev_info); if (chip->config3 & ADT7316_DA_EN_VIA_DAC_LDAC) - return sprintf(buf, "0 - auto at any MSB DAC writing\n" + return sysfs_emit(buf, "0 - auto at any MSB DAC writing\n" "1 - auto at MSB DAC AB and CD writing\n" "2 - auto at MSB DAC ABCD writing\n" "3 - manual\n"); - return sprintf(buf, "manual\n"); + return sysfs_emit(buf, "manual\n"); } static IIO_DEVICE_ATTR(all_DAC_update_modes, 0444, @@ -955,7 +955,7 @@ static ssize_t adt7316_show_DA_AB_Vref_bypass(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->dac_config & ADT7316_VREF_BYPASS_DAC_AB)); } @@ -994,7 +994,7 @@ static ssize_t adt7316_show_DA_CD_Vref_bypass(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->dac_config & ADT7316_VREF_BYPASS_DAC_CD)); } @@ -1034,10 +1034,10 @@ static ssize_t adt7316_show_DAC_internal_Vref(struct device *dev, struct adt7316_chip_info *chip = iio_priv(dev_info); if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) - return sprintf(buf, "0x%x\n", + return sysfs_emit(buf, "0x%x\n", (chip->ldac_config & ADT7516_DAC_IN_VREF_MASK) >> ADT7516_DAC_IN_VREF_OFFSET); - return sprintf(buf, "%d\n", + return sysfs_emit(buf, "%d\n", !!(chip->ldac_config & ADT7316_DAC_IN_VREF)); } @@ -1128,7 +1128,7 @@ static ssize_t adt7316_show_ad(struct adt7316_chip_info *chip, data = msb << ADT7316_T_VALUE_FLOAT_OFFSET; data |= (lsb & ADT7316_LSB_VDD_MASK) >> ADT7316_LSB_VDD_OFFSET; - return sprintf(buf, "%d\n", data); + return sysfs_emit(buf, "%d\n", data); default: /* ex_temp and ain */ ret = chip->bus.read(chip->bus.client, ADT7316_LSB_EX_TEMP_AIN, &lsb); @@ -1146,7 +1146,7 @@ static ssize_t adt7316_show_ad(struct adt7316_chip_info *chip, (ADT7316_MSB_EX_TEMP - ADT7316_AD_MSB_DATA_BASE)))); if ((chip->id & ID_FAMILY_MASK) == ID_ADT75XX) - return sprintf(buf, "%d\n", data); + return sysfs_emit(buf, "%d\n", data); break; } @@ -1157,7 +1157,7 @@ static ssize_t adt7316_show_ad(struct adt7316_chip_info *chip, sign = '-'; } - return sprintf(buf, "%c%d.%.2d\n", sign, + return sysfs_emit(buf, "%c%d.%.2d\n", sign, (data >> ADT7316_T_VALUE_FLOAT_OFFSET), (data & ADT7316_T_VALUE_FLOAT_MASK) * 25); } @@ -1247,7 +1247,7 @@ static ssize_t adt7316_show_temp_offset(struct adt7316_chip_info *chip, if (val & 0x80) data -= 256; - return sprintf(buf, "%d\n", data); + return sysfs_emit(buf, "%d\n", data); } static ssize_t adt7316_store_temp_offset(struct adt7316_chip_info *chip, @@ -1415,7 +1415,7 @@ static ssize_t adt7316_show_DAC(struct adt7316_chip_info *chip, data = lsb >> ADT7316_DA_10_BIT_LSB_SHIFT; data |= msb << offset; - return sprintf(buf, "%d\n", data); + return sysfs_emit(buf, "%d\n", data); } static ssize_t adt7316_store_DAC(struct adt7316_chip_info *chip, @@ -1568,7 +1568,7 @@ static ssize_t adt7316_show_device_id(struct device *dev, if (ret) return -EIO; - return sprintf(buf, "%d\n", id); + return sysfs_emit(buf, "%d\n", id); } static IIO_DEVICE_ATTR(device_id, 0444, adt7316_show_device_id, NULL, 0); @@ -1586,7 +1586,7 @@ static ssize_t adt7316_show_manufactorer_id(struct device *dev, if (ret) return -EIO; - return sprintf(buf, "%d\n", id); + return sysfs_emit(buf, "%d\n", id); } static IIO_DEVICE_ATTR(manufactorer_id, 0444, @@ -1605,7 +1605,7 @@ static ssize_t adt7316_show_device_rev(struct device *dev, if (ret) return -EIO; - return sprintf(buf, "%d\n", rev); + return sysfs_emit(buf, "%d\n", rev); } static IIO_DEVICE_ATTR(device_rev, 0444, adt7316_show_device_rev, NULL, 0); @@ -1624,9 +1624,9 @@ static ssize_t adt7316_show_bus_type(struct device *dev, return -EIO; if (stat) - return sprintf(buf, "spi\n"); + return sysfs_emit(buf, "spi\n"); - return sprintf(buf, "i2c\n"); + return sysfs_emit(buf, "i2c\n"); } static IIO_DEVICE_ATTR(bus_type, 0444, adt7316_show_bus_type, NULL, 0); @@ -1836,7 +1836,7 @@ static ssize_t adt7316_show_int_mask(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "0x%x\n", chip->int_mask); + return sysfs_emit(buf, "0x%x\n", chip->int_mask); } /* @@ -1910,7 +1910,7 @@ static inline ssize_t adt7316_show_ad_bound(struct device *dev, data -= 256; } - return sprintf(buf, "%d\n", data); + return sysfs_emit(buf, "%d\n", data); } static inline ssize_t adt7316_set_ad_bound(struct device *dev, @@ -1961,7 +1961,7 @@ static ssize_t adt7316_show_int_enabled(struct device *dev, struct iio_dev *dev_info = dev_to_iio_dev(dev); struct adt7316_chip_info *chip = iio_priv(dev_info); - return sprintf(buf, "%d\n", !!(chip->config1 & ADT7316_INT_EN)); + return sysfs_emit(buf, "%d\n", !!(chip->config1 & ADT7316_INT_EN)); } static ssize_t adt7316_set_int_enabled(struct device *dev,