summaryrefslogtreecommitdiff
path: root/drivers/iio
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio')
-rw-r--r--drivers/iio/accel/Kconfig19
-rw-r--r--drivers/iio/accel/Makefile4
-rw-r--r--drivers/iio/accel/adxl380.c134
-rw-r--r--drivers/iio/accel/adxl380.h4
-rw-r--r--drivers/iio/accel/adxl380_i2c.c4
-rw-r--r--drivers/iio/accel/adxl380_spi.c4
-rw-r--r--drivers/iio/accel/bma220.h28
-rw-r--r--drivers/iio/accel/bma220_core.c585
-rw-r--r--drivers/iio/accel/bma220_i2c.c69
-rw-r--r--drivers/iio/accel/bma220_spi.c318
-rw-r--r--drivers/iio/accel/bma400.h155
-rw-r--r--drivers/iio/accel/bma400_core.c349
-rw-r--r--drivers/iio/adc/Kconfig31
-rw-r--r--drivers/iio/adc/Makefile3
-rw-r--r--drivers/iio/adc/ad4030.c4
-rw-r--r--drivers/iio/adc/ad4080.c126
-rw-r--r--drivers/iio/adc/ad7124.c295
-rw-r--r--drivers/iio/adc/ad7768-1.c2
-rw-r--r--drivers/iio/adc/ade9000.c2
-rw-r--r--drivers/iio/adc/aspeed_adc.c34
-rw-r--r--drivers/iio/adc/max14001.c391
-rw-r--r--drivers/iio/adc/mcp3564.c2
-rw-r--r--drivers/iio/adc/meson_saradc.c6
-rw-r--r--drivers/iio/adc/mt6360-adc.c2
-rw-r--r--drivers/iio/adc/pac1921.c8
-rw-r--r--drivers/iio/adc/pac1934.c2
-rw-r--r--drivers/iio/adc/qcom-spmi-rradc.c2
-rw-r--r--drivers/iio/adc/rohm-bd79112.c13
-rw-r--r--drivers/iio/adc/rohm-bd79124.c39
-rw-r--r--drivers/iio/adc/rzn1-adc.c490
-rw-r--r--drivers/iio/adc/rzt2h_adc.c304
-rw-r--r--drivers/iio/adc/ti-ads131e08.c2
-rw-r--r--drivers/iio/adc/ti_am335x_adc.c2
-rw-r--r--drivers/iio/buffer/industrialio-buffer-cb.c1
-rw-r--r--drivers/iio/common/scmi_sensors/scmi_iio.c5
-rw-r--r--drivers/iio/dac/Kconfig31
-rw-r--r--drivers/iio/dac/Makefile2
-rw-r--r--drivers/iio/dac/ad3530r.c3
-rw-r--r--drivers/iio/dac/ad5446-i2c.c102
-rw-r--r--drivers/iio/dac/ad5446-spi.c252
-rw-r--r--drivers/iio/dac/ad5446.c506
-rw-r--r--drivers/iio/dac/ad5446.h77
-rw-r--r--drivers/iio/dac/ltc2688.c32
-rw-r--r--drivers/iio/health/max30100.c38
-rw-r--r--drivers/iio/imu/Kconfig2
-rw-r--r--drivers/iio/imu/Makefile2
-rw-r--r--drivers/iio/imu/bmi270/bmi270_core.c383
-rw-r--r--drivers/iio/imu/bmi270/bmi270_spi.c2
-rw-r--r--drivers/iio/imu/inv_icm45600/Kconfig70
-rw-r--r--drivers/iio/imu/inv_icm45600/Makefile16
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600.h385
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_accel.c782
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c558
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h101
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_core.c988
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_gyro.c791
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_i2c.c98
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c79
-rw-r--r--drivers/iio/imu/inv_icm45600/inv_icm45600_spi.c108
-rw-r--r--drivers/iio/imu/smi330/Kconfig33
-rw-r--r--drivers/iio/imu/smi330/Makefile7
-rw-r--r--drivers/iio/imu/smi330/smi330.h25
-rw-r--r--drivers/iio/imu/smi330/smi330_core.c918
-rw-r--r--drivers/iio/imu/smi330/smi330_i2c.c133
-rw-r--r--drivers/iio/imu/smi330/smi330_spi.c85
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h4
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_buffer.c71
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c7
-rw-r--r--drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_shub.c2
-rw-r--r--drivers/iio/industrialio-backend.c8
-rw-r--r--drivers/iio/industrialio-buffer.c12
-rw-r--r--drivers/iio/industrialio-core.c18
-rw-r--r--drivers/iio/light/apds9306.c2
-rw-r--r--drivers/iio/light/apds9960.c2
-rw-r--r--drivers/iio/light/ltr390.c8
-rw-r--r--drivers/iio/light/veml3235.c2
-rw-r--r--drivers/iio/position/hid-sensor-custom-intel-hinge.c2
-rw-r--r--drivers/iio/pressure/Kconfig12
-rw-r--r--drivers/iio/pressure/Makefile8
-rw-r--r--drivers/iio/pressure/adp810.c225
-rw-r--r--drivers/iio/pressure/mpl3115.c549
-rw-r--r--drivers/iio/resolver/ad2s1210.c30
-rw-r--r--drivers/iio/temperature/mlx90614.c5
83 files changed, 9633 insertions, 1382 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index 8c3f7cf55d5f..76911278fb21 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -218,15 +218,30 @@ 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
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_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
+ select REGMAP_SPI
+ 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..fa440a859283 100644
--- a/drivers/iio/accel/Makefile
+++ b/drivers/iio/accel/Makefile
@@ -25,7 +25,9 @@ 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_I2C) += bma220_i2c.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/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 },
{ }
diff --git a/drivers/iio/accel/bma220.h b/drivers/iio/accel/bma220.h
new file mode 100644
index 000000000000..00dfe275256b
--- /dev/null
+++ b/drivers/iio/accel/bma220.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Forward declarations needed by the bma220 sources.
+ *
+ * Copyright 2025 Petre Rodan <petre.rodan@subdimension.ro>
+ */
+
+#ifndef _BMA220_H
+#define _BMA220_H
+
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+#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;
+extern const struct regmap_config bma220_spi_regmap_config;
+extern const struct dev_pm_ops bma220_pm_ops;
+
+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
new file mode 100644
index 000000000000..f32d875b994e
--- /dev/null
+++ b/drivers/iio/accel/bma220_core.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BMA220 Digital triaxial acceleration sensor driver
+ *
+ * Copyright (c) 2016,2020 Intel Corporation.
+ * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro>
+ */
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#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_SUSPEND 0x18
+#define BMA220_REG_SOFTRESET 0x19
+
+#define BMA220_CHIP_ID 0xDD
+#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"
+
+#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) | \
+ 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', \
+ .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 regmap *regmap;
+ struct mutex lock;
+ u8 lpf_3dB_freq_idx;
+ u8 range_idx;
+ struct iio_trigger *trig;
+ struct {
+ s8 chans[3];
+ /* Ensure timestamp is naturally aligned. */
+ aligned_s64 timestamp;
+ } scan __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),
+};
+
+/* 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
+};
+
+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");
+
+/*
+ * 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 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;
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ ret = regmap_bulk_read(data->regmap, BMA220_REG_ACCEL_X,
+ &data->scan.chans,
+ sizeof(data->scan.chans));
+ if (ret < 0)
+ return IRQ_NONE;
+
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ iio_get_time_ns(indio_dev));
+ 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 index;
+ unsigned int reg;
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_read(data->regmap, chan->address, &reg);
+ if (ret < 0)
+ return -EINVAL;
+ *val = sign_extend32(reg >> chan->scan_type.shift,
+ chan->scan_type.realbits - 1);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ index = data->range_idx;
+ *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;
+}
+
+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_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)
+{
+ int ret;
+ int index = -1;
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ index = bma220_find_match_2dt(bma220_scale_table,
+ ARRAY_SIZE(bma220_scale_table),
+ val, val2);
+ if (index < 0)
+ return -EINVAL;
+
+ ret = regmap_update_bits(data->regmap, BMA220_REG_RANGE,
+ BMA220_RANGE_MASK,
+ FIELD_PREP(BMA220_RANGE_MASK, index));
+ if (ret < 0)
+ 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;
+ }
+
+ 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;
+ 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;
+ }
+}
+
+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)
+{
+ int ret;
+ unsigned int i, val;
+
+ /*
+ * 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 = regmap_read(data->regmap, BMA220_REG_SOFTRESET, &val);
+ if (ret < 0)
+ return ret;
+
+ if (up && val == BMA220_RESET_MODE)
+ return 0;
+
+ if (!up && val == BMA220_NONRESET_MODE)
+ return 0;
+ }
+
+ return -EBUSY;
+}
+
+static int bma220_power(struct bma220_data *data, bool up)
+{
+ int ret;
+ unsigned int i, val;
+
+ /*
+ * 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 = regmap_read(data->regmap, BMA220_REG_SUSPEND, &val);
+ if (ret < 0)
+ return ret;
+
+ if (up && val == BMA220_SUSPEND_SLEEP)
+ return 0;
+
+ if (!up && val == BMA220_SUSPEND_WAKE)
+ return 0;
+ }
+
+ return -EBUSY;
+}
+
+static int bma220_init(struct device *dev, struct bma220_data *data)
+{
+ int ret;
+ unsigned int val;
+ 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 = regmap_read(data->regmap, BMA220_REG_ID, &val);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to read chip id register\n");
+
+ if (val != BMA220_CHIP_ID)
+ dev_info(dev, "Unknown chip found: 0x%02x\n", val);
+
+ ret = bma220_power(data, true);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to power-on chip\n");
+
+ 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 *data_ptr)
+{
+ 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));
+}
+
+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;
+ struct iio_dev *indio_dev;
+ struct bma220_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 = 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;
+ 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;
+
+ 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;
+
+ 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");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS_GPL(bma220_common_probe, "IIO_BOSCH_BMA220");
+
+static int bma220_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ return bma220_power(data, false);
+}
+
+static int bma220_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct bma220_data *data = iio_priv(indio_dev);
+
+ return bma220_power(data, true);
+}
+EXPORT_NS_SIMPLE_DEV_PM_OPS(bma220_pm_ops, bma220_suspend, bma220_resume,
+ IIO_BOSCH_BMA220);
+
+MODULE_AUTHOR("Tiberiu Breana <tiberiu.a.breana@intel.com>");
+MODULE_DESCRIPTION("BMA220 acceleration sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/accel/bma220_i2c.c b/drivers/iio/accel/bma220_i2c.c
new file mode 100644
index 000000000000..8b6f8e305c8c
--- /dev/null
+++ b/drivers/iio/accel/bma220_i2c.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Bosch triaxial acceleration sensor
+ *
+ * Copyright (c) 2025 Petre Rodan <petre.rodan@subdimension.ro>
+ *
+ * Datasheet: https://media.digikey.com/pdf/Data%20Sheets/Bosch/BMA220.pdf
+ * I2C address is either 0x0b or 0x0a depending on CSB (pin 10)
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#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");
+
+ 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[] = {
+ { .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 <petre.rodan@subdimension.ro>");
+MODULE_DESCRIPTION("Bosch triaxial acceleration sensor i2c driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BOSCH_BMA220");
diff --git a/drivers/iio/accel/bma220_spi.c b/drivers/iio/accel/bma220_spi.c
index 01592eebf05b..383ee8a135ee 100644
--- a/drivers/iio/accel/bma220_spi.c
+++ b/drivers/iio/accel/bma220_spi.c
@@ -5,326 +5,56 @@
* Copyright (c) 2016,2020 Intel Corporation.
*/
-#include <linux/bits.h>
-#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/regmap.h>
#include <linux/types.h>
#include <linux/spi/spi.h>
-#include <linux/iio/buffer.h>
-#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
-#include <linux/iio/trigger_consumer.h>
-#include <linux/iio/triggered_buffer.h>
+#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);
-}
-
-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;
+ struct regmap *regmap;
- 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);
+ 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 IRQ_HANDLED;
+ return bma220_common_probe(&spi->dev, regmap, spi->irq);
}
-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_init(struct spi_device *spi)
-{
- int ret;
-
- ret = bma220_read_reg(spi, BMA220_REG_ID);
- if (ret != BMA220_CHIP_ID)
- return -ENODEV;
-
- /* 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 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);
-}
-
-static int bma220_probe(struct spi_device *spi)
-{
- int ret;
- struct iio_dev *indio_dev;
- struct bma220_data *data;
-
- indio_dev = devm_iio_device_alloc(&spi->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(&spi->dev, bma220_deinit, spi);
- if (ret)
- return ret;
-
- ret = devm_iio_triggered_buffer_setup(&spi->dev, indio_dev,
- iio_pollfunc_store_time,
- bma220_trigger_handler, NULL);
- if (ret < 0) {
- dev_err(&spi->dev, "iio triggered buffer setup failed\n");
- return ret;
- }
-
- return devm_iio_device_register(&spi->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},
+ { "bma220", 0 },
{ }
};
static const struct acpi_device_id bma220_acpi_id[] = {
- {"BMA0220", 0},
+ { "BMA0220", 0 },
{ }
};
MODULE_DEVICE_TABLE(spi, bma220_spi_id);
-static struct spi_driver bma220_driver = {
+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_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 <tiberiu.a.breana@intel.com>");
-MODULE_DESCRIPTION("BMA220 acceleration sensor driver");
-MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("BMA220 triaxial acceleration sensor spi driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_BOSCH_BMA220");
diff --git a/drivers/iio/accel/bma400.h b/drivers/iio/accel/bma400.h
index 932358b45f17..b5f3cac51610 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,100 @@
#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_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_ACC_CONFIG1_ACC_RANGE_MASK GENMASK(7, 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)
+
+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)
+#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_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)
+#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_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)
+#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,
+};
/* 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 +181,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..05f72707f830 100644
--- a/drivers/iio/accel/bma400_core.c
+++ b/drivers/iio/accel/bma400_core.c
@@ -121,21 +121,56 @@ 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) {
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 +194,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 +310,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, &reg_val);
+ ret = regmap_read(data->regmap, BMA400_TAP_CONFIG1_REG, &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 +337,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 +484,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 +510,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 +529,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 +542,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 +574,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 +596,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 +627,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 = FIELD_GET(BMA400_ACC_CONFIG0_LP_OSR_MASK, val);
data->oversampling_ratio = osr;
return 0;
@@ -603,7 +638,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 = FIELD_GET(BMA400_ACC_CONFIG1_NP_OSR_MASK, val);
data->oversampling_ratio = osr;
return 0;
@@ -637,8 +672,8 @@ 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) |
- (val << BMA400_LP_OSR_SHIFT));
+ (acc_config & ~BMA400_ACC_CONFIG0_LP_OSR_MASK) |
+ FIELD_PREP(BMA400_ACC_CONFIG0_LP_OSR_MASK, val));
if (ret) {
dev_err(data->dev, "Failed to write out OSR\n");
return ret;
@@ -653,8 +688,8 @@ 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) |
- (val << BMA400_NP_OSR_SHIFT));
+ (acc_config & ~BMA400_ACC_CONFIG1_NP_OSR_MASK) |
+ FIELD_PREP(BMA400_ACC_CONFIG1_NP_OSR_MASK, val));
if (ret) {
dev_err(data->dev, "Failed to write out OSR\n");
return ret;
@@ -679,7 +714,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 +730,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 = FIELD_GET(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, val);
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 +754,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) |
+ FIELD_PREP(BMA400_ACC_CONFIG1_ACC_RANGE_MASK, raw));
if (ret)
return ret;
@@ -786,8 +821,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 +861,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 +1098,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 +1149,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 +1181,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;
@@ -1155,63 +1190,68 @@ 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, 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;
- msk = BMA400_INT_GEN1_MSK;
- value = 2;
- set_mask_bits(&field_value, BMA400_INT_GEN1_MSK,
- FIELD_PREP(BMA400_INT_GEN1_MSK, state));
- break;
- case IIO_EV_DIR_FALLING:
- reg = BMA400_GEN2INT_CONFIG0;
- msk = BMA400_INT_GEN2_MSK;
- value = 0;
- set_mask_bits(&field_value, BMA400_INT_GEN2_MSK,
- FIELD_PREP(BMA400_INT_GEN2_MSK, state));
- break;
- default:
+ bma400_genintr = get_bma400_genintr_info(dir);
+ if (!bma400_genintr)
return -EINVAL;
- }
- /* Enabling all axis for interrupt evaluation */
- ret = regmap_write(data->regmap, reg, 0xF8);
+ genintr = bma400_genintr->genintr;
+ detect_criterion = bma400_genintr->detect_mode;
+ intrmask = bma400_genintr->intrmask;
+
+ /*
+ * 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 |
+ 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;
/* 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);
+ /*
+ * 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;
/* 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;
}
@@ -1240,21 +1280,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;
@@ -1303,7 +1343,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:
@@ -1336,18 +1376,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;
- case IIO_EV_DIR_RISING:
- return BMA400_GEN1INT_CONFIG0;
- 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,
@@ -1356,22 +1384,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;
@@ -1379,7 +1410,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) {
@@ -1390,10 +1421,12 @@ 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_GEN_HYST_MSK, *val);
+ *val = FIELD_GET(BMA400_GENINT_CONFIG0_HYST_MASK, *val);
return IIO_VAL_INT;
default:
return -EINVAL;
@@ -1401,30 +1434,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,
&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,
&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,
&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;
@@ -1444,16 +1477,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:
@@ -1461,7 +1497,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)
@@ -1470,7 +1506,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);
@@ -1479,9 +1515,10 @@ 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,
- BMA400_GEN_HYST_MSK,
- FIELD_PREP(BMA400_GEN_HYST_MSK,
+ 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));
default:
return -EINVAL;
@@ -1493,9 +1530,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 +1540,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 +1550,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 +1570,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 +1615,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 +1665,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 +1679,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 +1687,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 +1701,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 +1723,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;
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 58a14e6833f6..58da8255525e 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
@@ -1403,6 +1413,27 @@ 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
+ 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..7cc8f9a12f76 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
@@ -123,6 +124,8 @@ 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
obj-$(CONFIG_SOPHGO_CV1800B_ADC) += sophgo-cv1800b-adc.o
diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index d8bee6a4215a..68446db9bef1 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,
diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c
index 6e61787ed321..7cf3b6ed7940 100644
--- a/drivers/iio/adc/ad4080.c
+++ b/drivers/iio/adc/ad4080.c
@@ -125,7 +125,12 @@
/* Miscellaneous Definitions */
#define AD4080_SPI_READ BIT(7)
-#define AD4080_CHIP_ID GENMASK(2, 0)
+#define AD4080_CHIP_ID 0x0050
+#define AD4081_CHIP_ID 0x0051
+#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
@@ -167,6 +172,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 +420,35 @@ 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 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 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",
@@ -439,13 +457,65 @@ 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 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 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,
+ .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 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 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);
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,11 +528,13 @@ 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;
- if (id != AD4080_CHIP_ID)
+ id = le16_to_cpu(id_le);
+ 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,
@@ -488,7 +560,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;
@@ -593,12 +665,22 @@ 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 },
+ { "ad4086", (kernel_ulong_t)&ad4086_chip_info },
+ { "ad4087", (kernel_ulong_t)&ad4087_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,ad4081", &ad4081_chip_info },
+ { .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);
diff --git a/drivers/iio/adc/ad7124.c b/drivers/iio/adc/ad7124.c
index 61623cc6cb25..5c1a8f886bcc 100644
--- a/drivers/iio/adc/ad7124.c
+++ b/drivers/iio/adc/ad7124.c
@@ -10,6 +10,7 @@
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
+#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
@@ -110,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
@@ -175,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;
@@ -199,7 +201,6 @@ struct ad7124_channel_config {
};
struct ad7124_channel {
- unsigned int nr;
struct ad7124_channel_config cfg;
unsigned int ain;
unsigned int slot;
@@ -215,14 +216,14 @@ 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;
};
static const struct ad7124_chip_info ad7124_4_chip_info = {
@@ -366,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;
}
@@ -403,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)
{
@@ -486,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)
{
@@ -494,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;
@@ -510,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;
@@ -560,108 +518,107 @@ 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,
+ 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) |
- AD7124_FILTER_SINGLE_CYCLE |
+ FIELD_PREP(AD7124_FILTER_SINGLE_CYCLE,
+ st->enable_single_cycle) |
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;
-
- 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;
+ unsigned int slot;
- /* push the new config in configs queue */
- free_cfg_slot = lru_cfg->cfg_slot;
- kfifo_put(&st->live_cfgs_fifo, cfg);
- }
+ /*
+ * 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)
+ return;
- /* mark slot as used */
- assign_bit(free_cfg_slot, &st->cfg_slots_status, 1);
+ slot = st->channels[channel].cfg.cfg_slot;
- return ad7124_write_config(st, cfg, free_cfg_slot);
-}
+ if (slot == AD7124_CFG_SLOT_UNASSIGNED ||
+ st->cfg_slot_use_count[slot] == 0)
+ return;
-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);
+ 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 */
- return ad7124_enable_channel(st, &st->channels[address]);
-}
-
-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);
+ 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)
@@ -670,7 +627,7 @@ static int ad7124_set_channel(struct ad_sigma_delta *sd, unsigned int channel)
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;
@@ -700,6 +657,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);
}
@@ -709,7 +668,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;
@@ -921,9 +880,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:
@@ -965,7 +921,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);
@@ -1066,7 +1022,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;
@@ -1081,7 +1041,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;
@@ -1172,7 +1132,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);
@@ -1305,7 +1264,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 +1290,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 = {
@@ -1358,6 +1315,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,
};
}
@@ -1489,8 +1447,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);
- INIT_KFIFO(st->live_cfgs_fifo);
+ ret = devm_mutex_init(dev, &st->cfgs_lock);
+ if (ret)
+ return ret;
+
for (i = 0; i < st->num_channels; i++) {
struct ad7124_channel_config *cfg = &st->channels[i].cfg;
@@ -1498,6 +1458,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;
@@ -1559,9 +1521,9 @@ 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,
+ ret = ad_sd_read_reg(&st->sd, AD7124_GAIN(0), 3,
&st->channels[i].cfg.calibration_gain);
if (ret < 0)
return ret;
@@ -1571,7 +1533,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;
@@ -1613,6 +1579,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;
@@ -1633,6 +1611,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;
@@ -1690,6 +1671,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;
}
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,
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,
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);
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 <kimseer.paller@analog.com>
+ * Copyright (c) 2025 Marilene Andrade Garcia <marilene.agarcia@gmail.com>
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX14001-MAX14002.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitrev.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+/* 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 <kimseer.paller@analog.com>");
+MODULE_AUTHOR("Marilene Andrade Garcia <marilene.agarcia@gmail.com>");
+MODULE_DESCRIPTION("Analog Devices MAX14001/MAX14002 ADCs driver");
+MODULE_LICENSE("GPL");
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)
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;
}
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 = {
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;
}
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));
}
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);
}
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 = {
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 = {
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 <herve.codina@bootlin.com>
+ *
+ * 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 <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/dev_printk.h>
+#include <linux/err.h>
+#include <linux/iio/iio.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#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 <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("Renesas RZ/N1 ADC Driver");
+MODULE_LICENSE("GPL");
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 <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/iio/adc-helpers.h>
+#include <linux/iio/iio.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+
+#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, &reg, 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 <cosmin-gabriel.tanislav.xa@renesas.com>");
+MODULE_DESCRIPTION("Renesas RZ/T2H / RZ/N2H ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DRIVER");
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);
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
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/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;
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, 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-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 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 AD5301, AD5311, AD5321,
+ AD5602, AD5612, AD5622 DACs.
To compile this driver as a module, choose M here: the
- module will be called ad5446.
+ 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/ad3530r.c b/drivers/iio/dac/ad3530r.c
index 6134613777b8..b97b46090d80 100644
--- a/drivers/iio/dac/ad3530r.c
+++ b/drivers/iio/dac/ad3530r.c
@@ -53,9 +53,6 @@
#define AD3530R_MAX_CHANNELS 8
#define AD3531R_MAX_CHANNELS 4
-/* Non-constant mask variant of FIELD_PREP() */
-#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
-
enum ad3530r_mode {
AD3530R_NORMAL_OP,
AD3530R_POWERDOWN_1K,
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 <linux/err.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/i2c.h>
+
+#include <asm/byteorder.h>
+
+#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á <nuno.sa@analog.com>");
+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..e29d77f21482
--- /dev/null
+++ b/drivers/iio/dac/ad5446-spi.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AD5446 SPI DAC driver
+ *
+ * Copyright 2025 Analog Devices Inc.
+ */
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/spi/spi.h>
+#include <linux/unaligned.h>
+
+#include <asm/byteorder.h>
+
+#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},
+ {"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},
+ {"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,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 },
+ { .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á <nuno.sa@analog.com>");
+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 ad304b0fec08..46a2eadb1d9b 100644
--- a/drivers/iio/dac/ad5446.c
+++ b/drivers/iio/dac/ad5446.c
@@ -1,73 +1,35 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * AD5446 SPI DAC driver
+ * AD5446 CORE DAC driver
*
* Copyright 2010 Analog Devices Inc.
*/
-#include <linux/interrupt.h>
-#include <linux/workqueue.h>
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
-#include <linux/kernel.h>
-#include <linux/slab.h>
-#include <linux/sysfs.h>
-#include <linux/list.h>
-#include <linux/spi/spi.h>
-#include <linux/i2c.h>
-#include <linux/regulator/consumer.h>
#include <linux/err.h>
-#include <linux/module.h>
-#include <linux/mod_devicetable.h>
-
+#include <linux/export.h>
#include <linux/iio/iio.h>
-#include <linux/iio/sysfs.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
-#include <linux/unaligned.h>
+#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
- */
-
-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;
-};
-
-/**
- * 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"
};
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);
@@ -77,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);
@@ -92,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);
@@ -102,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;
@@ -116,7 +78,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) {
@@ -127,12 +89,13 @@ 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;
}
-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,
@@ -143,28 +106,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,
@@ -186,32 +128,35 @@ static int ad5446_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
-static int ad5446_write_raw(struct iio_dev *indio_dev,
- struct iio_chan_spec const *chan,
- int val,
- int val2,
- long mask)
+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);
- int ret = 0;
+ 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)
+{
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 = {
@@ -219,8 +164,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;
@@ -241,7 +186,9 @@ static 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;
@@ -249,354 +196,19 @@ 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;
}
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);
- __be16 data = cpu_to_be16(val);
-
- return spi_write(spi, &data, sizeof(data));
-}
-
-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]);
-
- return spi_write(spi, data, sizeof(data));
-}
-
-/*
- * 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.
- */
-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_AD5600,
- 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 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_AD5600] = {
- .channel = AD5446_CHANNEL(16, 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 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_AD5600},
- {"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},
- { }
-};
-MODULE_DEVICE_TABLE(spi, ad5446_spi_ids);
-
-static const struct of_device_id ad5446_of_ids[] = {
- { .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);
-
- return ad5446_probe(&spi->dev, id->name,
- &ad5446_spi_chip_info[id->driver_data]);
-}
-
-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);
- __be16 data = cpu_to_be16(val);
- int ret;
-
- ret = i2c_master_send(client, (char *)&data, sizeof(data));
- if (ret < 0)
- return ret;
- if (ret != sizeof(data))
- 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.
- */
-enum ad5446_supported_i2c_device_ids {
- ID_AD5602,
- ID_AD5612,
- ID_AD5622,
-};
-
-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 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]);
-}
-
-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},
- { }
-};
-MODULE_DEVICE_TABLE(i2c, ad5446_i2c_ids);
-
-static struct i2c_driver ad5446_i2c_driver = {
- .driver = {
- .name = "ad5446",
- },
- .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 <michael.hennerich@analog.com>");
-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..6ba31d98f415
--- /dev/null
+++ b/drivers/iio/dac/ad5446.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_AD5446_H
+#define _LINUX_AD5446_H
+
+#include <linux/bits.h>
+#include <linux/compiler.h>
+#include <linux/iio/iio.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+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
diff --git a/drivers/iio/dac/ltc2688.c b/drivers/iio/dac/ltc2688.c
index 7a2ee26a7d68..02f408229681 100644
--- a/drivers/iio/dac/ltc2688.c
+++ b/drivers/iio/dac/ltc2688.c
@@ -6,6 +6,7 @@
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/gpio/consumer.h>
@@ -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,
@@ -953,7 +949,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, &ltc2688_regmap_bus, st,
&ltc2688_regmap_config);
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 <matt.ranostay@konsulko.com>
*
- * TODO: enable pulse length controls via device tree properties
*/
#include <linux/module.h>
@@ -18,6 +17,7 @@
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/regmap.h>
+#include <linux/bitfield.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
#include <linux/iio/kfifo_buf.h>
@@ -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;
diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
index 15612f0f189b..7e0181c27bb6 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
@@ -124,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 e901aea498d3..13fb7846e9c9 100644
--- a/drivers/iio/imu/Makefile
+++ b/drivers/iio/imu/Makefile
@@ -25,11 +25,13 @@ 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
obj-$(CONFIG_SMI240) += smi240.o
+obj-y += smi330/
obj-y += st_lsm6dsx/
obj-y += st_lsm9ds0/
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, &regval);
+ 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, &regval);
+ if (ret)
+ return ret;
+
switch (chan->type) {
case IIO_STEPS:
- reg = bmi270_int_map_reg(data->irq_pin);
- if (reg)
- return reg;
-
- ret = regmap_read(data->regmap, reg, &regval);
- if (ret)
- return ret;
- return FIELD_GET(BMI270_INT_MAP_FEAT_STEP_CNT_WTRMRK_MSK,
- regval) ? 1 : 0;
+ 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;
+
+ 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, &regval);
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, &regval);
+ 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, &regval);
+ 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;
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);
}
diff --git a/drivers/iio/imu/inv_icm45600/Kconfig b/drivers/iio/imu/inv_icm45600/Kconfig
new file mode 100644
index 000000000000..dc133402f6d7
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/Kconfig
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+config INV_ICM45600
+ tristate
+ 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.
+
+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.
+
+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
new file mode 100644
index 000000000000..c98b8365b467
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/Makefile
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+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
+
+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
+
+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.h b/drivers/iio/imu/inv_icm45600/inv_icm45600.h
new file mode 100644
index 000000000000..c5b5446f6c3b
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600.h
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2025 Invensense, Inc. */
+
+#ifndef INV_ICM45600_H_
+#define INV_ICM45600_H_
+
+#include <linux/bits.h>
+#include <linux/limits.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+
+#include "inv_icm45600_buffer.h"
+
+#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;
+};
+
+#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;
+};
+
+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;
+ const int *accel_scales;
+ const int accel_scales_len;
+ 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_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];
+
+/**
+ * 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.
+ * @fifo: FIFO management structure.
+ * @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;
+ struct inv_icm45600_fifo fifo;
+ 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_MASK GENMASK(5, 0)
+#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);
+
+#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,
+ 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);
+
+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 <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+
+#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, &timestamp, &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
new file mode 100644
index 000000000000..2b9ea317385c
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.c
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (C) 2025 Invensense, Inc. */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/minmax.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+
+#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;
+ }
+
+ sleep = 0;
+ 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,
+ unsigned int max)
+{
+ 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;
+ 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;
+ 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, &timestamp, &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_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;
+
+ 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;
+ }
+
+ /* 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;
+}
+
+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;
+
+ 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;
+ }
+
+ 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;
+}
+
+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..e047871cdbe2
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_buffer.h
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2025 Invensense, Inc. */
+
+#ifndef INV_ICM45600_BUFFER_H_
+#define INV_ICM45600_BUFFER_H_
+
+#include <linux/bits.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+
+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,
+ 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);
+
+#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..ab1cb7b9dba4
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_core.c
@@ -0,0 +1,988 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (C) 2025 Invensense, Inc. */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+
+#include "inv_icm45600_buffer.h"
+#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,
+};
+
+/* 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,
+ },
+ .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 = {
+ .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,
+ },
+ .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,
+};
+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,
+ .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,
+};
+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,
+ .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,
+};
+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,
+ .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,
+};
+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,
+ .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,
+};
+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,
+ .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,
+};
+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,
+ .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,
+};
+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,
+ .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,
+};
+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)
+{
+ 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 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, 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. */
+ 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. */
+ 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;
+ 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))
+ 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);
+
+ 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;
+
+ 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 = inv_icm45600_buffer_init(st);
+ if (ret)
+ return ret;
+
+ st->indio_gyro = inv_icm45600_gyro_init(st);
+ 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;
+
+ 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);
+ 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;
+ }
+
+ 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);
+ 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;
+}
+
+/* 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);
+}
+
+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,
+ 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");
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 <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/common/inv_sensors_timestamp.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/kfifo_buf.h>
+
+#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, &timestamp, &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;
+}
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 <linux/device.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/regmap.h>
+
+#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");
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..9247eae9b3e2
--- /dev/null
+++ b/drivers/iio/imu/inv_icm45600/inv_icm45600_i3c.c
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright (C) 2025 InvenSense, Inc. */
+
+#include <linux/err.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
+
+#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)
+ 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);
+}
+
+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 <remi.buisson@tdk.com>");
+MODULE_DESCRIPTION("InvenSense ICM-456xx i3c driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_ICM45600");
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 <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/regmap.h>
+
+#include <linux/spi/spi.h>
+
+#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");
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 <linux/iio/iio.h>
+
+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 <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/string.h>
+#include <linux/units.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#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, &reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, reg, &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, &reg);
+ 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 <stefan.gutmann@de.bosch.com>");
+MODULE_AUTHOR("Roman Huber <roman.huber@de.bosch.com>");
+MODULE_AUTHOR("Filip Andrei <Andrei.Filip@ro.bosch.com>");
+MODULE_AUTHOR("Drimbarean Avram Andrei <Avram-Andrei.Drimbarean@ro.bosch.com>");
+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 <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#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:
+ * <Slave address[6:0], RnW> <x, Register address[6:0]>
+ * <Slave address[6:0], RnW> <Dummy[7:0]> <Dummy[7:0]> <Data_0[7:0]> <Data_1[15:8]>...
+ * <Data_N[7:0]> <Data_N[15:8]>
+ * 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:
+ * <Slave address[6:0], RnW> <x, Register address[6:0]> <Data_0[7:0]> <Data_1[15:8]>...
+ * <Data_N[7:0]> <Data_N[15:8]>
+ * 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 <stefan.gutmann@de.bosch.com>");
+MODULE_AUTHOR("Roman Huber <roman.huber@de.bosch.com>");
+MODULE_AUTHOR("Filip Andrei <Andrei.Filip@ro.bosch.com>");
+MODULE_AUTHOR("Drimbarean Avram Andrei <Avram-Andrei.Drimbarean@ro.bosch.com>");
+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 <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#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 <stefan.gutmann@de.bosch.com>");
+MODULE_AUTHOR("Roman Huber <roman.huber@de.bosch.com>");
+MODULE_AUTHOR("Filip Andrei <Andrei.Filip@ro.bosch.com>");
+MODULE_AUTHOR("Drimbarean Avram Andrei <Avram-Andrei.Drimbarean@ro.bosch.com>");
+MODULE_DESCRIPTION("Bosch SMI330 SPI driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS("IIO_SMI330");
diff --git a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
index 381b016fa524..6405a5367d76 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx.h
@@ -383,7 +383,8 @@ 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].
+ * 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.
@@ -398,6 +399,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 <linux/iio/kfifo_buf.h>
#include <linux/iio/iio.h>
#include <linux/iio/buffer.h>
+#include <linux/iio/sysfs.h>
#include <linux/regmap.h>
#include <linux/bitfield.h>
@@ -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 a2daf0c14d96..49ac17806e72 100644
--- a/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
+++ b/drivers/iio/imu/st_lsm6dsx/st_lsm6dsx_core.c
@@ -1851,10 +1851,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:
@@ -2381,6 +2383,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;
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)
{
diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
index 96ea0f039dfb..c6259213e150 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);
@@ -2383,6 +2381,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)
{
@@ -2412,6 +2413,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,
@@ -2420,6 +2424,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.
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index 88c3d585a1bd..f69deefcfb6f 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);
@@ -1694,12 +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);
- 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);
@@ -1720,7 +1717,14 @@ 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_with_key(&iio_dev_opaque->mlock, &iio_dev_opaque->mlock_key);
+ 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;
}
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 =
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[] = {
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)
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)
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 = {
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..a21443e992b9 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
@@ -15,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
@@ -34,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
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 <akhilesh@ee.iitb.ac.in>
+ *
+ * Driver for adp810 pressure and temperature sensor
+ * Datasheet:
+ * https://aosong.com/userfiles/files/media/Datasheet%20ADP810-Digital.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/cleanup.h>
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+/*
+ * 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 <akhilesh@ee.iitb.ac.in>");
+MODULE_DESCRIPTION("Driver for Aosong ADP810 sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/pressure/mpl3115.c b/drivers/iio/pressure/mpl3115.c
index 579da60ef441..aeac1586f12e 100644
--- a/drivers/iio/pressure/mpl3115.c
+++ b/drivers/iio/pressure/mpl3115.c
@@ -7,38 +7,97 @@
* (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 <linux/module.h>
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
#include <linux/i2c.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/buffer.h>
+#include <linux/iio/events.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
-#include <linux/iio/trigger_consumer.h>
-#include <linux/iio/buffer.h>
#include <linux/iio/triggered_buffer.h>
-#include <linux/delay.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/trigger.h>
#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_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
+#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_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_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)
+
+#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_CTRL2_ST GENMASK(3, 0)
+
+#define MPL3115_CTRL3_IPOL1 BIT(5)
+#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)
+
+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;
struct mutex lock;
u8 ctrl_reg1;
+ u8 ctrl_reg4;
+};
+
+enum mpl3115_irq_pin {
+ MPL3115_IRQ_INT1,
+ MPL3115_IRQ_INT2,
};
static int mpl3115_request(struct mpl3115_data *data)
@@ -47,7 +106,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 +115,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);
}
@@ -76,7 +135,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);
@@ -85,11 +144,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 */
@@ -144,51 +209,110 @@ 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 irqreturn_t mpl3115_trigger_handler(int irq, void *p)
+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 iio_poll_func *pf = p;
- struct iio_dev *indio_dev = pf->indio_dev;
struct mpl3115_data *data = iio_priv(indio_dev);
- /*
- * 32-bit channel + 16-bit channel + padding + ts
- * Note that it is possible for only one of the first 2
- * channels to be enabled. If that happens, the first element
- * of the buffer may be either 16 or 32-bits. As such we cannot
- * use a simple structure definition to express this data layout.
- */
- u8 buffer[16] __aligned(8) = { };
int ret, pos = 0;
- mutex_lock(&data->lock);
- ret = mpl3115_request(data);
- if (ret < 0) {
- mutex_unlock(&data->lock);
- goto done;
+ 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,
MPL3115_OUT_PRESS, 3, &buffer[pos]);
- if (ret < 0) {
- mutex_unlock(&data->lock);
- goto done;
- }
+ 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) {
- mutex_unlock(&data->lock);
- goto done;
- }
+ if (ret < 0)
+ return ret;
}
+
+ return 0;
+}
+
+static irqreturn_t mpl3115_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct mpl3115_data *data = iio_priv(indio_dev);
+ /*
+ * 32-bit channel + 16-bit channel + padding + ts
+ * Note that it is possible for only one of the first 2
+ * channels to be enabled. If that happens, the first element
+ * of the buffer may be either 16 or 32-bits. As such we cannot
+ * use a simple structure definition to express this data layout.
+ */
+ u8 buffer[16] __aligned(8) = { };
+ int ret;
+
+ mutex_lock(&data->lock);
+ 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));
@@ -198,11 +322,23 @@ 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,
.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',
@@ -210,12 +346,17 @@ 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,
.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',
@@ -223,15 +364,333 @@ 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),
};
+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_TTH | MPL3115_INT_SRC_PTH |
+ MPL3115_INT_SRC_DRDY)))
+ return IRQ_NONE;
+
+ 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;
+}
+
+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;
+ data->ctrl_reg4 = ctrl_reg4;
+
+ 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, 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);
+}
+
+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,
+ 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);
@@ -262,15 +721,19 @@ 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)
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)
@@ -289,7 +752,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)
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;
diff --git a/drivers/iio/temperature/mlx90614.c b/drivers/iio/temperature/mlx90614.c
index 8a44a00bfd5e..1ad21b73e1b4 100644
--- a/drivers/iio/temperature/mlx90614.c
+++ b/drivers/iio/temperature/mlx90614.c
@@ -22,6 +22,7 @@
* the "wakeup" GPIO is not given, power management will be disabled.
*/
+#include <linux/bitfield.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
@@ -68,10 +69,6 @@
#define MLX90614_CONST_SCALE 20 /* Scale in milliKelvin (0.02 * 1000) */
#define MLX90614_CONST_FIR 0x7 /* Fixed value for FIR part of low pass filter */
-/* Non-constant mask variant of FIELD_GET() and FIELD_PREP() */
-#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
-#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
-
struct mlx_chip_info {
/* EEPROM offsets with 16-bit data, MSB first */
/* emissivity correction coefficient */