summaryrefslogtreecommitdiffstats
path: root/drivers/iio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 18:50:36 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-18 18:50:36 +0000
commit50ba0232fd5312410f1b65247e774244f89a628e (patch)
treefd8f2fc78e9e548af0ff9590276602ee6125be00 /drivers/iio
parentReleasing progress-linux version 6.7.12-1~progress7.99u1. (diff)
downloadlinux-50ba0232fd5312410f1b65247e774244f89a628e.tar.xz
linux-50ba0232fd5312410f1b65247e774244f89a628e.zip
Merging upstream version 6.8.9.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/iio')
-rw-r--r--drivers/iio/accel/Kconfig7
-rw-r--r--drivers/iio/accel/bmi088-accel-core.c2
-rw-r--r--drivers/iio/accel/bmi088-accel-spi.c2
-rw-r--r--drivers/iio/adc/Kconfig29
-rw-r--r--drivers/iio/adc/Makefile5
-rw-r--r--drivers/iio/adc/ad7091r-base.c113
-rw-r--r--drivers/iio/adc/ad7091r-base.h75
-rw-r--r--drivers/iio/adc/ad7091r5.c92
-rw-r--r--drivers/iio/adc/ad7091r8.c272
-rw-r--r--drivers/iio/adc/ad9467.c95
-rw-r--r--drivers/iio/adc/adi-axi-adc.c85
-rw-r--r--drivers/iio/adc/max34408.c276
-rw-r--r--drivers/iio/adc/mcp3911.c64
-rw-r--r--drivers/iio/amplifiers/hmc425a.c23
-rw-r--r--drivers/iio/buffer/industrialio-buffer-dma.c87
-rw-r--r--drivers/iio/chemical/Kconfig11
-rw-r--r--drivers/iio/chemical/Makefile1
-rw-r--r--drivers/iio/chemical/ags02ma.c165
-rw-r--r--drivers/iio/chemical/pms7003.c6
-rw-r--r--drivers/iio/chemical/scd30_serial.c6
-rw-r--r--drivers/iio/chemical/sps30_serial.c18
-rw-r--r--drivers/iio/dac/Kconfig10
-rw-r--r--drivers/iio/dac/Makefile1
-rw-r--r--drivers/iio/dac/ad5791.c9
-rw-r--r--drivers/iio/dac/mcp4821.c236
-rw-r--r--drivers/iio/frequency/adf4377.c16
-rw-r--r--drivers/iio/frequency/admv1014.c31
-rw-r--r--drivers/iio/humidity/Kconfig12
-rw-r--r--drivers/iio/humidity/Makefile1
-rw-r--r--drivers/iio/humidity/hdc3020.c473
-rw-r--r--drivers/iio/imu/Kconfig1
-rw-r--r--drivers/iio/imu/Makefile1
-rw-r--r--drivers/iio/imu/adis.c18
-rw-r--r--drivers/iio/imu/bmi323/Kconfig33
-rw-r--r--drivers/iio/imu/bmi323/Makefile7
-rw-r--r--drivers/iio/imu/bmi323/bmi323.h209
-rw-r--r--drivers/iio/imu/bmi323/bmi323_core.c2139
-rw-r--r--drivers/iio/imu/bmi323/bmi323_i2c.c121
-rw-r--r--drivers/iio/imu/bmi323/bmi323_spi.c92
-rw-r--r--drivers/iio/imu/bno055/bno055_ser_core.c6
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c5
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c5
-rw-r--r--drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c5
-rw-r--r--drivers/iio/imu/inv_mpu6050/inv_mpu_core.c9
-rw-r--r--drivers/iio/industrialio-buffer.c22
-rw-r--r--drivers/iio/industrialio-core.c66
-rw-r--r--drivers/iio/light/Kconfig37
-rw-r--r--drivers/iio/light/Makefile3
-rw-r--r--drivers/iio/light/isl76682.c345
-rw-r--r--drivers/iio/light/ltr390.c196
-rw-r--r--drivers/iio/light/ltrf216a.c10
-rw-r--r--drivers/iio/light/pa12203001.c2
-rw-r--r--drivers/iio/light/rohm-bu27008.c201
-rw-r--r--drivers/iio/light/veml6075.c474
-rw-r--r--drivers/iio/magnetometer/tmag5273.c10
-rw-r--r--drivers/iio/pressure/Kconfig22
-rw-r--r--drivers/iio/pressure/Makefile3
-rw-r--r--drivers/iio/pressure/bmp280-core.c42
-rw-r--r--drivers/iio/pressure/bmp280-i2c.c8
-rw-r--r--drivers/iio/pressure/bmp280-spi.c60
-rw-r--r--drivers/iio/pressure/bmp280.h6
-rw-r--r--drivers/iio/pressure/dlhl60d.c7
-rw-r--r--drivers/iio/pressure/hsc030pa.c494
-rw-r--r--drivers/iio/pressure/hsc030pa.h74
-rw-r--r--drivers/iio/pressure/hsc030pa_i2c.c69
-rw-r--r--drivers/iio/pressure/hsc030pa_spi.c61
-rw-r--r--drivers/iio/proximity/irsd200.c1
-rw-r--r--drivers/iio/proximity/sx9324.c24
-rw-r--r--drivers/iio/resolver/ad2s1210.c162
-rw-r--r--drivers/iio/temperature/Kconfig22
-rw-r--r--drivers/iio/temperature/Makefile2
-rw-r--r--drivers/iio/temperature/mcp9600.c139
-rw-r--r--drivers/iio/temperature/mlx90635.c1097
73 files changed, 8074 insertions, 459 deletions
diff --git a/drivers/iio/accel/Kconfig b/drivers/iio/accel/Kconfig
index d2221fe9ad..c9d7afe489 100644
--- a/drivers/iio/accel/Kconfig
+++ b/drivers/iio/accel/Kconfig
@@ -262,10 +262,11 @@ config BMI088_ACCEL
select REGMAP
select BMI088_ACCEL_SPI
help
- Say yes here to build support for the Bosch BMI088 accelerometer.
+ Say yes here to build support for the following Bosch accelerometers:
+ BMI088, BMI085, BMI090L. Note that all of these are combo module that
+ include both accelerometer and gyroscope.
- This is a combo module with both accelerometer and gyroscope. This
- driver only implements the accelerometer part, which has its own
+ This driver only implements the accelerometer part, which has its own
address and register map. BMG160 provides the gyroscope driver.
config BMI088_ACCEL_SPI
diff --git a/drivers/iio/accel/bmi088-accel-core.c b/drivers/iio/accel/bmi088-accel-core.c
index 84edcc78d7..4d989708e6 100644
--- a/drivers/iio/accel/bmi088-accel-core.c
+++ b/drivers/iio/accel/bmi088-accel-core.c
@@ -2,6 +2,8 @@
/*
* 3-axis accelerometer driver supporting following Bosch-Sensortec chips:
* - BMI088
+ * - BMI085
+ * - BMI090L
*
* Copyright (c) 2018-2021, Topic Embedded Products
*/
diff --git a/drivers/iio/accel/bmi088-accel-spi.c b/drivers/iio/accel/bmi088-accel-spi.c
index ee540edd84..7b419a7b24 100644
--- a/drivers/iio/accel/bmi088-accel-spi.c
+++ b/drivers/iio/accel/bmi088-accel-spi.c
@@ -2,6 +2,8 @@
/*
* 3-axis accelerometer driver supporting following Bosch-Sensortec chips:
* - BMI088
+ * - BMI085
+ * - BMI090L
*
* Copyright (c) 2018-2020, Topic Embedded Products
*/
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 35f9867da1..3b73c509bd 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -36,13 +36,29 @@ config AD4130
To compile this driver as a module, choose M here: the module will be
called ad4130.
+config AD7091R
+ tristate
+
config AD7091R5
tristate "Analog Devices AD7091R5 ADC Driver"
depends on I2C
+ select AD7091R
select REGMAP_I2C
help
Say yes here to build support for Analog Devices AD7091R-5 ADC.
+config AD7091R8
+ tristate "Analog Devices AD7091R8 ADC Driver"
+ depends on SPI
+ select AD7091R
+ select REGMAP_SPI
+ help
+ Say yes here to build support for Analog Devices AD7091R-2, AD7091R-4,
+ and AD7091R-8 ADC.
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad7091r8.
+
config AD7124
tristate "Analog Devices AD7124 and similar sigma-delta ADCs driver"
depends on SPI_MASTER
@@ -292,7 +308,7 @@ config ADI_AXI_ADC
select IIO_BUFFER
select IIO_BUFFER_HW_CONSUMER
select IIO_BUFFER_DMAENGINE
- depends on HAS_IOMEM
+ select REGMAP_MMIO
depends on OF
help
Say yes here to build support for Analog Devices Generic
@@ -745,6 +761,17 @@ config MAX1363
To compile this driver as a module, choose M here: the module will be
called max1363.
+config MAX34408
+ tristate "Maxim max34408/max344089 ADC driver"
+ depends on I2C
+ help
+ Say yes here to build support for Maxim max34408/max34409 current sense
+ monitor with 8-bits ADC interface with overcurrent delay/threshold and
+ shutdown delay.
+
+ To compile this driver as a module, choose M here: the module will be
+ called max34408.
+
config MAX77541_ADC
tristate "Analog Devices MAX77541 ADC driver"
depends on MFD_MAX77541
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index bee11d442a..d2fda54a32 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -7,7 +7,9 @@
obj-$(CONFIG_AB8500_GPADC) += ab8500-gpadc.o
obj-$(CONFIG_AD_SIGMA_DELTA) += ad_sigma_delta.o
obj-$(CONFIG_AD4130) += ad4130.o
-obj-$(CONFIG_AD7091R5) += ad7091r5.o ad7091r-base.o
+obj-$(CONFIG_AD7091R) += ad7091r-base.o
+obj-$(CONFIG_AD7091R5) += ad7091r5.o
+obj-$(CONFIG_AD7091R8) += ad7091r8.o
obj-$(CONFIG_AD7124) += ad7124.o
obj-$(CONFIG_AD7192) += ad7192.o
obj-$(CONFIG_AD7266) += ad7266.o
@@ -68,6 +70,7 @@ obj-$(CONFIG_MAX11205) += max11205.o
obj-$(CONFIG_MAX11410) += max11410.o
obj-$(CONFIG_MAX1241) += max1241.o
obj-$(CONFIG_MAX1363) += max1363.o
+obj-$(CONFIG_MAX34408) += max34408.o
obj-$(CONFIG_MAX77541_ADC) += max77541-adc.o
obj-$(CONFIG_MAX9611) += max9611.o
obj-$(CONFIG_MCP320X) += mcp320x.o
diff --git a/drivers/iio/adc/ad7091r-base.c b/drivers/iio/adc/ad7091r-base.c
index 76002b91c8..f4255b91ac 100644
--- a/drivers/iio/adc/ad7091r-base.c
+++ b/drivers/iio/adc/ad7091r-base.c
@@ -16,41 +16,6 @@
#include "ad7091r-base.h"
-#define AD7091R_REG_RESULT 0
-#define AD7091R_REG_CHANNEL 1
-#define AD7091R_REG_CONF 2
-#define AD7091R_REG_ALERT 3
-#define AD7091R_REG_CH_LOW_LIMIT(ch) ((ch) * 3 + 4)
-#define AD7091R_REG_CH_HIGH_LIMIT(ch) ((ch) * 3 + 5)
-#define AD7091R_REG_CH_HYSTERESIS(ch) ((ch) * 3 + 6)
-
-/* AD7091R_REG_RESULT */
-#define AD7091R_REG_RESULT_CH_ID(x) (((x) >> 13) & 0x3)
-#define AD7091R_REG_RESULT_CONV_RESULT(x) ((x) & 0xfff)
-
-/* AD7091R_REG_CONF */
-#define AD7091R_REG_CONF_ALERT_EN BIT(4)
-#define AD7091R_REG_CONF_AUTO BIT(8)
-#define AD7091R_REG_CONF_CMD BIT(10)
-
-#define AD7091R_REG_CONF_MODE_MASK \
- (AD7091R_REG_CONF_AUTO | AD7091R_REG_CONF_CMD)
-
-enum ad7091r_mode {
- AD7091R_MODE_SAMPLE,
- AD7091R_MODE_COMMAND,
- AD7091R_MODE_AUTOCYCLE,
-};
-
-struct ad7091r_state {
- struct device *dev;
- struct regmap *map;
- struct regulator *vref;
- const struct ad7091r_chip_info *chip_info;
- enum ad7091r_mode mode;
- struct mutex lock; /*lock to prevent concurent reads */
-};
-
const struct iio_event_spec ad7091r_events[] = {
{
.type = IIO_EV_TYPE_THRESH,
@@ -72,34 +37,6 @@ const struct iio_event_spec ad7091r_events[] = {
};
EXPORT_SYMBOL_NS_GPL(ad7091r_events, IIO_AD7091R);
-static int ad7091r_set_mode(struct ad7091r_state *st, enum ad7091r_mode mode)
-{
- int ret, conf;
-
- switch (mode) {
- case AD7091R_MODE_SAMPLE:
- conf = 0;
- break;
- case AD7091R_MODE_COMMAND:
- conf = AD7091R_REG_CONF_CMD;
- break;
- case AD7091R_MODE_AUTOCYCLE:
- conf = AD7091R_REG_CONF_AUTO;
- break;
- default:
- return -EINVAL;
- }
-
- ret = regmap_update_bits(st->map, AD7091R_REG_CONF,
- AD7091R_REG_CONF_MODE_MASK, conf);
- if (ret)
- return ret;
-
- st->mode = mode;
-
- return 0;
-}
-
static int ad7091r_set_channel(struct ad7091r_state *st, unsigned int channel)
{
unsigned int dummy;
@@ -133,7 +70,7 @@ static int ad7091r_read_one(struct iio_dev *iio_dev,
if (ret)
return ret;
- if (AD7091R_REG_RESULT_CH_ID(val) != channel)
+ if (st->chip_info->reg_result_chan_id(val) != channel)
return -EIO;
*read_val = AD7091R_REG_RESULT_CONV_RESULT(val);
@@ -364,9 +301,8 @@ static void ad7091r_remove(void *data)
regulator_disable(st->vref);
}
-int ad7091r_probe(struct device *dev, const char *name,
- const struct ad7091r_chip_info *chip_info,
- struct regmap *map, int irq)
+int ad7091r_probe(struct device *dev, const struct ad7091r_init_info *init_info,
+ int irq)
{
struct iio_dev *iio_dev;
struct ad7091r_state *st;
@@ -378,29 +314,42 @@ int ad7091r_probe(struct device *dev, const char *name,
st = iio_priv(iio_dev);
st->dev = dev;
- st->chip_info = chip_info;
- st->map = map;
+ init_info->init_adc_regmap(st, init_info->regmap_config);
+ if (IS_ERR(st->map))
+ return dev_err_probe(st->dev, PTR_ERR(st->map),
+ "Error initializing regmap\n");
- iio_dev->name = name;
iio_dev->info = &ad7091r_info;
iio_dev->modes = INDIO_DIRECT_MODE;
- iio_dev->num_channels = chip_info->num_channels;
- iio_dev->channels = chip_info->channels;
+ if (init_info->setup) {
+ ret = init_info->setup(st);
+ if (ret < 0)
+ return ret;
+ }
if (irq) {
+ st->chip_info = init_info->info_irq;
ret = regmap_update_bits(st->map, AD7091R_REG_CONF,
AD7091R_REG_CONF_ALERT_EN, BIT(4));
if (ret)
return ret;
ret = devm_request_threaded_irq(dev, irq, NULL,
- ad7091r_event_handler,
- IRQF_TRIGGER_FALLING | IRQF_ONESHOT, name, iio_dev);
+ ad7091r_event_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ st->chip_info->name, iio_dev);
if (ret)
return ret;
+ } else {
+ st->chip_info = init_info->info_no_irq;
}
+ iio_dev->name = st->chip_info->name;
+ iio_dev->num_channels = st->chip_info->num_channels;
+ iio_dev->channels = st->chip_info->channels;
+
st->vref = devm_regulator_get_optional(dev, "vref");
if (IS_ERR(st->vref)) {
if (PTR_ERR(st->vref) == -EPROBE_DEFER)
@@ -423,7 +372,7 @@ int ad7091r_probe(struct device *dev, const char *name,
}
/* Use command mode by default to convert only desired channels*/
- ret = ad7091r_set_mode(st, AD7091R_MODE_COMMAND);
+ ret = st->chip_info->set_mode(st, AD7091R_MODE_COMMAND);
if (ret)
return ret;
@@ -431,7 +380,7 @@ int ad7091r_probe(struct device *dev, const char *name,
}
EXPORT_SYMBOL_NS_GPL(ad7091r_probe, IIO_AD7091R);
-static bool ad7091r_writeable_reg(struct device *dev, unsigned int reg)
+bool ad7091r_writeable_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case AD7091R_REG_RESULT:
@@ -441,8 +390,9 @@ static bool ad7091r_writeable_reg(struct device *dev, unsigned int reg)
return true;
}
}
+EXPORT_SYMBOL_NS_GPL(ad7091r_writeable_reg, IIO_AD7091R);
-static bool ad7091r_volatile_reg(struct device *dev, unsigned int reg)
+bool ad7091r_volatile_reg(struct device *dev, unsigned int reg)
{
switch (reg) {
case AD7091R_REG_RESULT:
@@ -452,14 +402,7 @@ static bool ad7091r_volatile_reg(struct device *dev, unsigned int reg)
return false;
}
}
-
-const struct regmap_config ad7091r_regmap_config = {
- .reg_bits = 8,
- .val_bits = 16,
- .writeable_reg = ad7091r_writeable_reg,
- .volatile_reg = ad7091r_volatile_reg,
-};
-EXPORT_SYMBOL_NS_GPL(ad7091r_regmap_config, IIO_AD7091R);
+EXPORT_SYMBOL_NS_GPL(ad7091r_volatile_reg, IIO_AD7091R);
MODULE_AUTHOR("Beniamin Bia <beniamin.bia@analog.com>");
MODULE_DESCRIPTION("Analog Devices AD7091Rx multi-channel converters");
diff --git a/drivers/iio/adc/ad7091r-base.h b/drivers/iio/adc/ad7091r-base.h
index b9e1c8bf34..696bf7a897 100644
--- a/drivers/iio/adc/ad7091r-base.h
+++ b/drivers/iio/adc/ad7091r-base.h
@@ -8,27 +8,92 @@
#ifndef __DRIVERS_IIO_ADC_AD7091R_BASE_H__
#define __DRIVERS_IIO_ADC_AD7091R_BASE_H__
+#include <linux/regmap.h>
+
+#define AD7091R_REG_RESULT 0
+#define AD7091R_REG_CHANNEL 1
+#define AD7091R_REG_CONF 2
+#define AD7091R_REG_ALERT 3
+#define AD7091R_REG_CH_LOW_LIMIT(ch) ((ch) * 3 + 4)
+#define AD7091R_REG_CH_HIGH_LIMIT(ch) ((ch) * 3 + 5)
+#define AD7091R_REG_CH_HYSTERESIS(ch) ((ch) * 3 + 6)
+
+/* AD7091R_REG_RESULT */
+#define AD7091R5_REG_RESULT_CH_ID(x) (((x) >> 13) & 0x3)
+#define AD7091R8_REG_RESULT_CH_ID(x) (((x) >> 13) & 0x7)
+#define AD7091R_REG_RESULT_CONV_RESULT(x) ((x) & 0xfff)
+
+/* AD7091R_REG_CONF */
#define AD7091R_REG_CONF_INT_VREF BIT(0)
+#define AD7091R_REG_CONF_ALERT_EN BIT(4)
+#define AD7091R_REG_CONF_AUTO BIT(8)
+#define AD7091R_REG_CONF_CMD BIT(10)
+
+#define AD7091R_REG_CONF_MODE_MASK \
+ (AD7091R_REG_CONF_AUTO | AD7091R_REG_CONF_CMD)
/* AD7091R_REG_CH_LIMIT */
#define AD7091R_HIGH_LIMIT 0xFFF
#define AD7091R_LOW_LIMIT 0x0
+#define AD7091R_CHANNEL(idx, bits, ev, num_ev) { \
+ .type = IIO_VOLTAGE, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .indexed = 1, \
+ .channel = idx, \
+ .event_spec = ev, \
+ .num_event_specs = num_ev, \
+ .scan_type.storagebits = 16, \
+ .scan_type.realbits = bits, \
+}
+
struct device;
-struct ad7091r_state;
+struct gpio_desc;
+
+enum ad7091r_mode {
+ AD7091R_MODE_SAMPLE,
+ AD7091R_MODE_COMMAND,
+ AD7091R_MODE_AUTOCYCLE,
+};
+
+struct ad7091r_state {
+ struct device *dev;
+ struct regmap *map;
+ struct gpio_desc *convst_gpio;
+ struct gpio_desc *reset_gpio;
+ struct regulator *vref;
+ const struct ad7091r_chip_info *chip_info;
+ enum ad7091r_mode mode;
+ struct mutex lock; /*lock to prevent concurent reads */
+ __be16 tx_buf __aligned(IIO_DMA_MINALIGN);
+ __be16 rx_buf;
+};
struct ad7091r_chip_info {
+ const char *name;
unsigned int num_channels;
const struct iio_chan_spec *channels;
unsigned int vref_mV;
+ unsigned int (*reg_result_chan_id)(unsigned int val);
+ int (*set_mode)(struct ad7091r_state *st, enum ad7091r_mode mode);
+};
+
+struct ad7091r_init_info {
+ const struct ad7091r_chip_info *info_irq;
+ const struct ad7091r_chip_info *info_no_irq;
+ const struct regmap_config *regmap_config;
+ void (*init_adc_regmap)(struct ad7091r_state *st,
+ const struct regmap_config *regmap_conf);
+ int (*setup)(struct ad7091r_state *st);
};
extern const struct iio_event_spec ad7091r_events[3];
-extern const struct regmap_config ad7091r_regmap_config;
+int ad7091r_probe(struct device *dev, const struct ad7091r_init_info *init_info,
+ int irq);
-int ad7091r_probe(struct device *dev, const char *name,
- const struct ad7091r_chip_info *chip_info,
- struct regmap *map, int irq);
+bool ad7091r_volatile_reg(struct device *dev, unsigned int reg);
+bool ad7091r_writeable_reg(struct device *dev, unsigned int reg);
#endif /* __DRIVERS_IIO_ADC_AD7091R_BASE_H__ */
diff --git a/drivers/iio/adc/ad7091r5.c b/drivers/iio/adc/ad7091r5.c
index dae98c95eb..a758373341 100644
--- a/drivers/iio/adc/ad7091r5.c
+++ b/drivers/iio/adc/ad7091r5.c
@@ -12,17 +12,6 @@
#include "ad7091r-base.h"
-#define AD7091R_CHANNEL(idx, bits, ev, num_ev) { \
- .type = IIO_VOLTAGE, \
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
- .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
- .indexed = 1, \
- .channel = idx, \
- .event_spec = ev, \
- .num_event_specs = num_ev, \
- .scan_type.storagebits = 16, \
- .scan_type.realbits = bits, \
-}
static const struct iio_chan_spec ad7091r5_channels_irq[] = {
AD7091R_CHANNEL(0, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
AD7091R_CHANNEL(1, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
@@ -37,43 +26,98 @@ static const struct iio_chan_spec ad7091r5_channels_noirq[] = {
AD7091R_CHANNEL(3, 12, NULL, 0),
};
+static int ad7091r5_set_mode(struct ad7091r_state *st, enum ad7091r_mode mode)
+{
+ int ret, conf;
+
+ switch (mode) {
+ case AD7091R_MODE_SAMPLE:
+ conf = 0;
+ break;
+ case AD7091R_MODE_COMMAND:
+ conf = AD7091R_REG_CONF_CMD;
+ break;
+ case AD7091R_MODE_AUTOCYCLE:
+ conf = AD7091R_REG_CONF_AUTO;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_update_bits(st->map, AD7091R_REG_CONF,
+ AD7091R_REG_CONF_MODE_MASK, conf);
+ if (ret)
+ return ret;
+
+ st->mode = mode;
+
+ return 0;
+}
+
+static unsigned int ad7091r5_reg_result_chan_id(unsigned int val)
+{
+ return AD7091R5_REG_RESULT_CH_ID(val);
+}
+
static const struct ad7091r_chip_info ad7091r5_chip_info_irq = {
+ .name = "ad7091r-5",
.channels = ad7091r5_channels_irq,
.num_channels = ARRAY_SIZE(ad7091r5_channels_irq),
.vref_mV = 2500,
+ .reg_result_chan_id = &ad7091r5_reg_result_chan_id,
+ .set_mode = &ad7091r5_set_mode,
};
static const struct ad7091r_chip_info ad7091r5_chip_info_noirq = {
+ .name = "ad7091r-5",
.channels = ad7091r5_channels_noirq,
.num_channels = ARRAY_SIZE(ad7091r5_channels_noirq),
.vref_mV = 2500,
+ .reg_result_chan_id = &ad7091r5_reg_result_chan_id,
+ .set_mode = &ad7091r5_set_mode,
};
-static int ad7091r5_i2c_probe(struct i2c_client *i2c)
+static const struct regmap_config ad7091r_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .writeable_reg = ad7091r_writeable_reg,
+ .volatile_reg = ad7091r_volatile_reg,
+};
+
+static void ad7091r5_regmap_init(struct ad7091r_state *st,
+ const struct regmap_config *regmap_conf)
{
- const struct i2c_device_id *id = i2c_client_get_device_id(i2c);
- const struct ad7091r_chip_info *chip_info;
- struct regmap *map = devm_regmap_init_i2c(i2c, &ad7091r_regmap_config);
+ struct i2c_client *i2c = container_of(st->dev, struct i2c_client, dev);
+
+ st->map = devm_regmap_init_i2c(i2c, regmap_conf);
+}
+
+static struct ad7091r_init_info ad7091r5_init_info = {
+ .info_irq = &ad7091r5_chip_info_irq,
+ .info_no_irq = &ad7091r5_chip_info_noirq,
+ .regmap_config = &ad7091r_regmap_config,
+ .init_adc_regmap = &ad7091r5_regmap_init
+};
- if (IS_ERR(map))
- return PTR_ERR(map);
+static int ad7091r5_i2c_probe(struct i2c_client *i2c)
+{
+ const struct ad7091r_init_info *init_info;
- if (i2c->irq)
- chip_info = &ad7091r5_chip_info_irq;
- else
- chip_info = &ad7091r5_chip_info_noirq;
+ init_info = i2c_get_match_data(i2c);
+ if (!init_info)
+ return -EINVAL;
- return ad7091r_probe(&i2c->dev, id->name, chip_info, map, i2c->irq);
+ return ad7091r_probe(&i2c->dev, init_info, i2c->irq);
}
static const struct of_device_id ad7091r5_dt_ids[] = {
- { .compatible = "adi,ad7091r5" },
+ { .compatible = "adi,ad7091r5", .data = &ad7091r5_init_info },
{},
};
MODULE_DEVICE_TABLE(of, ad7091r5_dt_ids);
static const struct i2c_device_id ad7091r5_i2c_ids[] = {
- {"ad7091r5", 0},
+ {"ad7091r5", (kernel_ulong_t)&ad7091r5_init_info },
{}
};
MODULE_DEVICE_TABLE(i2c, ad7091r5_i2c_ids);
diff --git a/drivers/iio/adc/ad7091r8.c b/drivers/iio/adc/ad7091r8.c
new file mode 100644
index 0000000000..7005643050
--- /dev/null
+++ b/drivers/iio/adc/ad7091r8.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Analog Devices AD7091R8 12-bit SAR ADC driver
+ *
+ * Copyright 2023 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/gpio/consumer.h>
+#include <linux/spi/spi.h>
+
+#include "ad7091r-base.h"
+
+#define AD7091R8_REG_ADDR_MSK GENMASK(15, 11)
+#define AD7091R8_RD_WR_FLAG_MSK BIT(10)
+#define AD7091R8_REG_DATA_MSK GENMASK(9, 0)
+
+#define AD7091R_SPI_REGMAP_CONFIG(n) { \
+ .reg_bits = 8, \
+ .val_bits = 16, \
+ .volatile_reg = ad7091r_volatile_reg, \
+ .writeable_reg = ad7091r_writeable_reg, \
+ .max_register = AD7091R_REG_CH_HYSTERESIS(n), \
+}
+
+static int ad7091r8_set_mode(struct ad7091r_state *st, enum ad7091r_mode mode)
+{
+ /* AD7091R-2/-4/-8 don't set sample/command/autocycle mode in conf reg */
+ st->mode = mode;
+ return 0;
+}
+
+static unsigned int ad7091r8_reg_result_chan_id(unsigned int val)
+{
+ return AD7091R8_REG_RESULT_CH_ID(val);
+}
+
+#define AD7091R_SPI_CHIP_INFO(_n, _name) { \
+ .name = _name, \
+ .channels = ad7091r##_n##_channels, \
+ .num_channels = ARRAY_SIZE(ad7091r##_n##_channels), \
+ .vref_mV = 2500, \
+ .reg_result_chan_id = &ad7091r8_reg_result_chan_id, \
+ .set_mode = &ad7091r8_set_mode, \
+}
+
+#define AD7091R_SPI_CHIP_INFO_IRQ(_n, _name) { \
+ .name = _name, \
+ .channels = ad7091r##_n##_channels_irq, \
+ .num_channels = ARRAY_SIZE(ad7091r##_n##_channels_irq), \
+ .vref_mV = 2500, \
+ .reg_result_chan_id = &ad7091r8_reg_result_chan_id, \
+ .set_mode = &ad7091r8_set_mode, \
+}
+
+enum ad7091r8_info_ids {
+ AD7091R2_INFO,
+ AD7091R4_INFO,
+ AD7091R4_INFO_IRQ,
+ AD7091R8_INFO,
+ AD7091R8_INFO_IRQ,
+};
+
+static const struct iio_chan_spec ad7091r2_channels[] = {
+ AD7091R_CHANNEL(0, 12, NULL, 0),
+ AD7091R_CHANNEL(1, 12, NULL, 0),
+};
+
+static const struct iio_chan_spec ad7091r4_channels[] = {
+ AD7091R_CHANNEL(0, 12, NULL, 0),
+ AD7091R_CHANNEL(1, 12, NULL, 0),
+ AD7091R_CHANNEL(2, 12, NULL, 0),
+ AD7091R_CHANNEL(3, 12, NULL, 0),
+};
+
+static const struct iio_chan_spec ad7091r4_channels_irq[] = {
+ AD7091R_CHANNEL(0, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(1, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(2, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(3, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+};
+
+static const struct iio_chan_spec ad7091r8_channels[] = {
+ AD7091R_CHANNEL(0, 12, NULL, 0),
+ AD7091R_CHANNEL(1, 12, NULL, 0),
+ AD7091R_CHANNEL(2, 12, NULL, 0),
+ AD7091R_CHANNEL(3, 12, NULL, 0),
+ AD7091R_CHANNEL(4, 12, NULL, 0),
+ AD7091R_CHANNEL(5, 12, NULL, 0),
+ AD7091R_CHANNEL(6, 12, NULL, 0),
+ AD7091R_CHANNEL(7, 12, NULL, 0),
+};
+
+static const struct iio_chan_spec ad7091r8_channels_irq[] = {
+ AD7091R_CHANNEL(0, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(1, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(2, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(3, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(4, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(5, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(6, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+ AD7091R_CHANNEL(7, 12, ad7091r_events, ARRAY_SIZE(ad7091r_events)),
+};
+
+static void ad7091r_pulse_convst(struct ad7091r_state *st)
+{
+ gpiod_set_value_cansleep(st->convst_gpio, 1);
+ gpiod_set_value_cansleep(st->convst_gpio, 0);
+}
+
+static int ad7091r_regmap_bus_reg_read(void *context, unsigned int reg,
+ unsigned int *val)
+{
+ struct ad7091r_state *st = context;
+ struct spi_device *spi = container_of(st->dev, struct spi_device, dev);
+ int ret;
+
+ struct spi_transfer t[] = {
+ {
+ .tx_buf = &st->tx_buf,
+ .len = 2,
+ .cs_change = 1,
+ }, {
+ .rx_buf = &st->rx_buf,
+ .len = 2,
+ }
+ };
+
+ if (reg == AD7091R_REG_RESULT)
+ ad7091r_pulse_convst(st);
+
+ st->tx_buf = cpu_to_be16(reg << 11);
+
+ ret = spi_sync_transfer(spi, t, ARRAY_SIZE(t));
+ if (ret < 0)
+ return ret;
+
+ *val = be16_to_cpu(st->rx_buf);
+ return 0;
+}
+
+static int ad7091r_regmap_bus_reg_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ struct ad7091r_state *st = context;
+ struct spi_device *spi = container_of(st->dev, struct spi_device, dev);
+
+ /*
+ * AD7091R-2/-4/-8 protocol (datasheet page 31) is to do a single SPI
+ * transfer with reg address set in bits B15:B11 and value set in B9:B0.
+ */
+ st->tx_buf = cpu_to_be16(FIELD_PREP(AD7091R8_REG_DATA_MSK, val) |
+ FIELD_PREP(AD7091R8_RD_WR_FLAG_MSK, 1) |
+ FIELD_PREP(AD7091R8_REG_ADDR_MSK, reg));
+
+ return spi_write(spi, &st->tx_buf, 2);
+}
+
+static struct regmap_bus ad7091r8_regmap_bus = {
+ .reg_read = ad7091r_regmap_bus_reg_read,
+ .reg_write = ad7091r_regmap_bus_reg_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
+static const struct ad7091r_chip_info ad7091r8_infos[] = {
+ [AD7091R2_INFO] = AD7091R_SPI_CHIP_INFO(2, "ad7091r-2"),
+ [AD7091R4_INFO] = AD7091R_SPI_CHIP_INFO(4, "ad7091r-4"),
+ [AD7091R4_INFO_IRQ] = AD7091R_SPI_CHIP_INFO_IRQ(4, "ad7091r-4"),
+ [AD7091R8_INFO] = AD7091R_SPI_CHIP_INFO(8, "ad7091r-8"),
+ [AD7091R8_INFO_IRQ] = AD7091R_SPI_CHIP_INFO_IRQ(8, "ad7091r-8")
+};
+
+static const struct regmap_config ad7091r2_reg_conf = AD7091R_SPI_REGMAP_CONFIG(2);
+static const struct regmap_config ad7091r4_reg_conf = AD7091R_SPI_REGMAP_CONFIG(4);
+static const struct regmap_config ad7091r8_reg_conf = AD7091R_SPI_REGMAP_CONFIG(8);
+
+static void ad7091r8_regmap_init(struct ad7091r_state *st,
+ const struct regmap_config *regmap_conf)
+{
+ st->map = devm_regmap_init(st->dev, &ad7091r8_regmap_bus, st,
+ regmap_conf);
+}
+
+static int ad7091r8_gpio_setup(struct ad7091r_state *st)
+{
+ st->convst_gpio = devm_gpiod_get(st->dev, "convst", GPIOD_OUT_LOW);
+ if (IS_ERR(st->convst_gpio))
+ return dev_err_probe(st->dev, PTR_ERR(st->convst_gpio),
+ "Error getting convst GPIO\n");
+
+ st->reset_gpio = devm_gpiod_get_optional(st->dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(st->reset_gpio))
+ return dev_err_probe(st->dev, PTR_ERR(st->reset_gpio),
+ "Error on requesting reset GPIO\n");
+
+ if (st->reset_gpio) {
+ fsleep(20);
+ gpiod_set_value_cansleep(st->reset_gpio, 0);
+ }
+
+ return 0;
+}
+
+static struct ad7091r_init_info ad7091r2_init_info = {
+ .info_no_irq = &ad7091r8_infos[AD7091R2_INFO],
+ .regmap_config = &ad7091r2_reg_conf,
+ .init_adc_regmap = &ad7091r8_regmap_init,
+ .setup = &ad7091r8_gpio_setup
+};
+
+static struct ad7091r_init_info ad7091r4_init_info = {
+ .info_no_irq = &ad7091r8_infos[AD7091R4_INFO],
+ .info_irq = &ad7091r8_infos[AD7091R4_INFO_IRQ],
+ .regmap_config = &ad7091r4_reg_conf,
+ .init_adc_regmap = &ad7091r8_regmap_init,
+ .setup = &ad7091r8_gpio_setup
+};
+
+static struct ad7091r_init_info ad7091r8_init_info = {
+ .info_no_irq = &ad7091r8_infos[AD7091R8_INFO],
+ .info_irq = &ad7091r8_infos[AD7091R8_INFO_IRQ],
+ .regmap_config = &ad7091r8_reg_conf,
+ .init_adc_regmap = &ad7091r8_regmap_init,
+ .setup = &ad7091r8_gpio_setup
+};
+
+static int ad7091r8_spi_probe(struct spi_device *spi)
+{
+ const struct ad7091r_init_info *init_info;
+
+ init_info = spi_get_device_match_data(spi);
+ if (!init_info)
+ return -EINVAL;
+
+ return ad7091r_probe(&spi->dev, init_info, spi->irq);
+}
+
+static const struct of_device_id ad7091r8_of_match[] = {
+ { .compatible = "adi,ad7091r2", .data = &ad7091r2_init_info },
+ { .compatible = "adi,ad7091r4", .data = &ad7091r4_init_info },
+ { .compatible = "adi,ad7091r8", .data = &ad7091r8_init_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad7091r8_of_match);
+
+static const struct spi_device_id ad7091r8_spi_id[] = {
+ { "ad7091r2", (kernel_ulong_t)&ad7091r2_init_info },
+ { "ad7091r4", (kernel_ulong_t)&ad7091r4_init_info },
+ { "ad7091r8", (kernel_ulong_t)&ad7091r8_init_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad7091r8_spi_id);
+
+static struct spi_driver ad7091r8_driver = {
+ .driver = {
+ .name = "ad7091r8",
+ .of_match_table = ad7091r8_of_match,
+ },
+ .probe = ad7091r8_spi_probe,
+ .id_table = ad7091r8_spi_id,
+};
+module_spi_driver(ad7091r8_driver);
+
+MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD7091R8 ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_AD7091R);
diff --git a/drivers/iio/adc/ad9467.c b/drivers/iio/adc/ad9467.c
index f668313730..6581fce4ba 100644
--- a/drivers/iio/adc/ad9467.c
+++ b/drivers/iio/adc/ad9467.c
@@ -101,12 +101,6 @@
#define AD9467_DEF_OUTPUT_MODE 0x08
#define AD9467_REG_VREF_MASK 0x0F
-enum {
- ID_AD9265,
- ID_AD9434,
- ID_AD9467,
-};
-
struct ad9467_chip_info {
struct adi_axi_adc_chip_info axi_adc_info;
unsigned int default_output_mode;
@@ -164,7 +158,7 @@ static int ad9467_reg_access(struct adi_axi_adc_conv *conv, unsigned int reg,
struct spi_device *spi = st->spi;
int ret;
- if (readval == NULL) {
+ if (!readval) {
guard(mutex)(&st->lock);
ret = ad9467_spi_write(spi, reg, writeval);
if (ret)
@@ -234,43 +228,46 @@ static const struct iio_chan_spec ad9467_channels[] = {
AD9467_CHAN(0, 0, 16, 'S'),
};
-static const struct ad9467_chip_info ad9467_chip_tbl[] = {
- [ID_AD9265] = {
- .axi_adc_info = {
- .id = CHIPID_AD9265,
- .max_rate = 125000000UL,
- .scale_table = ad9265_scale_table,
- .num_scales = ARRAY_SIZE(ad9265_scale_table),
- .channels = ad9467_channels,
- .num_channels = ARRAY_SIZE(ad9467_channels),
- },
- .default_output_mode = AD9265_DEF_OUTPUT_MODE,
- .vref_mask = AD9265_REG_VREF_MASK,
+static const struct ad9467_chip_info ad9467_chip_tbl = {
+ .axi_adc_info = {
+ .name = "ad9467",
+ .id = CHIPID_AD9467,
+ .max_rate = 250000000UL,
+ .scale_table = ad9467_scale_table,
+ .num_scales = ARRAY_SIZE(ad9467_scale_table),
+ .channels = ad9467_channels,
+ .num_channels = ARRAY_SIZE(ad9467_channels),
},
- [ID_AD9434] = {
- .axi_adc_info = {
- .id = CHIPID_AD9434,
- .max_rate = 500000000UL,
- .scale_table = ad9434_scale_table,
- .num_scales = ARRAY_SIZE(ad9434_scale_table),
- .channels = ad9434_channels,
- .num_channels = ARRAY_SIZE(ad9434_channels),
- },
- .default_output_mode = AD9434_DEF_OUTPUT_MODE,
- .vref_mask = AD9434_REG_VREF_MASK,
+ .default_output_mode = AD9467_DEF_OUTPUT_MODE,
+ .vref_mask = AD9467_REG_VREF_MASK,
+};
+
+static const struct ad9467_chip_info ad9434_chip_tbl = {
+ .axi_adc_info = {
+ .name = "ad9434",
+ .id = CHIPID_AD9434,
+ .max_rate = 500000000UL,
+ .scale_table = ad9434_scale_table,
+ .num_scales = ARRAY_SIZE(ad9434_scale_table),
+ .channels = ad9434_channels,
+ .num_channels = ARRAY_SIZE(ad9434_channels),
},
- [ID_AD9467] = {
- .axi_adc_info = {
- .id = CHIPID_AD9467,
- .max_rate = 250000000UL,
- .scale_table = ad9467_scale_table,
- .num_scales = ARRAY_SIZE(ad9467_scale_table),
- .channels = ad9467_channels,
- .num_channels = ARRAY_SIZE(ad9467_channels),
- },
- .default_output_mode = AD9467_DEF_OUTPUT_MODE,
- .vref_mask = AD9467_REG_VREF_MASK,
+ .default_output_mode = AD9434_DEF_OUTPUT_MODE,
+ .vref_mask = AD9434_REG_VREF_MASK,
+};
+
+static const struct ad9467_chip_info ad9265_chip_tbl = {
+ .axi_adc_info = {
+ .name = "ad9265",
+ .id = CHIPID_AD9265,
+ .max_rate = 125000000UL,
+ .scale_table = ad9265_scale_table,
+ .num_scales = ARRAY_SIZE(ad9265_scale_table),
+ .channels = ad9467_channels,
+ .num_channels = ARRAY_SIZE(ad9467_channels),
},
+ .default_output_mode = AD9265_DEF_OUTPUT_MODE,
+ .vref_mask = AD9265_REG_VREF_MASK,
};
static int ad9467_get_scale(struct adi_axi_adc_conv *conv, int *val, int *val2)
@@ -454,9 +451,7 @@ static int ad9467_probe(struct spi_device *spi)
unsigned int id;
int ret;
- info = of_device_get_match_data(&spi->dev);
- if (!info)
- info = (void *)spi_get_device_id(spi)->driver_data;
+ info = spi_get_device_match_data(spi);
if (!info)
return -ENODEV;
@@ -506,17 +501,17 @@ static int ad9467_probe(struct spi_device *spi)
}
static const struct of_device_id ad9467_of_match[] = {
- { .compatible = "adi,ad9265", .data = &ad9467_chip_tbl[ID_AD9265], },
- { .compatible = "adi,ad9434", .data = &ad9467_chip_tbl[ID_AD9434], },
- { .compatible = "adi,ad9467", .data = &ad9467_chip_tbl[ID_AD9467], },
+ { .compatible = "adi,ad9265", .data = &ad9265_chip_tbl, },
+ { .compatible = "adi,ad9434", .data = &ad9434_chip_tbl, },
+ { .compatible = "adi,ad9467", .data = &ad9467_chip_tbl, },
{}
};
MODULE_DEVICE_TABLE(of, ad9467_of_match);
static const struct spi_device_id ad9467_ids[] = {
- { "ad9265", (kernel_ulong_t)&ad9467_chip_tbl[ID_AD9265] },
- { "ad9434", (kernel_ulong_t)&ad9467_chip_tbl[ID_AD9434] },
- { "ad9467", (kernel_ulong_t)&ad9467_chip_tbl[ID_AD9467] },
+ { "ad9265", (kernel_ulong_t)&ad9265_chip_tbl },
+ { "ad9434", (kernel_ulong_t)&ad9434_chip_tbl },
+ { "ad9467", (kernel_ulong_t)&ad9467_chip_tbl },
{}
};
MODULE_DEVICE_TABLE(spi, ad9467_ids);
diff --git a/drivers/iio/adc/adi-axi-adc.c b/drivers/iio/adc/adi-axi-adc.c
index ae83ada7f9..c247ff1541 100644
--- a/drivers/iio/adc/adi-axi-adc.c
+++ b/drivers/iio/adc/adi-axi-adc.c
@@ -14,6 +14,7 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
+#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/iio/iio.h>
@@ -62,7 +63,7 @@ struct adi_axi_adc_state {
struct mutex lock;
struct adi_axi_adc_client *client;
- void __iomem *regs;
+ struct regmap *regmap;
};
struct adi_axi_adc_client {
@@ -90,19 +91,6 @@ void *adi_axi_adc_conv_priv(struct adi_axi_adc_conv *conv)
}
EXPORT_SYMBOL_NS_GPL(adi_axi_adc_conv_priv, IIO_ADI_AXI);
-static void adi_axi_adc_write(struct adi_axi_adc_state *st,
- unsigned int reg,
- unsigned int val)
-{
- iowrite32(val, st->regs + reg);
-}
-
-static unsigned int adi_axi_adc_read(struct adi_axi_adc_state *st,
- unsigned int reg)
-{
- return ioread32(st->regs + reg);
-}
-
static int adi_axi_adc_config_dma_buffer(struct device *dev,
struct iio_dev *indio_dev)
{
@@ -163,17 +151,20 @@ static int adi_axi_adc_update_scan_mode(struct iio_dev *indio_dev,
{
struct adi_axi_adc_state *st = iio_priv(indio_dev);
struct adi_axi_adc_conv *conv = &st->client->conv;
- unsigned int i, ctrl;
+ unsigned int i;
+ int ret;
for (i = 0; i < conv->chip_info->num_channels; i++) {
- ctrl = adi_axi_adc_read(st, ADI_AXI_REG_CHAN_CTRL(i));
-
if (test_bit(i, scan_mask))
- ctrl |= ADI_AXI_REG_CHAN_CTRL_ENABLE;
+ ret = regmap_set_bits(st->regmap,
+ ADI_AXI_REG_CHAN_CTRL(i),
+ ADI_AXI_REG_CHAN_CTRL_ENABLE);
else
- ctrl &= ~ADI_AXI_REG_CHAN_CTRL_ENABLE;
-
- adi_axi_adc_write(st, ADI_AXI_REG_CHAN_CTRL(i), ctrl);
+ ret = regmap_clear_bits(st->regmap,
+ ADI_AXI_REG_CHAN_CTRL(i),
+ ADI_AXI_REG_CHAN_CTRL_ENABLE);
+ if (ret)
+ return ret;
}
return 0;
@@ -310,21 +301,32 @@ static int adi_axi_adc_setup_channels(struct device *dev,
}
for (i = 0; i < conv->chip_info->num_channels; i++) {
- adi_axi_adc_write(st, ADI_AXI_REG_CHAN_CTRL(i),
- ADI_AXI_REG_CHAN_CTRL_DEFAULTS);
+ ret = regmap_write(st->regmap, ADI_AXI_REG_CHAN_CTRL(i),
+ ADI_AXI_REG_CHAN_CTRL_DEFAULTS);
+ if (ret)
+ return ret;
}
return 0;
}
-static void axi_adc_reset(struct adi_axi_adc_state *st)
+static int axi_adc_reset(struct adi_axi_adc_state *st)
{
- adi_axi_adc_write(st, ADI_AXI_REG_RSTN, 0);
+ int ret;
+
+ ret = regmap_write(st->regmap, ADI_AXI_REG_RSTN, 0);
+ if (ret)
+ return ret;
+
mdelay(10);
- adi_axi_adc_write(st, ADI_AXI_REG_RSTN, ADI_AXI_REG_RSTN_MMCM_RSTN);
+ ret = regmap_write(st->regmap, ADI_AXI_REG_RSTN,
+ ADI_AXI_REG_RSTN_MMCM_RSTN);
+ if (ret)
+ return ret;
+
mdelay(10);
- adi_axi_adc_write(st, ADI_AXI_REG_RSTN,
- ADI_AXI_REG_RSTN_RSTN | ADI_AXI_REG_RSTN_MMCM_RSTN);
+ return regmap_write(st->regmap, ADI_AXI_REG_RSTN,
+ ADI_AXI_REG_RSTN_RSTN | ADI_AXI_REG_RSTN_MMCM_RSTN);
}
static void adi_axi_adc_cleanup(void *data)
@@ -335,12 +337,20 @@ static void adi_axi_adc_cleanup(void *data)
module_put(cl->dev->driver->owner);
}
+static const struct regmap_config axi_adc_regmap_config = {
+ .val_bits = 32,
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .max_register = 0x0800,
+};
+
static int adi_axi_adc_probe(struct platform_device *pdev)
{
struct adi_axi_adc_conv *conv;
struct iio_dev *indio_dev;
struct adi_axi_adc_client *cl;
struct adi_axi_adc_state *st;
+ void __iomem *base;
unsigned int ver;
int ret;
@@ -361,15 +371,24 @@ static int adi_axi_adc_probe(struct platform_device *pdev)
cl->state = st;
mutex_init(&st->lock);
- st->regs = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(st->regs))
- return PTR_ERR(st->regs);
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ st->regmap = devm_regmap_init_mmio(&pdev->dev, base,
+ &axi_adc_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
conv = &st->client->conv;
- axi_adc_reset(st);
+ ret = axi_adc_reset(st);
+ if (ret)
+ return ret;
- ver = adi_axi_adc_read(st, ADI_AXI_REG_VERSION);
+ ret = regmap_read(st->regmap, ADI_AXI_REG_VERSION, &ver);
+ if (ret)
+ return ret;
if (cl->info->version > ver) {
dev_err(&pdev->dev,
diff --git a/drivers/iio/adc/max34408.c b/drivers/iio/adc/max34408.c
new file mode 100644
index 0000000000..6c2ea2bc52
--- /dev/null
+++ b/drivers/iio/adc/max34408.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * IIO driver for Maxim MAX34409/34408 ADC, 4-Channels/2-Channels, 8bits, I2C
+ *
+ * Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX34408-MAX34409.pdf
+ *
+ * TODO: ALERT interrupt, Overcurrent delay, Shutdown delay
+ */
+
+#include <linux/bitfield.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+#define MAX34408_STATUS_REG 0x0
+#define MAX34408_CONTROL_REG 0x1
+#define MAX34408_OCDELAY_REG 0x2
+#define MAX34408_SDDELAY_REG 0x3
+
+#define MAX34408_ADC1_REG 0x4
+#define MAX34408_ADC2_REG 0x5
+/* ADC3 & ADC4 always returns 0x0 on 34408 */
+#define MAX34409_ADC3_REG 0x6
+#define MAX34409_ADC4_REG 0x7
+
+#define MAX34408_OCT1_REG 0x8
+#define MAX34408_OCT2_REG 0x9
+#define MAX34409_OCT3_REG 0xA
+#define MAX34409_OCT4_REG 0xB
+
+#define MAX34408_DID_REG 0xC
+#define MAX34408_DCYY_REG 0xD
+#define MAX34408_DCWW_REG 0xE
+
+/* Bit masks for status register */
+#define MAX34408_STATUS_OC_MSK GENMASK(1, 0)
+#define MAX34409_STATUS_OC_MSK GENMASK(3, 0)
+#define MAX34408_STATUS_SHTDN BIT(4)
+#define MAX34408_STATUS_ENA BIT(5)
+
+/* Bit masks for control register */
+#define MAX34408_CONTROL_AVG0 BIT(0)
+#define MAX34408_CONTROL_AVG1 BIT(1)
+#define MAX34408_CONTROL_AVG2 BIT(2)
+#define MAX34408_CONTROL_ALERT BIT(3)
+
+#define MAX34408_DEFAULT_AVG 0x4
+
+/* Bit masks for over current delay */
+#define MAX34408_OCDELAY_OCD_MSK GENMASK(6, 0)
+#define MAX34408_OCDELAY_RESET BIT(7)
+
+/* Bit masks for shutdown delay */
+#define MAX34408_SDDELAY_SHD_MSK GENMASK(6, 0)
+#define MAX34408_SDDELAY_RESET BIT(7)
+
+#define MAX34408_DEFAULT_RSENSE 1000
+
+/**
+ * struct max34408_data - max34408/max34409 specific data.
+ * @regmap: device register map.
+ * @dev: max34408 device.
+ * @lock: lock for protecting access to device hardware registers, mostly
+ * for read modify write cycles for control registers.
+ * @input_rsense: Rsense values in uOhm, will be overwritten by
+ * values from channel nodes.
+ */
+struct max34408_data {
+ struct regmap *regmap;
+ struct device *dev;
+ struct mutex lock;
+ u32 input_rsense[4];
+};
+
+static const struct regmap_config max34408_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX34408_DCWW_REG,
+};
+
+struct max34408_adc_model_data {
+ const char *model_name;
+ const struct iio_chan_spec *channels;
+ const int num_channels;
+};
+
+#define MAX34008_CHANNEL(_index, _address) \
+ { \
+ .type = IIO_CURRENT, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OFFSET), \
+ .channel = (_index), \
+ .address = (_address), \
+ .indexed = 1, \
+ }
+
+static const struct iio_chan_spec max34408_channels[] = {
+ MAX34008_CHANNEL(0, MAX34408_ADC1_REG),
+ MAX34008_CHANNEL(1, MAX34408_ADC2_REG),
+};
+
+static const struct iio_chan_spec max34409_channels[] = {
+ MAX34008_CHANNEL(0, MAX34408_ADC1_REG),
+ MAX34008_CHANNEL(1, MAX34408_ADC2_REG),
+ MAX34008_CHANNEL(2, MAX34409_ADC3_REG),
+ MAX34008_CHANNEL(3, MAX34409_ADC4_REG),
+};
+
+static int max34408_read_adc_avg(struct max34408_data *max34408,
+ const struct iio_chan_spec *chan, int *val)
+{
+ unsigned int ctrl;
+ int rc;
+
+ guard(mutex)(&max34408->lock);
+ rc = regmap_read(max34408->regmap, MAX34408_CONTROL_REG, (u32 *)&ctrl);
+ if (rc)
+ return rc;
+
+ /* set averaging (0b100) default values*/
+ rc = regmap_write(max34408->regmap, MAX34408_CONTROL_REG,
+ MAX34408_DEFAULT_AVG);
+ if (rc) {
+ dev_err(max34408->dev,
+ "Error (%d) writing control register\n", rc);
+ return rc;
+ }
+
+ rc = regmap_read(max34408->regmap, chan->address, val);
+ if (rc)
+ return rc;
+
+ /* back to old values */
+ rc = regmap_write(max34408->regmap, MAX34408_CONTROL_REG, ctrl);
+ if (rc)
+ dev_err(max34408->dev,
+ "Error (%d) writing control register\n", rc);
+
+ return rc;
+}
+
+static int max34408_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct max34408_data *max34408 = iio_priv(indio_dev);
+ int rc;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ rc = max34408_read_adc_avg(max34408, chan, val);
+ if (rc)
+ return rc;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /*
+ * calcluate current for 8bit ADC with Rsense
+ * value.
+ * 10 mV * 1000 / Rsense uOhm = max current
+ * (max current * adc val * 1000) / (2^8 - 1) mA
+ */
+ *val = 10000 / max34408->input_rsense[chan->channel];
+ *val2 = 8;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info max34408_info = {
+ .read_raw = max34408_read_raw,
+};
+
+static const struct max34408_adc_model_data max34408_model_data = {
+ .model_name = "max34408",
+ .channels = max34408_channels,
+ .num_channels = 2,
+};
+
+static const struct max34408_adc_model_data max34409_model_data = {
+ .model_name = "max34409",
+ .channels = max34409_channels,
+ .num_channels = 4,
+};
+
+static int max34408_probe(struct i2c_client *client)
+{
+ const struct max34408_adc_model_data *model_data;
+ struct device *dev = &client->dev;
+ struct max34408_data *max34408;
+ struct fwnode_handle *node;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ int rc, i = 0;
+
+ model_data = i2c_get_match_data(client);
+ if (!model_data)
+ return -EINVAL;
+
+ regmap = devm_regmap_init_i2c(client, &max34408_regmap_config);
+ if (IS_ERR(regmap)) {
+ dev_err_probe(dev, PTR_ERR(regmap),
+ "regmap_init failed\n");
+ return PTR_ERR(regmap);
+ }
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*max34408));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ max34408 = iio_priv(indio_dev);
+ max34408->regmap = regmap;
+ max34408->dev = dev;
+ mutex_init(&max34408->lock);
+
+ device_for_each_child_node(dev, node) {
+ fwnode_property_read_u32(node, "maxim,rsense-val-micro-ohms",
+ &max34408->input_rsense[i]);
+ i++;
+ }
+
+ /* disable ALERT and averaging */
+ rc = regmap_write(max34408->regmap, MAX34408_CONTROL_REG, 0x0);
+ if (rc)
+ return rc;
+
+ indio_dev->channels = model_data->channels;
+ indio_dev->num_channels = model_data->num_channels;
+ indio_dev->name = model_data->model_name;
+
+ indio_dev->info = &max34408_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id max34408_of_match[] = {
+ {
+ .compatible = "maxim,max34408",
+ .data = &max34408_model_data,
+ },
+ {
+ .compatible = "maxim,max34409",
+ .data = &max34409_model_data,
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, max34408_of_match);
+
+static const struct i2c_device_id max34408_id[] = {
+ { "max34408", (kernel_ulong_t)&max34408_model_data },
+ { "max34409", (kernel_ulong_t)&max34409_model_data },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, max34408_id);
+
+static struct i2c_driver max34408_driver = {
+ .driver = {
+ .name = "max34408",
+ .of_match_table = max34408_of_match,
+ },
+ .probe = max34408_probe,
+ .id_table = max34408_id,
+};
+module_i2c_driver(max34408_driver);
+
+MODULE_AUTHOR("Ivan Mikhaylov <fr0st61te@gmail.com>");
+MODULE_DESCRIPTION("Maxim MAX34408/34409 ADC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/mcp3911.c b/drivers/iio/adc/mcp3911.c
index d864558bc0..7a32e7a1be 100644
--- a/drivers/iio/adc/mcp3911.c
+++ b/drivers/iio/adc/mcp3911.c
@@ -7,6 +7,7 @@
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/err.h>
@@ -316,47 +317,37 @@ static int mcp3911_read_raw(struct iio_dev *indio_dev,
int *val2, long mask)
{
struct mcp3911 *adc = iio_priv(indio_dev);
- int ret = -EINVAL;
+ int ret;
- mutex_lock(&adc->lock);
+ guard(mutex)(&adc->lock);
switch (mask) {
case IIO_CHAN_INFO_RAW:
ret = mcp3911_read(adc,
MCP3911_CHANNEL(channel->channel), val, 3);
if (ret)
- goto out;
+ return ret;
*val = sign_extend32(*val, 23);
-
- ret = IIO_VAL_INT;
- break;
-
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_OFFSET:
-
ret = adc->chip->get_offset(adc, channel->channel, val);
if (ret)
- goto out;
+ return ret;
- ret = IIO_VAL_INT;
- break;
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
ret = adc->chip->get_osr(adc, val);
if (ret)
- goto out;
-
- ret = IIO_VAL_INT;
- break;
+ return ret;
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = mcp3911_scale_table[ilog2(adc->gain[channel->channel])][0];
*val2 = mcp3911_scale_table[ilog2(adc->gain[channel->channel])][1];
- ret = IIO_VAL_INT_PLUS_NANO;
- break;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
}
-
-out:
- mutex_unlock(&adc->lock);
- return ret;
}
static int mcp3911_write_raw(struct iio_dev *indio_dev,
@@ -364,9 +355,8 @@ static int mcp3911_write_raw(struct iio_dev *indio_dev,
int val2, long mask)
{
struct mcp3911 *adc = iio_priv(indio_dev);
- int ret = -EINVAL;
- mutex_lock(&adc->lock);
+ guard(mutex)(&adc->lock);
switch (mask) {
case IIO_CHAN_INFO_SCALE:
for (int i = 0; i < MCP3911_NUM_SCALES; i++) {
@@ -374,32 +364,25 @@ static int mcp3911_write_raw(struct iio_dev *indio_dev,
val2 == mcp3911_scale_table[i][1]) {
adc->gain[channel->channel] = BIT(i);
- ret = adc->chip->set_scale(adc, channel->channel, i);
+ return adc->chip->set_scale(adc, channel->channel, i);
}
}
- break;
+ return -EINVAL;
case IIO_CHAN_INFO_OFFSET:
- if (val2 != 0) {
- ret = -EINVAL;
- goto out;
- }
-
- ret = adc->chip->set_offset(adc, channel->channel, val);
- break;
+ if (val2 != 0)
+ return -EINVAL;
+ return adc->chip->set_offset(adc, channel->channel, val);
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
for (int i = 0; i < ARRAY_SIZE(mcp3911_osr_table); i++) {
if (val == mcp3911_osr_table[i]) {
- ret = adc->chip->set_osr(adc, i);
- break;
+ return adc->chip->set_osr(adc, i);
}
}
- break;
+ return -EINVAL;
+ default:
+ return -EINVAL;
}
-
-out:
- mutex_unlock(&adc->lock);
- return ret;
}
static int mcp3911_calc_scale_table(struct mcp3911 *adc)
@@ -532,7 +515,7 @@ static irqreturn_t mcp3911_trigger_handler(int irq, void *p)
int i = 0;
int ret;
- mutex_lock(&adc->lock);
+ guard(mutex)(&adc->lock);
adc->tx_buf = MCP3911_REG_READ(MCP3911_CHANNEL(0), adc->dev_addr);
ret = spi_sync_transfer(adc->spi, xfer, ARRAY_SIZE(xfer));
if (ret < 0) {
@@ -549,7 +532,6 @@ static irqreturn_t mcp3911_trigger_handler(int irq, void *p)
iio_push_to_buffers_with_timestamp(indio_dev, &adc->scan,
iio_get_time_ns(indio_dev));
out:
- mutex_unlock(&adc->lock);
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
diff --git a/drivers/iio/amplifiers/hmc425a.c b/drivers/iio/amplifiers/hmc425a.c
index e87d35d50a..ed4d729226 100644
--- a/drivers/iio/amplifiers/hmc425a.c
+++ b/drivers/iio/amplifiers/hmc425a.c
@@ -5,6 +5,7 @@
* Copyright 2020 Analog Devices Inc.
*/
+#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
@@ -22,6 +23,7 @@
enum hmc425a_type {
ID_HMC425A,
ID_HMC540S,
+ ID_ADRF5740
};
struct hmc425a_chip_info {
@@ -74,6 +76,10 @@ static int hmc425a_read_raw(struct iio_dev *indio_dev,
case ID_HMC540S:
gain = ~code * -1000;
break;
+ case ID_ADRF5740:
+ code = code & BIT(3) ? code & ~BIT(2) : code;
+ gain = code * -2000;
+ break;
}
*val = gain / 1000;
@@ -113,6 +119,10 @@ static int hmc425a_write_raw(struct iio_dev *indio_dev,
case ID_HMC540S:
code = ~((abs(gain) / 1000) & 0xF);
break;
+ case ID_ADRF5740:
+ code = (abs(gain) / 2000) & 0xF;
+ code = code & BIT(3) ? code | BIT(2) : code;
+ break;
}
mutex_lock(&st->lock);
@@ -165,6 +175,7 @@ static const struct iio_chan_spec hmc425a_channels[] = {
static const struct of_device_id hmc425a_of_match[] = {
{ .compatible = "adi,hmc425a", .data = (void *)ID_HMC425A },
{ .compatible = "adi,hmc540s", .data = (void *)ID_HMC540S },
+ { .compatible = "adi,adrf5740", .data = (void *)ID_ADRF5740 },
{},
};
MODULE_DEVICE_TABLE(of, hmc425a_of_match);
@@ -188,6 +199,15 @@ static struct hmc425a_chip_info hmc425a_chip_info_tbl[] = {
.gain_max = 0,
.default_gain = -0x10, /* set default gain -15.0db*/
},
+ [ID_ADRF5740] = {
+ .name = "adrf5740",
+ .channels = hmc425a_channels,
+ .num_channels = ARRAY_SIZE(hmc425a_channels),
+ .num_gpios = 4,
+ .gain_min = -22000,
+ .gain_max = 0,
+ .default_gain = 0xF, /* set default gain -22.0db*/
+ },
};
static int hmc425a_probe(struct platform_device *pdev)
@@ -229,6 +249,9 @@ static int hmc425a_probe(struct platform_device *pdev)
indio_dev->info = &hmc425a_info;
indio_dev->modes = INDIO_DIRECT_MODE;
+ /* Set default gain */
+ hmc425a_write(indio_dev, st->gain);
+
return devm_iio_device_register(&pdev->dev, indio_dev);
}
diff --git a/drivers/iio/buffer/industrialio-buffer-dma.c b/drivers/iio/buffer/industrialio-buffer-dma.c
index d348af8b97..5610ba6792 100644
--- a/drivers/iio/buffer/industrialio-buffer-dma.c
+++ b/drivers/iio/buffer/industrialio-buffer-dma.c
@@ -179,7 +179,7 @@ static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block(
}
block->size = size;
- block->state = IIO_BLOCK_STATE_DEQUEUED;
+ block->state = IIO_BLOCK_STATE_DONE;
block->queue = queue;
INIT_LIST_HEAD(&block->head);
kref_init(&block->kref);
@@ -191,16 +191,8 @@ static struct iio_dma_buffer_block *iio_dma_buffer_alloc_block(
static void _iio_dma_buffer_block_done(struct iio_dma_buffer_block *block)
{
- struct iio_dma_buffer_queue *queue = block->queue;
-
- /*
- * The buffer has already been freed by the application, just drop the
- * reference.
- */
- if (block->state != IIO_BLOCK_STATE_DEAD) {
+ if (block->state != IIO_BLOCK_STATE_DEAD)
block->state = IIO_BLOCK_STATE_DONE;
- list_add_tail(&block->head, &queue->outgoing);
- }
}
/**
@@ -261,7 +253,6 @@ static bool iio_dma_block_reusable(struct iio_dma_buffer_block *block)
* not support abort and has not given back the block yet.
*/
switch (block->state) {
- case IIO_BLOCK_STATE_DEQUEUED:
case IIO_BLOCK_STATE_QUEUED:
case IIO_BLOCK_STATE_DONE:
return true;
@@ -317,7 +308,6 @@ int iio_dma_buffer_request_update(struct iio_buffer *buffer)
* dead. This means we can reset the lists without having to fear
* corrution.
*/
- INIT_LIST_HEAD(&queue->outgoing);
spin_unlock_irq(&queue->list_lock);
INIT_LIST_HEAD(&queue->incoming);
@@ -356,6 +346,29 @@ out_unlock:
}
EXPORT_SYMBOL_GPL(iio_dma_buffer_request_update);
+static void iio_dma_buffer_fileio_free(struct iio_dma_buffer_queue *queue)
+{
+ unsigned int i;
+
+ spin_lock_irq(&queue->list_lock);
+ for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
+ if (!queue->fileio.blocks[i])
+ continue;
+ queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD;
+ }
+ spin_unlock_irq(&queue->list_lock);
+
+ INIT_LIST_HEAD(&queue->incoming);
+
+ for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
+ if (!queue->fileio.blocks[i])
+ continue;
+ iio_buffer_block_put(queue->fileio.blocks[i]);
+ queue->fileio.blocks[i] = NULL;
+ }
+ queue->fileio.active_block = NULL;
+}
+
static void iio_dma_buffer_submit_block(struct iio_dma_buffer_queue *queue,
struct iio_dma_buffer_block *block)
{
@@ -456,14 +469,20 @@ static struct iio_dma_buffer_block *iio_dma_buffer_dequeue(
struct iio_dma_buffer_queue *queue)
{
struct iio_dma_buffer_block *block;
+ unsigned int idx;
spin_lock_irq(&queue->list_lock);
- block = list_first_entry_or_null(&queue->outgoing, struct
- iio_dma_buffer_block, head);
- if (block != NULL) {
- list_del(&block->head);
- block->state = IIO_BLOCK_STATE_DEQUEUED;
+
+ idx = queue->fileio.next_dequeue;
+ block = queue->fileio.blocks[idx];
+
+ if (block->state == IIO_BLOCK_STATE_DONE) {
+ idx = (idx + 1) % ARRAY_SIZE(queue->fileio.blocks);
+ queue->fileio.next_dequeue = idx;
+ } else {
+ block = NULL;
}
+
spin_unlock_irq(&queue->list_lock);
return block;
@@ -539,6 +558,7 @@ size_t iio_dma_buffer_data_available(struct iio_buffer *buf)
struct iio_dma_buffer_queue *queue = iio_buffer_to_queue(buf);
struct iio_dma_buffer_block *block;
size_t data_available = 0;
+ unsigned int i;
/*
* For counting the available bytes we'll use the size of the block not
@@ -552,8 +572,15 @@ size_t iio_dma_buffer_data_available(struct iio_buffer *buf)
data_available += queue->fileio.active_block->size;
spin_lock_irq(&queue->list_lock);
- list_for_each_entry(block, &queue->outgoing, head)
- data_available += block->size;
+
+ for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
+ block = queue->fileio.blocks[i];
+
+ if (block != queue->fileio.active_block
+ && block->state == IIO_BLOCK_STATE_DONE)
+ data_available += block->size;
+ }
+
spin_unlock_irq(&queue->list_lock);
mutex_unlock(&queue->lock);
@@ -617,7 +644,6 @@ int iio_dma_buffer_init(struct iio_dma_buffer_queue *queue,
queue->ops = ops;
INIT_LIST_HEAD(&queue->incoming);
- INIT_LIST_HEAD(&queue->outgoing);
mutex_init(&queue->lock);
spin_lock_init(&queue->list_lock);
@@ -635,28 +661,9 @@ EXPORT_SYMBOL_GPL(iio_dma_buffer_init);
*/
void iio_dma_buffer_exit(struct iio_dma_buffer_queue *queue)
{
- unsigned int i;
-
mutex_lock(&queue->lock);
- spin_lock_irq(&queue->list_lock);
- for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
- if (!queue->fileio.blocks[i])
- continue;
- queue->fileio.blocks[i]->state = IIO_BLOCK_STATE_DEAD;
- }
- INIT_LIST_HEAD(&queue->outgoing);
- spin_unlock_irq(&queue->list_lock);
-
- INIT_LIST_HEAD(&queue->incoming);
-
- for (i = 0; i < ARRAY_SIZE(queue->fileio.blocks); i++) {
- if (!queue->fileio.blocks[i])
- continue;
- iio_buffer_block_put(queue->fileio.blocks[i]);
- queue->fileio.blocks[i] = NULL;
- }
- queue->fileio.active_block = NULL;
+ iio_dma_buffer_fileio_free(queue);
queue->ops = NULL;
mutex_unlock(&queue->lock);
diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index c30657e10e..02649ab81b 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -5,6 +5,17 @@
menu "Chemical Sensors"
+config AOSONG_AGS02MA
+ tristate "Aosong AGS02MA TVOC sensor driver"
+ depends on I2C
+ select CRC8
+ help
+ Say Y here to build support for Aosong AGS02MA TVOC (Total Volatile
+ Organic Compounds) sensor.
+
+ To compile this driver as module, choose M here: the module will be
+ called ags02ma.
+
config ATLAS_PH_SENSOR
tristate "Atlas Scientific OEM SM sensors"
depends on I2C
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
index a11e777a7a..2f3dee8bb7 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -4,6 +4,7 @@
#
# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_AOSONG_AGS02MA) += ags02ma.o
obj-$(CONFIG_ATLAS_PH_SENSOR) += atlas-sensor.o
obj-$(CONFIG_ATLAS_EZO_SENSOR) += atlas-ezo-sensor.o
obj-$(CONFIG_BME680) += bme680_core.o
diff --git a/drivers/iio/chemical/ags02ma.c b/drivers/iio/chemical/ags02ma.c
new file mode 100644
index 0000000000..8fcd809465
--- /dev/null
+++ b/drivers/iio/chemical/ags02ma.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com>
+ *
+ * Driver for Aosong AGS02MA
+ *
+ * Datasheet:
+ * https://asairsensors.com/wp-content/uploads/2021/09/AGS02MA.pdf
+ * Product Page:
+ * http://www.aosong.com/m/en/products-33.html
+ */
+
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+
+#define AGS02MA_TVOC_READ_REG 0x00
+#define AGS02MA_VERSION_REG 0x11
+
+#define AGS02MA_VERSION_PROCESSING_DELAY 30
+#define AGS02MA_TVOC_READ_PROCESSING_DELAY 1500
+
+#define AGS02MA_CRC8_INIT 0xff
+#define AGS02MA_CRC8_POLYNOMIAL 0x31
+
+DECLARE_CRC8_TABLE(ags02ma_crc8_table);
+
+struct ags02ma_data {
+ struct i2c_client *client;
+};
+
+struct ags02ma_reading {
+ __be32 data;
+ u8 crc;
+} __packed;
+
+static int ags02ma_register_read(struct i2c_client *client, u8 reg, u16 delay,
+ u32 *val)
+{
+ int ret;
+ u8 crc;
+ struct ags02ma_reading read_buffer;
+
+ ret = i2c_master_send(client, &reg, sizeof(reg));
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to send data to register 0x%x: %d", reg, ret);
+ return ret;
+ }
+
+ /* Processing Delay, Check Table 7.7 in the datasheet */
+ msleep_interruptible(delay);
+
+ ret = i2c_master_recv(client, (u8 *)&read_buffer, sizeof(read_buffer));
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to receive from register 0x%x: %d", reg, ret);
+ return ret;
+ }
+
+ crc = crc8(ags02ma_crc8_table, (u8 *)&read_buffer.data,
+ sizeof(read_buffer.data), AGS02MA_CRC8_INIT);
+ if (crc != read_buffer.crc) {
+ dev_err(&client->dev, "CRC error\n");
+ return -EIO;
+ }
+
+ *val = be32_to_cpu(read_buffer.data);
+ return 0;
+}
+
+static int ags02ma_read_raw(struct iio_dev *iio_device,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ int ret;
+ struct ags02ma_data *data = iio_priv(iio_device);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ags02ma_register_read(data->client, AGS02MA_TVOC_READ_REG,
+ AGS02MA_TVOC_READ_PROCESSING_DELAY,
+ val);
+ if (ret < 0)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ /* The sensor reads data as ppb */
+ *val = 0;
+ *val2 = 100;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ags02ma_info = {
+ .read_raw = ags02ma_read_raw,
+};
+
+static const struct iio_chan_spec ags02ma_channel = {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_VOC,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+};
+
+static int ags02ma_probe(struct i2c_client *client)
+{
+ int ret;
+ struct ags02ma_data *data;
+ struct iio_dev *indio_dev;
+ u32 version;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ crc8_populate_msb(ags02ma_crc8_table, AGS02MA_CRC8_POLYNOMIAL);
+
+ ret = ags02ma_register_read(client, AGS02MA_VERSION_REG,
+ AGS02MA_VERSION_PROCESSING_DELAY, &version);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to read device version\n");
+ dev_dbg(&client->dev, "Aosong AGS02MA, Version: 0x%x", version);
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ indio_dev->info = &ags02ma_info;
+ indio_dev->channels = &ags02ma_channel;
+ indio_dev->num_channels = 1;
+ indio_dev->name = "ags02ma";
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id ags02ma_id_table[] = {
+ { "ags02ma" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ags02ma_id_table);
+
+static const struct of_device_id ags02ma_of_table[] = {
+ { .compatible = "aosong,ags02ma" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ags02ma_of_table);
+
+static struct i2c_driver ags02ma_driver = {
+ .driver = {
+ .name = "ags02ma",
+ .of_match_table = ags02ma_of_table,
+ },
+ .id_table = ags02ma_id_table,
+ .probe = ags02ma_probe,
+};
+module_i2c_driver(ags02ma_driver);
+
+MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");
+MODULE_DESCRIPTION("Aosong AGS02MA TVOC Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/chemical/pms7003.c b/drivers/iio/chemical/pms7003.c
index e9857d93b3..b5cf15a515 100644
--- a/drivers/iio/chemical/pms7003.c
+++ b/drivers/iio/chemical/pms7003.c
@@ -211,13 +211,13 @@ static bool pms7003_frame_is_okay(struct pms7003_frame *frame)
return checksum == pms7003_calc_checksum(frame);
}
-static int pms7003_receive_buf(struct serdev_device *serdev,
- const unsigned char *buf, size_t size)
+static ssize_t pms7003_receive_buf(struct serdev_device *serdev, const u8 *buf,
+ size_t size)
{
struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
struct pms7003_state *state = iio_priv(indio_dev);
struct pms7003_frame *frame = &state->frame;
- int num;
+ size_t num;
if (!frame->expected_length) {
u16 magic;
diff --git a/drivers/iio/chemical/scd30_serial.c b/drivers/iio/chemical/scd30_serial.c
index 3c519103d3..a47654591e 100644
--- a/drivers/iio/chemical/scd30_serial.c
+++ b/drivers/iio/chemical/scd30_serial.c
@@ -174,13 +174,13 @@ static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u
return 0;
}
-static int scd30_serdev_receive_buf(struct serdev_device *serdev,
- const unsigned char *buf, size_t size)
+static ssize_t scd30_serdev_receive_buf(struct serdev_device *serdev,
+ const u8 *buf, size_t size)
{
struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
struct scd30_serdev_priv *priv;
struct scd30_state *state;
- int num;
+ size_t num;
if (!indio_dev)
return 0;
diff --git a/drivers/iio/chemical/sps30_serial.c b/drivers/iio/chemical/sps30_serial.c
index 164f4b3e02..3afa89f8ac 100644
--- a/drivers/iio/chemical/sps30_serial.c
+++ b/drivers/iio/chemical/sps30_serial.c
@@ -74,8 +74,8 @@ static int sps30_serial_xfer(struct sps30_state *state, const unsigned char *buf
}
static const struct {
- unsigned char byte;
- unsigned char byte2;
+ u8 byte;
+ u8 byte2;
} sps30_serial_bytes[] = {
{ 0x11, 0x31 },
{ 0x13, 0x33 },
@@ -83,7 +83,7 @@ static const struct {
{ 0x7d, 0x5d },
};
-static int sps30_serial_put_byte(unsigned char *buf, unsigned char byte)
+static int sps30_serial_put_byte(u8 *buf, u8 byte)
{
int i;
@@ -102,7 +102,7 @@ static int sps30_serial_put_byte(unsigned char *buf, unsigned char byte)
return 1;
}
-static char sps30_serial_get_byte(bool escaped, unsigned char byte2)
+static u8 sps30_serial_get_byte(bool escaped, u8 byte2)
{
int i;
@@ -130,8 +130,8 @@ static unsigned char sps30_serial_calc_chksum(const unsigned char *buf, size_t n
return ~chksum;
}
-static int sps30_serial_prep_frame(unsigned char *buf, unsigned char cmd,
- const unsigned char *arg, size_t arg_size)
+static int sps30_serial_prep_frame(u8 *buf, u8 cmd, const u8 *arg,
+ size_t arg_size)
{
unsigned char chksum;
int num = 0;
@@ -210,14 +210,14 @@ static int sps30_serial_command(struct sps30_state *state, unsigned char cmd,
return rsp_size;
}
-static int sps30_serial_receive_buf(struct serdev_device *serdev,
- const unsigned char *buf, size_t size)
+static ssize_t sps30_serial_receive_buf(struct serdev_device *serdev,
+ const u8 *buf, size_t size)
{
struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
struct sps30_serial_priv *priv;
struct sps30_state *state;
- unsigned char byte;
size_t i;
+ u8 byte;
if (!indio_dev)
return 0;
diff --git a/drivers/iio/dac/Kconfig b/drivers/iio/dac/Kconfig
index 93b8be183d..34eb40bb95 100644
--- a/drivers/iio/dac/Kconfig
+++ b/drivers/iio/dac/Kconfig
@@ -400,6 +400,16 @@ config MCP4728
To compile this driver as a module, choose M here: the module
will be called mcp4728.
+config MCP4821
+ tristate "MCP4801/02/11/12/21/22 DAC driver"
+ depends on SPI
+ help
+ Say yes here to build the driver for the Microchip MCP4801
+ MCP4802, MCP4811, MCP4812, MCP4821 and MCP4822 DAC devices.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mcp4821.
+
config MCP4922
tristate "MCP4902, MCP4912, MCP4922 DAC driver"
depends on SPI
diff --git a/drivers/iio/dac/Makefile b/drivers/iio/dac/Makefile
index 5b2bac900d..55bf89739d 100644
--- a/drivers/iio/dac/Makefile
+++ b/drivers/iio/dac/Makefile
@@ -42,6 +42,7 @@ obj-$(CONFIG_MAX5522) += max5522.o
obj-$(CONFIG_MAX5821) += max5821.o
obj-$(CONFIG_MCP4725) += mcp4725.o
obj-$(CONFIG_MCP4728) += mcp4728.o
+obj-$(CONFIG_MCP4821) += mcp4821.o
obj-$(CONFIG_MCP4922) += mcp4922.o
obj-$(CONFIG_STM32_DAC_CORE) += stm32-dac-core.o
obj-$(CONFIG_STM32_DAC) += stm32-dac.o
diff --git a/drivers/iio/dac/ad5791.c b/drivers/iio/dac/ad5791.c
index a4167454da..75b549827e 100644
--- a/drivers/iio/dac/ad5791.c
+++ b/drivers/iio/dac/ad5791.c
@@ -345,6 +345,7 @@ static int ad5791_probe(struct spi_device *spi)
struct iio_dev *indio_dev;
struct ad5791_state *st;
int ret, pos_voltage_uv = 0, neg_voltage_uv = 0;
+ bool use_rbuf_gain2;
indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
if (!indio_dev)
@@ -379,6 +380,12 @@ static int ad5791_probe(struct spi_device *spi)
st->pwr_down = true;
st->spi = spi;
+ if (pdata)
+ use_rbuf_gain2 = pdata->use_rbuf_gain2;
+ else
+ use_rbuf_gain2 = device_property_read_bool(&spi->dev,
+ "adi,rbuf-gain2-en");
+
if (!IS_ERR(st->reg_vss) && !IS_ERR(st->reg_vdd)) {
st->vref_mv = (pos_voltage_uv + neg_voltage_uv) / 1000;
st->vref_neg_mv = neg_voltage_uv / 1000;
@@ -398,7 +405,7 @@ static int ad5791_probe(struct spi_device *spi)
st->ctrl = AD5761_CTRL_LINCOMP(st->chip_info->get_lin_comp(st->vref_mv))
- | ((pdata && pdata->use_rbuf_gain2) ? 0 : AD5791_CTRL_RBUF) |
+ | (use_rbuf_gain2 ? 0 : AD5791_CTRL_RBUF) |
AD5791_CTRL_BIN2SC;
ret = ad5791_spi_write(st, AD5791_ADDR_CTRL, st->ctrl |
diff --git a/drivers/iio/dac/mcp4821.c b/drivers/iio/dac/mcp4821.c
new file mode 100644
index 0000000000..8a0480d338
--- /dev/null
+++ b/drivers/iio/dac/mcp4821.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com>
+ *
+ * Driver for Microchip MCP4801, MCP4802, MCP4811, MCP4812, MCP4821 and MCP4822
+ *
+ * Based on the work of:
+ * Michael Welling (MCP4922 Driver)
+ *
+ * Datasheet:
+ * MCP48x1: https://ww1.microchip.com/downloads/en/DeviceDoc/22244B.pdf
+ * MCP48x2: https://ww1.microchip.com/downloads/en/DeviceDoc/20002249B.pdf
+ *
+ * TODO:
+ * - Configurable gain
+ * - Regulator control
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+#include <asm/unaligned.h>
+
+#define MCP4821_ACTIVE_MODE BIT(12)
+#define MCP4802_SECOND_CHAN BIT(15)
+
+/* DAC uses an internal Voltage reference of 4.096V at a gain of 2x */
+#define MCP4821_2X_GAIN_VREF_MV 4096
+
+enum mcp4821_supported_drvice_ids {
+ ID_MCP4801,
+ ID_MCP4802,
+ ID_MCP4811,
+ ID_MCP4812,
+ ID_MCP4821,
+ ID_MCP4822,
+};
+
+struct mcp4821_state {
+ struct spi_device *spi;
+ u16 dac_value[2];
+};
+
+struct mcp4821_chip_info {
+ const char *name;
+ int num_channels;
+ const struct iio_chan_spec channels[2];
+};
+
+#define MCP4821_CHAN(channel_id, resolution) \
+ { \
+ .type = IIO_VOLTAGE, .output = 1, .indexed = 1, \
+ .channel = (channel_id), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .scan_type = { \
+ .realbits = (resolution), \
+ .shift = 12 - (resolution), \
+ }, \
+ }
+
+static const struct mcp4821_chip_info mcp4821_chip_info_table[6] = {
+ [ID_MCP4801] = {
+ .name = "mcp4801",
+ .num_channels = 1,
+ .channels = {
+ MCP4821_CHAN(0, 8),
+ },
+ },
+ [ID_MCP4802] = {
+ .name = "mcp4802",
+ .num_channels = 2,
+ .channels = {
+ MCP4821_CHAN(0, 8),
+ MCP4821_CHAN(1, 8),
+ },
+ },
+ [ID_MCP4811] = {
+ .name = "mcp4811",
+ .num_channels = 1,
+ .channels = {
+ MCP4821_CHAN(0, 10),
+ },
+ },
+ [ID_MCP4812] = {
+ .name = "mcp4812",
+ .num_channels = 2,
+ .channels = {
+ MCP4821_CHAN(0, 10),
+ MCP4821_CHAN(1, 10),
+ },
+ },
+ [ID_MCP4821] = {
+ .name = "mcp4821",
+ .num_channels = 1,
+ .channels = {
+ MCP4821_CHAN(0, 12),
+ },
+ },
+ [ID_MCP4822] = {
+ .name = "mcp4822",
+ .num_channels = 2,
+ .channels = {
+ MCP4821_CHAN(0, 12),
+ MCP4821_CHAN(1, 12),
+ },
+ },
+};
+
+static int mcp4821_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct mcp4821_state *state;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ state = iio_priv(indio_dev);
+ *val = state->dac_value[chan->channel];
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = MCP4821_2X_GAIN_VREF_MV;
+ *val2 = chan->scan_type.realbits;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mcp4821_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct mcp4821_state *state = iio_priv(indio_dev);
+ u16 write_val;
+ __be16 write_buffer;
+ int ret;
+
+ if (val2 != 0)
+ return -EINVAL;
+
+ if (val < 0 || val >= BIT(chan->scan_type.realbits))
+ return -EINVAL;
+
+ if (mask != IIO_CHAN_INFO_RAW)
+ return -EINVAL;
+
+ write_val = MCP4821_ACTIVE_MODE | val << chan->scan_type.shift;
+ if (chan->channel)
+ write_val |= MCP4802_SECOND_CHAN;
+
+ write_buffer = cpu_to_be16(write_val);
+ ret = spi_write(state->spi, &write_buffer, sizeof(write_buffer));
+ if (ret) {
+ dev_err(&state->spi->dev, "Failed to write to device: %d", ret);
+ return ret;
+ }
+
+ state->dac_value[chan->channel] = val;
+
+ return 0;
+}
+
+static const struct iio_info mcp4821_info = {
+ .read_raw = &mcp4821_read_raw,
+ .write_raw = &mcp4821_write_raw,
+};
+
+static int mcp4821_probe(struct spi_device *spi)
+{
+ struct iio_dev *indio_dev;
+ struct mcp4821_state *state;
+ const struct mcp4821_chip_info *info;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*state));
+ if (indio_dev == NULL)
+ return -ENOMEM;
+
+ state = iio_priv(indio_dev);
+ state->spi = spi;
+
+ info = spi_get_device_match_data(spi);
+ indio_dev->name = info->name;
+ indio_dev->info = &mcp4821_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = info->channels;
+ indio_dev->num_channels = info->num_channels;
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+#define MCP4821_COMPATIBLE(of_compatible, id) \
+ { \
+ .compatible = of_compatible, \
+ .data = &mcp4821_chip_info_table[id] \
+ }
+
+static const struct of_device_id mcp4821_of_table[] = {
+ MCP4821_COMPATIBLE("microchip,mcp4801", ID_MCP4801),
+ MCP4821_COMPATIBLE("microchip,mcp4802", ID_MCP4802),
+ MCP4821_COMPATIBLE("microchip,mcp4811", ID_MCP4811),
+ MCP4821_COMPATIBLE("microchip,mcp4812", ID_MCP4812),
+ MCP4821_COMPATIBLE("microchip,mcp4821", ID_MCP4821),
+ MCP4821_COMPATIBLE("microchip,mcp4822", ID_MCP4822),
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, mcp4821_of_table);
+
+static const struct spi_device_id mcp4821_id_table[] = {
+ { "mcp4801", (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4801]},
+ { "mcp4802", (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4802]},
+ { "mcp4811", (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4811]},
+ { "mcp4812", (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4812]},
+ { "mcp4821", (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4821]},
+ { "mcp4822", (kernel_ulong_t)&mcp4821_chip_info_table[ID_MCP4822]},
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(spi, mcp4821_id_table);
+
+static struct spi_driver mcp4821_driver = {
+ .driver = {
+ .name = "mcp4821",
+ .of_match_table = mcp4821_of_table,
+ },
+ .probe = mcp4821_probe,
+ .id_table = mcp4821_id_table,
+};
+module_spi_driver(mcp4821_driver);
+
+MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");
+MODULE_DESCRIPTION("Microchip MCP4821 DAC Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/frequency/adf4377.c b/drivers/iio/frequency/adf4377.c
index 26abecbd51..9284c13f1a 100644
--- a/drivers/iio/frequency/adf4377.c
+++ b/drivers/iio/frequency/adf4377.c
@@ -870,7 +870,6 @@ static const struct iio_chan_spec adf4377_channels[] = {
static int adf4377_properties_parse(struct adf4377_state *st)
{
struct spi_device *spi = st->spi;
- const char *str;
int ret;
st->clkin = devm_clk_get_enabled(&spi->dev, "ref_in");
@@ -896,16 +895,13 @@ static int adf4377_properties_parse(struct adf4377_state *st)
return dev_err_probe(&spi->dev, PTR_ERR(st->gpio_enclk2),
"failed to get the CE GPIO\n");
- ret = device_property_read_string(&spi->dev, "adi,muxout-select", &str);
- if (ret) {
- st->muxout_select = ADF4377_MUXOUT_HIGH_Z;
- } else {
- ret = match_string(adf4377_muxout_modes, ARRAY_SIZE(adf4377_muxout_modes), str);
- if (ret < 0)
- return ret;
-
+ ret = device_property_match_property_string(&spi->dev, "adi,muxout-select",
+ adf4377_muxout_modes,
+ ARRAY_SIZE(adf4377_muxout_modes));
+ if (ret >= 0)
st->muxout_select = ret;
- }
+ else
+ st->muxout_select = ADF4377_MUXOUT_HIGH_Z;
return 0;
}
diff --git a/drivers/iio/frequency/admv1014.c b/drivers/iio/frequency/admv1014.c
index bb5e1feef4..b46b73b89e 100644
--- a/drivers/iio/frequency/admv1014.c
+++ b/drivers/iio/frequency/admv1014.c
@@ -710,7 +710,6 @@ static int admv1014_init(struct admv1014_state *st)
static int admv1014_properties_parse(struct admv1014_state *st)
{
- const char *str;
unsigned int i;
struct spi_device *spi = st->spi;
int ret;
@@ -719,27 +718,21 @@ static int admv1014_properties_parse(struct admv1014_state *st)
st->p1db_comp = device_property_read_bool(&spi->dev, "adi,p1db-compensation-enable");
- ret = device_property_read_string(&spi->dev, "adi,input-mode", &str);
- if (ret) {
- st->input_mode = ADMV1014_IQ_MODE;
- } else {
- ret = match_string(input_mode_names, ARRAY_SIZE(input_mode_names), str);
- if (ret < 0)
- return ret;
-
+ ret = device_property_match_property_string(&spi->dev, "adi,input-mode",
+ input_mode_names,
+ ARRAY_SIZE(input_mode_names));
+ if (ret >= 0)
st->input_mode = ret;
- }
-
- ret = device_property_read_string(&spi->dev, "adi,quad-se-mode", &str);
- if (ret) {
- st->quad_se_mode = ADMV1014_SE_MODE_POS;
- } else {
- ret = match_string(quad_se_mode_names, ARRAY_SIZE(quad_se_mode_names), str);
- if (ret < 0)
- return ret;
+ else
+ st->input_mode = ADMV1014_IQ_MODE;
+ ret = device_property_match_property_string(&spi->dev, "adi,quad-se-mode",
+ quad_se_mode_names,
+ ARRAY_SIZE(quad_se_mode_names));
+ if (ret >= 0)
st->quad_se_mode = ADMV1014_SE_MODE_POS + (ret * 3);
- }
+ else
+ st->quad_se_mode = ADMV1014_SE_MODE_POS;
for (i = 0; i < ADMV1014_NUM_REGULATORS; ++i)
st->regulators[i].supply = admv1014_reg_name[i];
diff --git a/drivers/iio/humidity/Kconfig b/drivers/iio/humidity/Kconfig
index 2de5494e7c..b15b7a3b66 100644
--- a/drivers/iio/humidity/Kconfig
+++ b/drivers/iio/humidity/Kconfig
@@ -48,6 +48,18 @@ config HDC2010
To compile this driver as a module, choose M here: the module
will be called hdc2010.
+config HDC3020
+ tristate "TI HDC3020 relative humidity and temperature sensor"
+ depends on I2C
+ select CRC8
+ help
+ Say yes here to build support for the Texas Instruments
+ HDC3020, HDC3021 and HDC3022 relative humidity and temperature
+ sensors.
+
+ To compile this driver as a module, choose M here: the module
+ will be called hdc3020.
+
config HID_SENSOR_HUMIDITY
tristate "HID Environmental humidity sensor"
depends on HID_SENSOR_HUB
diff --git a/drivers/iio/humidity/Makefile b/drivers/iio/humidity/Makefile
index f19ff3de97..5fbeef299f 100644
--- a/drivers/iio/humidity/Makefile
+++ b/drivers/iio/humidity/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_AM2315) += am2315.o
obj-$(CONFIG_DHT11) += dht11.o
obj-$(CONFIG_HDC100X) += hdc100x.o
obj-$(CONFIG_HDC2010) += hdc2010.o
+obj-$(CONFIG_HDC3020) += hdc3020.o
obj-$(CONFIG_HID_SENSOR_HUMIDITY) += hid-sensor-humidity.o
hts221-y := hts221_core.o \
diff --git a/drivers/iio/humidity/hdc3020.c b/drivers/iio/humidity/hdc3020.c
new file mode 100644
index 0000000000..ed70415512
--- /dev/null
+++ b/drivers/iio/humidity/hdc3020.c
@@ -0,0 +1,473 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * hdc3020.c - Support for the TI HDC3020,HDC3021 and HDC3022
+ * temperature + relative humidity sensors
+ *
+ * Copyright (C) 2023
+ *
+ * Datasheet: https://www.ti.com/lit/ds/symlink/hdc3020.pdf
+ */
+
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <asm/unaligned.h>
+
+#include <linux/iio/iio.h>
+
+#define HDC3020_HEATER_CMD_MSB 0x30 /* shared by all heater commands */
+#define HDC3020_HEATER_ENABLE 0x6D
+#define HDC3020_HEATER_DISABLE 0x66
+#define HDC3020_HEATER_CONFIG 0x6E
+
+#define HDC3020_READ_RETRY_TIMES 10
+#define HDC3020_BUSY_DELAY_MS 10
+
+#define HDC3020_CRC8_POLYNOMIAL 0x31
+
+static const u8 HDC3020_S_AUTO_10HZ_MOD0[2] = { 0x27, 0x37 };
+
+static const u8 HDC3020_EXIT_AUTO[2] = { 0x30, 0x93 };
+
+static const u8 HDC3020_R_T_RH_AUTO[2] = { 0xE0, 0x00 };
+static const u8 HDC3020_R_T_LOW_AUTO[2] = { 0xE0, 0x02 };
+static const u8 HDC3020_R_T_HIGH_AUTO[2] = { 0xE0, 0x03 };
+static const u8 HDC3020_R_RH_LOW_AUTO[2] = { 0xE0, 0x04 };
+static const u8 HDC3020_R_RH_HIGH_AUTO[2] = { 0xE0, 0x05 };
+
+struct hdc3020_data {
+ struct i2c_client *client;
+ /*
+ * Ensure that the sensor configuration (currently only heater is
+ * supported) will not be changed during the process of reading
+ * sensor data (this driver will try HDC3020_READ_RETRY_TIMES times
+ * if the device does not respond).
+ */
+ struct mutex lock;
+};
+
+static const int hdc3020_heater_vals[] = {0, 1, 0x3FFF};
+
+static const struct iio_chan_spec hdc3020_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PEAK) |
+ BIT(IIO_CHAN_INFO_TROUGH) | BIT(IIO_CHAN_INFO_OFFSET),
+ },
+ {
+ .type = IIO_HUMIDITYRELATIVE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_PEAK) |
+ BIT(IIO_CHAN_INFO_TROUGH),
+ },
+ {
+ /*
+ * For setting the internal heater, which can be switched on to
+ * prevent or remove any condensation that may develop when the
+ * ambient environment approaches its dew point temperature.
+ */
+ .type = IIO_CURRENT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW),
+ .output = 1,
+ },
+};
+
+DECLARE_CRC8_TABLE(hdc3020_crc8_table);
+
+static int hdc3020_write_bytes(struct hdc3020_data *data, const u8 *buf, u8 len)
+{
+ struct i2c_client *client = data->client;
+ struct i2c_msg msg;
+ int ret, cnt;
+
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.buf = (char *)buf;
+ msg.len = len;
+
+ /*
+ * During the measurement process, HDC3020 will not return data.
+ * So wait for a while and try again
+ */
+ for (cnt = 0; cnt < HDC3020_READ_RETRY_TIMES; cnt++) {
+ ret = i2c_transfer(client->adapter, &msg, 1);
+ if (ret == 1)
+ return 0;
+
+ mdelay(HDC3020_BUSY_DELAY_MS);
+ }
+ dev_err(&client->dev, "Could not write sensor command\n");
+
+ return -ETIMEDOUT;
+}
+
+static int hdc3020_read_bytes(struct hdc3020_data *data, const u8 *buf,
+ void *val, int len)
+{
+ int ret, cnt;
+ struct i2c_client *client = data->client;
+ struct i2c_msg msg[2] = {
+ [0] = {
+ .addr = client->addr,
+ .flags = 0,
+ .buf = (char *)buf,
+ .len = 2,
+ },
+ [1] = {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .buf = val,
+ .len = len,
+ },
+ };
+
+ /*
+ * During the measurement process, HDC3020 will not return data.
+ * So wait for a while and try again
+ */
+ for (cnt = 0; cnt < HDC3020_READ_RETRY_TIMES; cnt++) {
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret == 2)
+ return 0;
+
+ mdelay(HDC3020_BUSY_DELAY_MS);
+ }
+ dev_err(&client->dev, "Could not read sensor data\n");
+
+ return -ETIMEDOUT;
+}
+
+static int hdc3020_read_measurement(struct hdc3020_data *data,
+ enum iio_chan_type type, int *val)
+{
+ u8 crc, buf[6];
+ int ret;
+
+ ret = hdc3020_read_bytes(data, HDC3020_R_T_RH_AUTO, buf, 6);
+ if (ret < 0)
+ return ret;
+
+ /* CRC check of the temperature measurement */
+ crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE);
+ if (crc != buf[2])
+ return -EINVAL;
+
+ /* CRC check of the relative humidity measurement */
+ crc = crc8(hdc3020_crc8_table, buf + 3, 2, CRC8_INIT_VALUE);
+ if (crc != buf[5])
+ return -EINVAL;
+
+ if (type == IIO_TEMP)
+ *val = get_unaligned_be16(buf);
+ else if (type == IIO_HUMIDITYRELATIVE)
+ *val = get_unaligned_be16(&buf[3]);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * After exiting the automatic measurement mode or resetting, the peak
+ * value will be reset to the default value
+ * This method is used to get the highest temp measured during automatic
+ * measurement
+ */
+static int hdc3020_read_high_peak_t(struct hdc3020_data *data, int *val)
+{
+ u8 crc, buf[3];
+ int ret;
+
+ ret = hdc3020_read_bytes(data, HDC3020_R_T_HIGH_AUTO, buf, 3);
+ if (ret < 0)
+ return ret;
+
+ crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE);
+ if (crc != buf[2])
+ return -EINVAL;
+
+ *val = get_unaligned_be16(buf);
+
+ return 0;
+}
+
+/*
+ * This method is used to get the lowest temp measured during automatic
+ * measurement
+ */
+static int hdc3020_read_low_peak_t(struct hdc3020_data *data, int *val)
+{
+ u8 crc, buf[3];
+ int ret;
+
+ ret = hdc3020_read_bytes(data, HDC3020_R_T_LOW_AUTO, buf, 3);
+ if (ret < 0)
+ return ret;
+
+ crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE);
+ if (crc != buf[2])
+ return -EINVAL;
+
+ *val = get_unaligned_be16(buf);
+
+ return 0;
+}
+
+/*
+ * This method is used to get the highest humidity measured during automatic
+ * measurement
+ */
+static int hdc3020_read_high_peak_rh(struct hdc3020_data *data, int *val)
+{
+ u8 crc, buf[3];
+ int ret;
+
+ ret = hdc3020_read_bytes(data, HDC3020_R_RH_HIGH_AUTO, buf, 3);
+ if (ret < 0)
+ return ret;
+
+ crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE);
+ if (crc != buf[2])
+ return -EINVAL;
+
+ *val = get_unaligned_be16(buf);
+
+ return 0;
+}
+
+/*
+ * This method is used to get the lowest humidity measured during automatic
+ * measurement
+ */
+static int hdc3020_read_low_peak_rh(struct hdc3020_data *data, int *val)
+{
+ u8 crc, buf[3];
+ int ret;
+
+ ret = hdc3020_read_bytes(data, HDC3020_R_RH_LOW_AUTO, buf, 3);
+ if (ret < 0)
+ return ret;
+
+ crc = crc8(hdc3020_crc8_table, buf, 2, CRC8_INIT_VALUE);
+ if (crc != buf[2])
+ return -EINVAL;
+
+ *val = get_unaligned_be16(buf);
+
+ return 0;
+}
+
+static int hdc3020_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct hdc3020_data *data = iio_priv(indio_dev);
+ int ret;
+
+ if (chan->type != IIO_TEMP && chan->type != IIO_HUMIDITYRELATIVE)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ guard(mutex)(&data->lock);
+ ret = hdc3020_read_measurement(data, chan->type, val);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_PEAK: {
+ guard(mutex)(&data->lock);
+ if (chan->type == IIO_TEMP) {
+ ret = hdc3020_read_high_peak_t(data, val);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = hdc3020_read_high_peak_rh(data, val);
+ if (ret < 0)
+ return ret;
+ }
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_TROUGH: {
+ guard(mutex)(&data->lock);
+ if (chan->type == IIO_TEMP) {
+ ret = hdc3020_read_low_peak_t(data, val);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = hdc3020_read_low_peak_rh(data, val);
+ if (ret < 0)
+ return ret;
+ }
+ return IIO_VAL_INT;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ *val2 = 65536;
+ if (chan->type == IIO_TEMP)
+ *val = 175;
+ else
+ *val = 100;
+ return IIO_VAL_FRACTIONAL;
+
+ case IIO_CHAN_INFO_OFFSET:
+ if (chan->type != IIO_TEMP)
+ return -EINVAL;
+
+ *val = -16852;
+ return IIO_VAL_INT;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int hdc3020_read_available(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_RAW || chan->type != IIO_CURRENT)
+ return -EINVAL;
+
+ *vals = hdc3020_heater_vals;
+ *type = IIO_VAL_INT;
+
+ return IIO_AVAIL_RANGE;
+}
+
+static int hdc3020_update_heater(struct hdc3020_data *data, int val)
+{
+ u8 buf[5];
+ int ret;
+
+ if (val < hdc3020_heater_vals[0] || val > hdc3020_heater_vals[2])
+ return -EINVAL;
+
+ buf[0] = HDC3020_HEATER_CMD_MSB;
+
+ if (!val) {
+ buf[1] = HDC3020_HEATER_DISABLE;
+ return hdc3020_write_bytes(data, buf, 2);
+ }
+
+ buf[1] = HDC3020_HEATER_CONFIG;
+ put_unaligned_be16(val & GENMASK(13, 0), &buf[2]);
+ buf[4] = crc8(hdc3020_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
+ ret = hdc3020_write_bytes(data, buf, 5);
+ if (ret < 0)
+ return ret;
+
+ buf[1] = HDC3020_HEATER_ENABLE;
+
+ return hdc3020_write_bytes(data, buf, 2);
+}
+
+static int hdc3020_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct hdc3020_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ if (chan->type != IIO_CURRENT)
+ return -EINVAL;
+
+ guard(mutex)(&data->lock);
+ return hdc3020_update_heater(data, val);
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info hdc3020_info = {
+ .read_raw = hdc3020_read_raw,
+ .write_raw = hdc3020_write_raw,
+ .read_avail = hdc3020_read_available,
+};
+
+static void hdc3020_stop(void *data)
+{
+ hdc3020_write_bytes((struct hdc3020_data *)data, HDC3020_EXIT_AUTO, 2);
+}
+
+static int hdc3020_probe(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev;
+ struct hdc3020_data *data;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EOPNOTSUPP;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ mutex_init(&data->lock);
+
+ crc8_populate_msb(hdc3020_crc8_table, HDC3020_CRC8_POLYNOMIAL);
+
+ indio_dev->name = "hdc3020";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &hdc3020_info;
+ indio_dev->channels = hdc3020_channels;
+ indio_dev->num_channels = ARRAY_SIZE(hdc3020_channels);
+
+ ret = hdc3020_write_bytes(data, HDC3020_S_AUTO_10HZ_MOD0, 2);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "Unable to set up measurement\n");
+
+ ret = devm_add_action_or_reset(&data->client->dev, hdc3020_stop, data);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_device_register(&data->client->dev, indio_dev);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to add device");
+
+ return 0;
+}
+
+static const struct i2c_device_id hdc3020_id[] = {
+ { "hdc3020" },
+ { "hdc3021" },
+ { "hdc3022" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, hdc3020_id);
+
+static const struct of_device_id hdc3020_dt_ids[] = {
+ { .compatible = "ti,hdc3020" },
+ { .compatible = "ti,hdc3021" },
+ { .compatible = "ti,hdc3022" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, hdc3020_dt_ids);
+
+static struct i2c_driver hdc3020_driver = {
+ .driver = {
+ .name = "hdc3020",
+ .of_match_table = hdc3020_dt_ids,
+ },
+ .probe = hdc3020_probe,
+ .id_table = hdc3020_id,
+};
+module_i2c_driver(hdc3020_driver);
+
+MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>");
+MODULE_AUTHOR("Li peiyu <579lpy@gmail.com>");
+MODULE_DESCRIPTION("TI HDC3020 humidity and temperature sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/imu/Kconfig b/drivers/iio/imu/Kconfig
index c2f97629e9..52a155ff32 100644
--- a/drivers/iio/imu/Kconfig
+++ b/drivers/iio/imu/Kconfig
@@ -53,6 +53,7 @@ config ADIS16480
ADIS16485, ADIS16488 inertial sensors.
source "drivers/iio/imu/bmi160/Kconfig"
+source "drivers/iio/imu/bmi323/Kconfig"
source "drivers/iio/imu/bno055/Kconfig"
config FXOS8700
diff --git a/drivers/iio/imu/Makefile b/drivers/iio/imu/Makefile
index 6eb6120347..7e2d7d5c3b 100644
--- a/drivers/iio/imu/Makefile
+++ b/drivers/iio/imu/Makefile
@@ -15,6 +15,7 @@ adis_lib-$(CONFIG_IIO_ADIS_LIB_BUFFER) += adis_buffer.o
obj-$(CONFIG_IIO_ADIS_LIB) += adis_lib.o
obj-y += bmi160/
+obj-y += bmi323/
obj-y += bno055/
obj-$(CONFIG_FXOS8700) += fxos8700_core.o
diff --git a/drivers/iio/imu/adis.c b/drivers/iio/imu/adis.c
index bc40240b29..495caf4ce8 100644
--- a/drivers/iio/imu/adis.c
+++ b/drivers/iio/imu/adis.c
@@ -44,8 +44,6 @@ int __adis_write_reg(struct adis *adis, unsigned int reg, unsigned int value,
.cs_change = 1,
.delay.value = adis->data->write_delay,
.delay.unit = SPI_DELAY_UNIT_USECS,
- .cs_change_delay.value = adis->data->cs_change_delay,
- .cs_change_delay.unit = SPI_DELAY_UNIT_USECS,
}, {
.tx_buf = adis->tx + 2,
.bits_per_word = 8,
@@ -53,8 +51,6 @@ int __adis_write_reg(struct adis *adis, unsigned int reg, unsigned int value,
.cs_change = 1,
.delay.value = adis->data->write_delay,
.delay.unit = SPI_DELAY_UNIT_USECS,
- .cs_change_delay.value = adis->data->cs_change_delay,
- .cs_change_delay.unit = SPI_DELAY_UNIT_USECS,
}, {
.tx_buf = adis->tx + 4,
.bits_per_word = 8,
@@ -62,8 +58,6 @@ int __adis_write_reg(struct adis *adis, unsigned int reg, unsigned int value,
.cs_change = 1,
.delay.value = adis->data->write_delay,
.delay.unit = SPI_DELAY_UNIT_USECS,
- .cs_change_delay.value = adis->data->cs_change_delay,
- .cs_change_delay.unit = SPI_DELAY_UNIT_USECS,
}, {
.tx_buf = adis->tx + 6,
.bits_per_word = 8,
@@ -144,8 +138,6 @@ int __adis_read_reg(struct adis *adis, unsigned int reg, unsigned int *val,
.cs_change = 1,
.delay.value = adis->data->write_delay,
.delay.unit = SPI_DELAY_UNIT_USECS,
- .cs_change_delay.value = adis->data->cs_change_delay,
- .cs_change_delay.unit = SPI_DELAY_UNIT_USECS,
}, {
.tx_buf = adis->tx + 2,
.bits_per_word = 8,
@@ -153,8 +145,6 @@ int __adis_read_reg(struct adis *adis, unsigned int reg, unsigned int *val,
.cs_change = 1,
.delay.value = adis->data->read_delay,
.delay.unit = SPI_DELAY_UNIT_USECS,
- .cs_change_delay.value = adis->data->cs_change_delay,
- .cs_change_delay.unit = SPI_DELAY_UNIT_USECS,
}, {
.tx_buf = adis->tx + 4,
.rx_buf = adis->rx,
@@ -163,8 +153,6 @@ int __adis_read_reg(struct adis *adis, unsigned int reg, unsigned int *val,
.cs_change = 1,
.delay.value = adis->data->read_delay,
.delay.unit = SPI_DELAY_UNIT_USECS,
- .cs_change_delay.value = adis->data->cs_change_delay,
- .cs_change_delay.unit = SPI_DELAY_UNIT_USECS,
}, {
.rx_buf = adis->rx + 2,
.bits_per_word = 8,
@@ -524,6 +512,12 @@ int adis_init(struct adis *adis, struct iio_dev *indio_dev,
}
mutex_init(&adis->state_lock);
+
+ if (!spi->cs_inactive.value) {
+ spi->cs_inactive.value = data->cs_change_delay;
+ spi->cs_inactive.unit = SPI_DELAY_UNIT_USECS;
+ }
+
adis->spi = spi;
adis->data = data;
iio_device_set_drvdata(indio_dev, adis);
diff --git a/drivers/iio/imu/bmi323/Kconfig b/drivers/iio/imu/bmi323/Kconfig
new file mode 100644
index 0000000000..ab37b28539
--- /dev/null
+++ b/drivers/iio/imu/bmi323/Kconfig
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# BMI323 IMU driver
+#
+
+config BMI323
+ tristate
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
+
+config BMI323_I2C
+ tristate "Bosch BMI323 I2C driver"
+ depends on I2C
+ select BMI323
+ select REGMAP_I2C
+ help
+ Enable support for the Bosch BMI323 6-Axis IMU connected to I2C
+ interface.
+
+ This driver can also be built as a module. If so, the module will be
+ called bmi323_i2c.
+
+config BMI323_SPI
+ tristate "Bosch BMI323 SPI driver"
+ depends on SPI
+ select BMI323
+ select REGMAP_SPI
+ help
+ Enable support for the Bosch BMI323 6-Axis IMU connected to SPI
+ interface.
+
+ This driver can also be built as a module. If so, the module will be
+ called bmi323_spi.
diff --git a/drivers/iio/imu/bmi323/Makefile b/drivers/iio/imu/bmi323/Makefile
new file mode 100644
index 0000000000..a6a6dc0207
--- /dev/null
+++ b/drivers/iio/imu/bmi323/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for Bosch BMI323 IMU
+#
+obj-$(CONFIG_BMI323) += bmi323_core.o
+obj-$(CONFIG_BMI323_I2C) += bmi323_i2c.o
+obj-$(CONFIG_BMI323_SPI) += bmi323_spi.o
diff --git a/drivers/iio/imu/bmi323/bmi323.h b/drivers/iio/imu/bmi323/bmi323.h
new file mode 100644
index 0000000000..dff126d416
--- /dev/null
+++ b/drivers/iio/imu/bmi323/bmi323.h
@@ -0,0 +1,209 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * IIO driver for Bosch BMI323 6-Axis IMU
+ *
+ * Copyright (C) 2023, Jagath Jog J <jagathjog1996@gmail.com>
+ */
+
+#ifndef _BMI323_H_
+#define _BMI323_H_
+
+#include <linux/bits.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#define BMI323_I2C_DUMMY 2
+#define BMI323_SPI_DUMMY 1
+
+/* Register map */
+
+#define BMI323_CHIP_ID_REG 0x00
+#define BMI323_CHIP_ID_VAL 0x0043
+#define BMI323_CHIP_ID_MSK GENMASK(7, 0)
+#define BMI323_ERR_REG 0x01
+#define BMI323_STATUS_REG 0x02
+#define BMI323_STATUS_POR_MSK BIT(0)
+
+/* Accelero/Gyro/Temp data registers */
+#define BMI323_ACCEL_X_REG 0x03
+#define BMI323_GYRO_X_REG 0x06
+#define BMI323_TEMP_REG 0x09
+#define BMI323_ALL_CHAN_MSK GENMASK(5, 0)
+
+/* Status registers */
+#define BMI323_STATUS_INT1_REG 0x0D
+#define BMI323_STATUS_INT2_REG 0x0E
+#define BMI323_STATUS_NOMOTION_MSK BIT(0)
+#define BMI323_STATUS_MOTION_MSK BIT(1)
+#define BMI323_STATUS_STP_WTR_MSK BIT(5)
+#define BMI323_STATUS_TAP_MSK BIT(8)
+#define BMI323_STATUS_ERROR_MSK BIT(10)
+#define BMI323_STATUS_TMP_DRDY_MSK BIT(11)
+#define BMI323_STATUS_GYR_DRDY_MSK BIT(12)
+#define BMI323_STATUS_ACC_DRDY_MSK BIT(13)
+#define BMI323_STATUS_ACC_GYR_DRDY_MSK GENMASK(13, 12)
+#define BMI323_STATUS_FIFO_WTRMRK_MSK BIT(14)
+#define BMI323_STATUS_FIFO_FULL_MSK BIT(15)
+
+/* Feature registers */
+#define BMI323_FEAT_IO0_REG 0x10
+#define BMI323_FEAT_IO0_XYZ_NOMOTION_MSK GENMASK(2, 0)
+#define BMI323_FEAT_IO0_XYZ_MOTION_MSK GENMASK(5, 3)
+#define BMI323_FEAT_XYZ_MSK GENMASK(2, 0)
+#define BMI323_FEAT_IO0_STP_CNT_MSK BIT(9)
+#define BMI323_FEAT_IO0_S_TAP_MSK BIT(12)
+#define BMI323_FEAT_IO0_D_TAP_MSK BIT(13)
+#define BMI323_FEAT_IO1_REG 0x11
+#define BMI323_FEAT_IO1_ERR_MSK GENMASK(3, 0)
+#define BMI323_FEAT_IO2_REG 0x12
+#define BMI323_FEAT_IO_STATUS_REG 0x14
+#define BMI323_FEAT_IO_STATUS_MSK BIT(0)
+#define BMI323_FEAT_ENG_POLL 2000
+#define BMI323_FEAT_ENG_TIMEOUT 10000
+
+/* FIFO registers */
+#define BMI323_FIFO_FILL_LEVEL_REG 0x15
+#define BMI323_FIFO_DATA_REG 0x16
+
+/* Accelero/Gyro config registers */
+#define BMI323_ACC_CONF_REG 0x20
+#define BMI323_GYRO_CONF_REG 0x21
+#define BMI323_ACC_GYRO_CONF_MODE_MSK GENMASK(14, 12)
+#define BMI323_ACC_GYRO_CONF_ODR_MSK GENMASK(3, 0)
+#define BMI323_ACC_GYRO_CONF_SCL_MSK GENMASK(6, 4)
+#define BMI323_ACC_GYRO_CONF_BW_MSK BIT(7)
+#define BMI323_ACC_GYRO_CONF_AVG_MSK GENMASK(10, 8)
+
+/* FIFO registers */
+#define BMI323_FIFO_WTRMRK_REG 0x35
+#define BMI323_FIFO_CONF_REG 0x36
+#define BMI323_FIFO_CONF_STP_FUL_MSK BIT(0)
+#define BMI323_FIFO_CONF_ACC_GYR_EN_MSK GENMASK(10, 9)
+#define BMI323_FIFO_ACC_GYR_MSK GENMASK(1, 0)
+#define BMI323_FIFO_CTRL_REG 0x37
+#define BMI323_FIFO_FLUSH_MSK BIT(0)
+
+/* Interrupt pin config registers */
+#define BMI323_IO_INT_CTR_REG 0x38
+#define BMI323_IO_INT1_LVL_MSK BIT(0)
+#define BMI323_IO_INT1_OD_MSK BIT(1)
+#define BMI323_IO_INT1_OP_EN_MSK BIT(2)
+#define BMI323_IO_INT1_LVL_OD_OP_MSK GENMASK(2, 0)
+#define BMI323_IO_INT2_LVL_MSK BIT(8)
+#define BMI323_IO_INT2_OD_MSK BIT(9)
+#define BMI323_IO_INT2_OP_EN_MSK BIT(10)
+#define BMI323_IO_INT2_LVL_OD_OP_MSK GENMASK(10, 8)
+#define BMI323_IO_INT_CONF_REG 0x39
+#define BMI323_IO_INT_LTCH_MSK BIT(0)
+#define BMI323_INT_MAP1_REG 0x3A
+#define BMI323_INT_MAP2_REG 0x3B
+#define BMI323_NOMOTION_MSK GENMASK(1, 0)
+#define BMI323_MOTION_MSK GENMASK(3, 2)
+#define BMI323_STEP_CNT_MSK GENMASK(11, 10)
+#define BMI323_TAP_MSK GENMASK(1, 0)
+#define BMI323_TMP_DRDY_MSK GENMASK(7, 6)
+#define BMI323_GYR_DRDY_MSK GENMASK(9, 8)
+#define BMI323_ACC_DRDY_MSK GENMASK(11, 10)
+#define BMI323_FIFO_WTRMRK_MSK GENMASK(13, 12)
+#define BMI323_FIFO_FULL_MSK GENMASK(15, 14)
+
+/* Feature registers */
+#define BMI323_FEAT_CTRL_REG 0x40
+#define BMI323_FEAT_ENG_EN_MSK BIT(0)
+#define BMI323_FEAT_DATA_ADDR 0x41
+#define BMI323_FEAT_DATA_TX 0x42
+#define BMI323_FEAT_DATA_STATUS 0x43
+#define BMI323_FEAT_DATA_TX_RDY_MSK BIT(1)
+#define BMI323_FEAT_EVNT_EXT_REG 0x47
+#define BMI323_FEAT_EVNT_EXT_S_MSK BIT(3)
+#define BMI323_FEAT_EVNT_EXT_D_MSK BIT(4)
+
+#define BMI323_CMD_REG 0x7E
+#define BMI323_RST_VAL 0xDEAF
+#define BMI323_CFG_RES_REG 0x7F
+
+/* Extended registers */
+#define BMI323_GEN_SET1_REG 0x02
+#define BMI323_GEN_SET1_MODE_MSK BIT(0)
+#define BMI323_GEN_HOLD_DUR_MSK GENMASK(4, 1)
+
+/* Any Motion/No Motion config registers */
+#define BMI323_ANYMO1_REG 0x05
+#define BMI323_NOMO1_REG 0x08
+#define BMI323_MO2_OFFSET 0x01
+#define BMI323_MO3_OFFSET 0x02
+#define BMI323_MO1_REF_UP_MSK BIT(12)
+#define BMI323_MO1_SLOPE_TH_MSK GENMASK(11, 0)
+#define BMI323_MO2_HYSTR_MSK GENMASK(9, 0)
+#define BMI323_MO3_DURA_MSK GENMASK(12, 0)
+
+/* Step counter config registers */
+#define BMI323_STEP_SC1_REG 0x10
+#define BMI323_STEP_SC1_WTRMRK_MSK GENMASK(9, 0)
+#define BMI323_STEP_SC1_RST_CNT_MSK BIT(10)
+#define BMI323_STEP_SC1_REG 0x10
+#define BMI323_STEP_LEN 2
+
+/* Tap gesture config registers */
+#define BMI323_TAP1_REG 0x1E
+#define BMI323_TAP1_AXIS_SEL_MSK GENMASK(1, 0)
+#define BMI323_AXIS_XYZ_MSK GENMASK(1, 0)
+#define BMI323_TAP1_TIMOUT_MSK BIT(2)
+#define BMI323_TAP1_MAX_PEAKS_MSK GENMASK(5, 3)
+#define BMI323_TAP1_MODE_MSK GENMASK(7, 6)
+#define BMI323_TAP2_REG 0x1F
+#define BMI323_TAP2_THRES_MSK GENMASK(9, 0)
+#define BMI323_TAP2_MAX_DUR_MSK GENMASK(15, 10)
+#define BMI323_TAP3_REG 0x20
+#define BMI323_TAP3_QUIET_TIM_MSK GENMASK(15, 12)
+#define BMI323_TAP3_QT_BW_TAP_MSK GENMASK(11, 8)
+#define BMI323_TAP3_QT_AFT_GES_MSK GENMASK(15, 12)
+
+#define BMI323_MOTION_THRES_SCALE 512
+#define BMI323_MOTION_HYSTR_SCALE 512
+#define BMI323_MOTION_DURAT_SCALE 50
+#define BMI323_TAP_THRES_SCALE 512
+#define BMI323_DUR_BW_TAP_SCALE 200
+#define BMI323_QUITE_TIM_GES_SCALE 25
+#define BMI323_MAX_GES_DUR_SCALE 25
+
+/*
+ * The formula to calculate temperature in C.
+ * See datasheet section 6.1.1, Register Map Overview
+ *
+ * T_C = (temp_raw / 512) + 23
+ */
+#define BMI323_TEMP_OFFSET 11776
+#define BMI323_TEMP_SCALE 1953125
+
+/*
+ * The BMI323 features a FIFO with a capacity of 2048 bytes. Each frame
+ * consists of accelerometer (X, Y, Z) data and gyroscope (X, Y, Z) data,
+ * totaling 6 words or 12 bytes. The FIFO buffer can hold a total of
+ * 170 frames.
+ *
+ * If a watermark interrupt is configured for 170 frames, the interrupt will
+ * trigger when the FIFO reaches 169 frames, so limit the maximum watermark
+ * level to 169 frames. In terms of data, 169 frames would equal 1014 bytes,
+ * which is approximately 2 frames before the FIFO reaches its full capacity.
+ * See datasheet section 5.7.3 FIFO Buffer Interrupts
+ */
+#define BMI323_BYTES_PER_SAMPLE 2
+#define BMI323_FIFO_LENGTH_IN_BYTES 2048
+#define BMI323_FIFO_FRAME_LENGTH 6
+#define BMI323_FIFO_FULL_IN_FRAMES \
+ ((BMI323_FIFO_LENGTH_IN_BYTES / \
+ (BMI323_BYTES_PER_SAMPLE * BMI323_FIFO_FRAME_LENGTH)) - 1)
+#define BMI323_FIFO_FULL_IN_WORDS \
+ (BMI323_FIFO_FULL_IN_FRAMES * BMI323_FIFO_FRAME_LENGTH)
+
+#define BMI323_INT_MICRO_TO_RAW(val, val2, scale) ((val) * (scale) + \
+ ((val2) * (scale)) / MEGA)
+
+#define BMI323_RAW_TO_MICRO(raw, scale) ((((raw) % (scale)) * MEGA) / scale)
+
+struct device;
+int bmi323_core_probe(struct device *dev);
+extern const struct regmap_config bmi323_regmap_config;
+
+#endif
diff --git a/drivers/iio/imu/bmi323/bmi323_core.c b/drivers/iio/imu/bmi323/bmi323_core.c
new file mode 100644
index 0000000000..183af48282
--- /dev/null
+++ b/drivers/iio/imu/bmi323/bmi323_core.c
@@ -0,0 +1,2139 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * IIO core driver for Bosch BMI323 6-Axis IMU.
+ *
+ * Copyright (C) 2023, Jagath Jog J <jagathjog1996@gmail.com>
+ *
+ * Datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi323-ds000.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/units.h>
+
+#include <asm/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.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "bmi323.h"
+
+enum bmi323_sensor_type {
+ BMI323_ACCEL,
+ BMI323_GYRO,
+ BMI323_SENSORS_CNT,
+};
+
+enum bmi323_opr_mode {
+ ACC_GYRO_MODE_DISABLE = 0x00,
+ GYRO_DRIVE_MODE_ENABLED = 0x01,
+ ACC_GYRO_MODE_DUTYCYCLE = 0x03,
+ ACC_GYRO_MODE_CONTINOUS = 0x04,
+ ACC_GYRO_MODE_HIGH_PERF = 0x07,
+};
+
+enum bmi323_state {
+ BMI323_IDLE,
+ BMI323_BUFFER_DRDY_TRIGGERED,
+ BMI323_BUFFER_FIFO,
+};
+
+enum bmi323_irq_pin {
+ BMI323_IRQ_DISABLED,
+ BMI323_IRQ_INT1,
+ BMI323_IRQ_INT2,
+};
+
+enum bmi323_3db_bw {
+ BMI323_BW_ODR_BY_2,
+ BMI323_BW_ODR_BY_4,
+};
+
+enum bmi323_scan {
+ BMI323_ACCEL_X,
+ BMI323_ACCEL_Y,
+ BMI323_ACCEL_Z,
+ BMI323_GYRO_X,
+ BMI323_GYRO_Y,
+ BMI323_GYRO_Z,
+ BMI323_CHAN_MAX
+};
+
+struct bmi323_hw {
+ u8 data;
+ u8 config;
+ const int (*scale_table)[2];
+ int scale_table_len;
+};
+
+/*
+ * The accelerometer supports +-2G/4G/8G/16G ranges, and the resolution of
+ * each sample is 16 bits, signed.
+ * At +-8G the scale can calculated by
+ * ((8 + 8) * 9.80665 / (2^16 - 1)) * 10^6 = 2394.23819 scale in micro
+ *
+ */
+static const int bmi323_accel_scale[][2] = {
+ { 0, 598 },
+ { 0, 1197 },
+ { 0, 2394 },
+ { 0, 4788 },
+};
+
+static const int bmi323_gyro_scale[][2] = {
+ { 0, 66 },
+ { 0, 133 },
+ { 0, 266 },
+ { 0, 532 },
+ { 0, 1065 },
+};
+
+static const int bmi323_accel_gyro_avrg[] = {0, 2, 4, 8, 16, 32, 64};
+
+static const struct bmi323_hw bmi323_hw[2] = {
+ [BMI323_ACCEL] = {
+ .data = BMI323_ACCEL_X_REG,
+ .config = BMI323_ACC_CONF_REG,
+ .scale_table = bmi323_accel_scale,
+ .scale_table_len = ARRAY_SIZE(bmi323_accel_scale),
+ },
+ [BMI323_GYRO] = {
+ .data = BMI323_GYRO_X_REG,
+ .config = BMI323_GYRO_CONF_REG,
+ .scale_table = bmi323_gyro_scale,
+ .scale_table_len = ARRAY_SIZE(bmi323_gyro_scale),
+ },
+};
+
+struct bmi323_data {
+ struct device *dev;
+ struct regmap *regmap;
+ struct iio_mount_matrix orientation;
+ enum bmi323_irq_pin irq_pin;
+ struct iio_trigger *trig;
+ bool drdy_trigger_enabled;
+ enum bmi323_state state;
+ s64 fifo_tstamp, old_fifo_tstamp;
+ u32 odrns[BMI323_SENSORS_CNT];
+ u32 odrhz[BMI323_SENSORS_CNT];
+ unsigned int feature_events;
+
+ /*
+ * Lock to protect the members of device's private data from concurrent
+ * access and also to serialize the access of extended registers.
+ * See bmi323_write_ext_reg(..) for more info.
+ */
+ struct mutex mutex;
+ int watermark;
+ __le16 fifo_buff[BMI323_FIFO_FULL_IN_WORDS] __aligned(IIO_DMA_MINALIGN);
+ struct {
+ __le16 channels[BMI323_CHAN_MAX];
+ s64 ts __aligned(8);
+ } buffer;
+ __le16 steps_count[BMI323_STEP_LEN];
+};
+
+static const struct iio_mount_matrix *
+bmi323_get_mount_matrix(const struct iio_dev *idev,
+ const struct iio_chan_spec *chan)
+{
+ struct bmi323_data *data = iio_priv(idev);
+
+ return &data->orientation;
+}
+
+static const struct iio_chan_spec_ext_info bmi323_ext_info[] = {
+ IIO_MOUNT_MATRIX(IIO_SHARED_BY_TYPE, bmi323_get_mount_matrix),
+ { }
+};
+
+static const struct iio_event_spec bmi323_step_wtrmrk_event = {
+ .type = IIO_EV_TYPE_CHANGE,
+ .dir = IIO_EV_DIR_NONE,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE),
+};
+
+static const struct iio_event_spec bmi323_accel_event[] = {
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_FALLING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_HYSTERESIS) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_MAG,
+ .dir = IIO_EV_DIR_RISING,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_PERIOD) |
+ BIT(IIO_EV_INFO_HYSTERESIS) |
+ BIT(IIO_EV_INFO_ENABLE),
+ },
+ {
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_SINGLETAP,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT),
+ },
+ {
+ .type = IIO_EV_TYPE_GESTURE,
+ .dir = IIO_EV_DIR_DOUBLETAP,
+ .mask_shared_by_type = BIT(IIO_EV_INFO_ENABLE) |
+ BIT(IIO_EV_INFO_VALUE) |
+ BIT(IIO_EV_INFO_RESET_TIMEOUT) |
+ BIT(IIO_EV_INFO_TAP2_MIN_DELAY),
+ },
+};
+
+#define BMI323_ACCEL_CHANNEL(_type, _axis, _index) { \
+ .type = _type, \
+ .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), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .scan_index = _index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+ .ext_info = bmi323_ext_info, \
+ .event_spec = bmi323_accel_event, \
+ .num_event_specs = ARRAY_SIZE(bmi323_accel_event), \
+}
+
+#define BMI323_GYRO_CHANNEL(_type, _axis, _index) { \
+ .type = _type, \
+ .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), \
+ .info_mask_shared_by_type_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .scan_index = _index, \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_LE, \
+ }, \
+ .ext_info = bmi323_ext_info, \
+}
+
+static const struct iio_chan_spec bmi323_channels[] = {
+ BMI323_ACCEL_CHANNEL(IIO_ACCEL, X, BMI323_ACCEL_X),
+ BMI323_ACCEL_CHANNEL(IIO_ACCEL, Y, BMI323_ACCEL_Y),
+ BMI323_ACCEL_CHANNEL(IIO_ACCEL, Z, BMI323_ACCEL_Z),
+ BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, X, BMI323_GYRO_X),
+ BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Y, BMI323_GYRO_Y),
+ BMI323_GYRO_CHANNEL(IIO_ANGL_VEL, Z, BMI323_GYRO_Z),
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .scan_index = -1,
+ },
+ {
+ .type = IIO_STEPS,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_ENABLE),
+ .scan_index = -1,
+ .event_spec = &bmi323_step_wtrmrk_event,
+ .num_event_specs = 1,
+
+ },
+ IIO_CHAN_SOFT_TIMESTAMP(BMI323_CHAN_MAX),
+};
+
+static const int bmi323_acc_gyro_odr[][2] = {
+ { 0, 781250 },
+ { 1, 562500 },
+ { 3, 125000 },
+ { 6, 250000 },
+ { 12, 500000 },
+ { 25, 0 },
+ { 50, 0 },
+ { 100, 0 },
+ { 200, 0 },
+ { 400, 0 },
+ { 800, 0 },
+};
+
+static const int bmi323_acc_gyro_odrns[] = {
+ 1280 * MEGA,
+ 640 * MEGA,
+ 320 * MEGA,
+ 160 * MEGA,
+ 80 * MEGA,
+ 40 * MEGA,
+ 20 * MEGA,
+ 10 * MEGA,
+ 5 * MEGA,
+ 2500 * KILO,
+ 1250 * KILO,
+};
+
+static enum bmi323_sensor_type bmi323_iio_to_sensor(enum iio_chan_type iio_type)
+{
+ switch (iio_type) {
+ case IIO_ACCEL:
+ return BMI323_ACCEL;
+ case IIO_ANGL_VEL:
+ return BMI323_GYRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi323_set_mode(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor,
+ enum bmi323_opr_mode mode)
+{
+ guard(mutex)(&data->mutex);
+ return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
+ BMI323_ACC_GYRO_CONF_MODE_MSK,
+ FIELD_PREP(BMI323_ACC_GYRO_CONF_MODE_MSK,
+ mode));
+}
+
+/*
+ * When writing data to extended register there must be no communication to
+ * any other register before write transaction is complete.
+ * See datasheet section 6.2 Extended Register Map Description.
+ */
+static int bmi323_write_ext_reg(struct bmi323_data *data, unsigned int ext_addr,
+ unsigned int ext_data)
+{
+ int ret, feature_status;
+
+ ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS,
+ &feature_status);
+ if (ret)
+ return ret;
+
+ if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status))
+ return -EBUSY;
+
+ ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr);
+ if (ret)
+ return ret;
+
+ return regmap_write(data->regmap, BMI323_FEAT_DATA_TX, ext_data);
+}
+
+/*
+ * When reading data from extended register there must be no communication to
+ * any other register before read transaction is complete.
+ * See datasheet section 6.2 Extended Register Map Description.
+ */
+static int bmi323_read_ext_reg(struct bmi323_data *data, unsigned int ext_addr,
+ unsigned int *ext_data)
+{
+ int ret, feature_status;
+
+ ret = regmap_read(data->regmap, BMI323_FEAT_DATA_STATUS,
+ &feature_status);
+ if (ret)
+ return ret;
+
+ if (!FIELD_GET(BMI323_FEAT_DATA_TX_RDY_MSK, feature_status))
+ return -EBUSY;
+
+ ret = regmap_write(data->regmap, BMI323_FEAT_DATA_ADDR, ext_addr);
+ if (ret)
+ return ret;
+
+ return regmap_read(data->regmap, BMI323_FEAT_DATA_TX, ext_data);
+}
+
+static int bmi323_update_ext_reg(struct bmi323_data *data,
+ unsigned int ext_addr,
+ unsigned int mask, unsigned int ext_data)
+{
+ unsigned int value;
+ int ret;
+
+ ret = bmi323_read_ext_reg(data, ext_addr, &value);
+ if (ret)
+ return ret;
+
+ set_mask_bits(&value, mask, ext_data);
+
+ return bmi323_write_ext_reg(data, ext_addr, value);
+}
+
+static int bmi323_get_error_status(struct bmi323_data *data)
+{
+ int error, ret;
+
+ guard(mutex)(&data->mutex);
+ ret = regmap_read(data->regmap, BMI323_ERR_REG, &error);
+ if (ret)
+ return ret;
+
+ if (error)
+ dev_err(data->dev, "Sensor error 0x%x\n", error);
+
+ return error;
+}
+
+static int bmi323_feature_engine_events(struct bmi323_data *data,
+ const unsigned int event_mask,
+ bool state)
+{
+ unsigned int value;
+ int ret;
+
+ ret = regmap_read(data->regmap, BMI323_FEAT_IO0_REG, &value);
+ if (ret)
+ return ret;
+
+ /* Register must be cleared before changing an active config */
+ ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, 0);
+ if (ret)
+ return ret;
+
+ if (state)
+ value |= event_mask;
+ else
+ value &= ~event_mask;
+
+ ret = regmap_write(data->regmap, BMI323_FEAT_IO0_REG, value);
+ if (ret)
+ return ret;
+
+ return regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG,
+ BMI323_FEAT_IO_STATUS_MSK);
+}
+
+static int bmi323_step_wtrmrk_en(struct bmi323_data *data, int state)
+{
+ enum bmi323_irq_pin step_irq;
+ int ret;
+
+ guard(mutex)(&data->mutex);
+ if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events))
+ return -EINVAL;
+
+ if (state)
+ step_irq = data->irq_pin;
+ else
+ step_irq = BMI323_IRQ_DISABLED;
+
+ ret = bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG,
+ BMI323_STEP_SC1_WTRMRK_MSK,
+ FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK,
+ state ? 1 : 0));
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG,
+ BMI323_STEP_CNT_MSK,
+ FIELD_PREP(BMI323_STEP_CNT_MSK, step_irq));
+}
+
+static int bmi323_motion_config_reg(enum iio_event_direction dir)
+{
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ return BMI323_ANYMO1_REG;
+ case IIO_EV_DIR_FALLING:
+ return BMI323_NOMO1_REG;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi323_motion_event_en(struct bmi323_data *data,
+ enum iio_event_direction dir, int state)
+{
+ unsigned int state_value = state ? BMI323_FEAT_XYZ_MSK : 0;
+ int config, ret, msk, raw, field_value;
+ enum bmi323_irq_pin motion_irq;
+ int irq_msk, irq_field_val;
+
+ if (state)
+ motion_irq = data->irq_pin;
+ else
+ motion_irq = BMI323_IRQ_DISABLED;
+
+ switch (dir) {
+ case IIO_EV_DIR_RISING:
+ msk = BMI323_FEAT_IO0_XYZ_MOTION_MSK;
+ raw = 512;
+ config = BMI323_ANYMO1_REG;
+ irq_msk = BMI323_MOTION_MSK;
+ irq_field_val = FIELD_PREP(BMI323_MOTION_MSK, motion_irq);
+ field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_MOTION_MSK,
+ state_value);
+ break;
+ case IIO_EV_DIR_FALLING:
+ msk = BMI323_FEAT_IO0_XYZ_NOMOTION_MSK;
+ raw = 0;
+ config = BMI323_NOMO1_REG;
+ irq_msk = BMI323_NOMOTION_MSK;
+ irq_field_val = FIELD_PREP(BMI323_NOMOTION_MSK, motion_irq);
+ field_value = FIELD_PREP(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK,
+ state_value);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ guard(mutex)(&data->mutex);
+ ret = bmi323_feature_engine_events(data, msk, state);
+ if (ret)
+ return ret;
+
+ ret = bmi323_update_ext_reg(data, config,
+ BMI323_MO1_REF_UP_MSK,
+ FIELD_PREP(BMI323_MO1_REF_UP_MSK, 0));
+ if (ret)
+ return ret;
+
+ /* Set initial value to avoid interrupts while enabling*/
+ ret = bmi323_update_ext_reg(data, config,
+ BMI323_MO1_SLOPE_TH_MSK,
+ FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK, raw));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, BMI323_INT_MAP1_REG, irq_msk,
+ irq_field_val);
+ if (ret)
+ return ret;
+
+ set_mask_bits(&data->feature_events, msk, field_value);
+
+ return 0;
+}
+
+static int bmi323_tap_event_en(struct bmi323_data *data,
+ enum iio_event_direction dir, int state)
+{
+ enum bmi323_irq_pin tap_irq;
+ int ret, tap_enabled;
+
+ guard(mutex)(&data->mutex);
+
+ if (data->odrhz[BMI323_ACCEL] < 200) {
+ dev_err(data->dev, "Invalid accelerometer parameter\n");
+ return -EINVAL;
+ }
+
+ switch (dir) {
+ case IIO_EV_DIR_SINGLETAP:
+ ret = bmi323_feature_engine_events(data,
+ BMI323_FEAT_IO0_S_TAP_MSK,
+ state);
+ if (ret)
+ return ret;
+
+ set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_S_TAP_MSK,
+ FIELD_PREP(BMI323_FEAT_IO0_S_TAP_MSK, state));
+ break;
+ case IIO_EV_DIR_DOUBLETAP:
+ ret = bmi323_feature_engine_events(data,
+ BMI323_FEAT_IO0_D_TAP_MSK,
+ state);
+ if (ret)
+ return ret;
+
+ set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_D_TAP_MSK,
+ FIELD_PREP(BMI323_FEAT_IO0_D_TAP_MSK, state));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tap_enabled = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK |
+ BMI323_FEAT_IO0_D_TAP_MSK,
+ data->feature_events);
+
+ if (tap_enabled)
+ tap_irq = data->irq_pin;
+ else
+ tap_irq = BMI323_IRQ_DISABLED;
+
+ ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
+ BMI323_TAP_MSK,
+ FIELD_PREP(BMI323_TAP_MSK, tap_irq));
+ if (ret)
+ return ret;
+
+ if (!state)
+ return 0;
+
+ ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG,
+ BMI323_TAP1_MAX_PEAKS_MSK,
+ FIELD_PREP(BMI323_TAP1_MAX_PEAKS_MSK,
+ 0x04));
+ if (ret)
+ return ret;
+
+ ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG,
+ BMI323_TAP1_AXIS_SEL_MSK,
+ FIELD_PREP(BMI323_TAP1_AXIS_SEL_MSK,
+ BMI323_AXIS_XYZ_MSK));
+ if (ret)
+ return ret;
+
+ return bmi323_update_ext_reg(data, BMI323_TAP1_REG,
+ BMI323_TAP1_TIMOUT_MSK,
+ FIELD_PREP(BMI323_TAP1_TIMOUT_MSK,
+ 0));
+}
+
+static ssize_t in_accel_gesture_tap_wait_dur_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmi323_data *data = iio_priv(indio_dev);
+ unsigned int reg_value, raw;
+ int ret, val[2];
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG, &reg_value);
+ if (ret)
+ return ret;
+ }
+
+ raw = FIELD_GET(BMI323_TAP2_MAX_DUR_MSK, reg_value);
+ val[0] = raw / BMI323_MAX_GES_DUR_SCALE;
+ val[1] = BMI323_RAW_TO_MICRO(raw, BMI323_MAX_GES_DUR_SCALE);
+
+ return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(val),
+ val);
+}
+
+static ssize_t in_accel_gesture_tap_wait_dur_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmi323_data *data = iio_priv(indio_dev);
+ int ret, val_int, val_fract, raw;
+
+ ret = iio_str_to_fixpoint(buf, 100000, &val_int, &val_fract);
+ if (ret)
+ return ret;
+
+ raw = BMI323_INT_MICRO_TO_RAW(val_int, val_fract,
+ BMI323_MAX_GES_DUR_SCALE);
+ if (!in_range(raw, 0, 64))
+ return -EINVAL;
+
+ guard(mutex)(&data->mutex);
+ ret = bmi323_update_ext_reg(data, BMI323_TAP2_REG,
+ BMI323_TAP2_MAX_DUR_MSK,
+ FIELD_PREP(BMI323_TAP2_MAX_DUR_MSK, raw));
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+/*
+ * Maximum duration from first tap within the second tap is expected to happen.
+ * This timeout is applicable only if gesture_tap_wait_timeout is enabled.
+ */
+static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_dur, 0);
+
+static ssize_t in_accel_gesture_tap_wait_timeout_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmi323_data *data = iio_priv(indio_dev);
+ unsigned int reg_value, raw;
+ int ret;
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = bmi323_read_ext_reg(data, BMI323_TAP1_REG, &reg_value);
+ if (ret)
+ return ret;
+ }
+
+ raw = FIELD_GET(BMI323_TAP1_TIMOUT_MSK, reg_value);
+
+ return iio_format_value(buf, IIO_VAL_INT, 1, &raw);
+}
+
+static ssize_t in_accel_gesture_tap_wait_timeout_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmi323_data *data = iio_priv(indio_dev);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&data->mutex);
+ ret = bmi323_update_ext_reg(data, BMI323_TAP1_REG,
+ BMI323_TAP1_TIMOUT_MSK,
+ FIELD_PREP(BMI323_TAP1_TIMOUT_MSK, val));
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+/* Enable/disable gesture confirmation with wait time */
+static IIO_DEVICE_ATTR_RW(in_accel_gesture_tap_wait_timeout, 0);
+
+static IIO_CONST_ATTR(in_accel_gesture_tap_wait_dur_available,
+ "[0.0 0.04 2.52]");
+
+static IIO_CONST_ATTR(in_accel_gesture_doubletap_tap2_min_delay_available,
+ "[0.005 0.005 0.075]");
+
+static IIO_CONST_ATTR(in_accel_gesture_tap_reset_timeout_available,
+ "[0.04 0.04 0.6]");
+
+static IIO_CONST_ATTR(in_accel_gesture_tap_value_available, "[0.0 0.002 1.99]");
+
+static IIO_CONST_ATTR(in_accel_mag_value_available, "[0.0 0.002 7.99]");
+
+static IIO_CONST_ATTR(in_accel_mag_period_available, "[0.0 0.02 162.0]");
+
+static IIO_CONST_ATTR(in_accel_mag_hysteresis_available, "[0.0 0.002 1.99]");
+
+static struct attribute *bmi323_event_attributes[] = {
+ &iio_const_attr_in_accel_gesture_tap_value_available.dev_attr.attr,
+ &iio_const_attr_in_accel_gesture_tap_reset_timeout_available.dev_attr.attr,
+ &iio_const_attr_in_accel_gesture_doubletap_tap2_min_delay_available.dev_attr.attr,
+ &iio_const_attr_in_accel_gesture_tap_wait_dur_available.dev_attr.attr,
+ &iio_dev_attr_in_accel_gesture_tap_wait_timeout.dev_attr.attr,
+ &iio_dev_attr_in_accel_gesture_tap_wait_dur.dev_attr.attr,
+ &iio_const_attr_in_accel_mag_value_available.dev_attr.attr,
+ &iio_const_attr_in_accel_mag_period_available.dev_attr.attr,
+ &iio_const_attr_in_accel_mag_hysteresis_available.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group bmi323_event_attribute_group = {
+ .attrs = bmi323_event_attributes,
+};
+
+static int bmi323_write_event_config(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ enum iio_event_type type,
+ enum iio_event_direction dir, int state)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+
+ switch (type) {
+ case IIO_EV_TYPE_MAG:
+ return bmi323_motion_event_en(data, dir, state);
+ case IIO_EV_TYPE_GESTURE:
+ return bmi323_tap_event_en(data, dir, state);
+ case IIO_EV_TYPE_CHANGE:
+ return bmi323_step_wtrmrk_en(data, state);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi323_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 bmi323_data *data = iio_priv(indio_dev);
+ int ret, value, reg_val;
+
+ guard(mutex)(&data->mutex);
+
+ switch (chan->type) {
+ case IIO_ACCEL:
+ switch (dir) {
+ case IIO_EV_DIR_SINGLETAP:
+ ret = FIELD_GET(BMI323_FEAT_IO0_S_TAP_MSK,
+ data->feature_events);
+ break;
+ case IIO_EV_DIR_DOUBLETAP:
+ ret = FIELD_GET(BMI323_FEAT_IO0_D_TAP_MSK,
+ data->feature_events);
+ break;
+ case IIO_EV_DIR_RISING:
+ value = FIELD_GET(BMI323_FEAT_IO0_XYZ_MOTION_MSK,
+ data->feature_events);
+ ret = value ? 1 : 0;
+ break;
+ case IIO_EV_DIR_FALLING:
+ value = FIELD_GET(BMI323_FEAT_IO0_XYZ_NOMOTION_MSK,
+ data->feature_events);
+ ret = value ? 1 : 0;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+ case IIO_STEPS:
+ ret = regmap_read(data->regmap, BMI323_INT_MAP1_REG, &reg_val);
+ if (ret)
+ return ret;
+
+ return FIELD_GET(BMI323_STEP_CNT_MSK, reg_val) ? 1 : 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi323_write_event_value(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 bmi323_data *data = iio_priv(indio_dev);
+ unsigned int raw;
+ int reg;
+
+ guard(mutex)(&data->mutex);
+
+ switch (type) {
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (!in_range(val, 0, 2))
+ return -EINVAL;
+
+ raw = BMI323_INT_MICRO_TO_RAW(val, val2,
+ BMI323_TAP_THRES_SCALE);
+
+ return bmi323_update_ext_reg(data, BMI323_TAP2_REG,
+ BMI323_TAP2_THRES_MSK,
+ FIELD_PREP(BMI323_TAP2_THRES_MSK,
+ raw));
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ if (val || !in_range(val2, 40000, 560001))
+ return -EINVAL;
+
+ raw = BMI323_INT_MICRO_TO_RAW(val, val2,
+ BMI323_QUITE_TIM_GES_SCALE);
+
+ return bmi323_update_ext_reg(data, BMI323_TAP3_REG,
+ BMI323_TAP3_QT_AFT_GES_MSK,
+ FIELD_PREP(BMI323_TAP3_QT_AFT_GES_MSK,
+ raw));
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ if (val || !in_range(val2, 5000, 70001))
+ return -EINVAL;
+
+ raw = BMI323_INT_MICRO_TO_RAW(val, val2,
+ BMI323_DUR_BW_TAP_SCALE);
+
+ return bmi323_update_ext_reg(data, BMI323_TAP3_REG,
+ BMI323_TAP3_QT_BW_TAP_MSK,
+ FIELD_PREP(BMI323_TAP3_QT_BW_TAP_MSK,
+ raw));
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_MAG:
+ reg = bmi323_motion_config_reg(dir);
+ if (reg < 0)
+ return -EINVAL;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ if (!in_range(val, 0, 8))
+ return -EINVAL;
+
+ raw = BMI323_INT_MICRO_TO_RAW(val, val2,
+ BMI323_MOTION_THRES_SCALE);
+
+ return bmi323_update_ext_reg(data, reg,
+ BMI323_MO1_SLOPE_TH_MSK,
+ FIELD_PREP(BMI323_MO1_SLOPE_TH_MSK,
+ raw));
+ case IIO_EV_INFO_PERIOD:
+ if (!in_range(val, 0, 163))
+ return -EINVAL;
+
+ raw = BMI323_INT_MICRO_TO_RAW(val, val2,
+ BMI323_MOTION_DURAT_SCALE);
+
+ return bmi323_update_ext_reg(data,
+ reg + BMI323_MO3_OFFSET,
+ BMI323_MO3_DURA_MSK,
+ FIELD_PREP(BMI323_MO3_DURA_MSK,
+ raw));
+ case IIO_EV_INFO_HYSTERESIS:
+ if (!in_range(val, 0, 2))
+ return -EINVAL;
+
+ raw = BMI323_INT_MICRO_TO_RAW(val, val2,
+ BMI323_MOTION_HYSTR_SCALE);
+
+ return bmi323_update_ext_reg(data,
+ reg + BMI323_MO2_OFFSET,
+ BMI323_MO2_HYSTR_MSK,
+ FIELD_PREP(BMI323_MO2_HYSTR_MSK,
+ raw));
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_CHANGE:
+ if (!in_range(val, 0, 20461))
+ return -EINVAL;
+
+ raw = val / 20;
+ return bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG,
+ BMI323_STEP_SC1_WTRMRK_MSK,
+ FIELD_PREP(BMI323_STEP_SC1_WTRMRK_MSK,
+ raw));
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi323_read_event_value(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 bmi323_data *data = iio_priv(indio_dev);
+ unsigned int raw, reg_value;
+ int ret, reg;
+
+ guard(mutex)(&data->mutex);
+
+ switch (type) {
+ case IIO_EV_TYPE_GESTURE:
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = bmi323_read_ext_reg(data, BMI323_TAP2_REG,
+ &reg_value);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMI323_TAP2_THRES_MSK, reg_value);
+ *val = raw / BMI323_TAP_THRES_SCALE;
+ *val2 = BMI323_RAW_TO_MICRO(raw, BMI323_TAP_THRES_SCALE);
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_EV_INFO_RESET_TIMEOUT:
+ ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG,
+ &reg_value);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMI323_TAP3_QT_AFT_GES_MSK, reg_value);
+ *val = 0;
+ *val2 = BMI323_RAW_TO_MICRO(raw,
+ BMI323_QUITE_TIM_GES_SCALE);
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_EV_INFO_TAP2_MIN_DELAY:
+ ret = bmi323_read_ext_reg(data, BMI323_TAP3_REG,
+ &reg_value);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMI323_TAP3_QT_BW_TAP_MSK, reg_value);
+ *val = 0;
+ *val2 = BMI323_RAW_TO_MICRO(raw,
+ BMI323_DUR_BW_TAP_SCALE);
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_MAG:
+ reg = bmi323_motion_config_reg(dir);
+ if (reg < 0)
+ return -EINVAL;
+
+ switch (info) {
+ case IIO_EV_INFO_VALUE:
+ ret = bmi323_read_ext_reg(data, reg, &reg_value);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMI323_MO1_SLOPE_TH_MSK, reg_value);
+ *val = raw / BMI323_MOTION_THRES_SCALE;
+ *val2 = BMI323_RAW_TO_MICRO(raw,
+ BMI323_MOTION_THRES_SCALE);
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_EV_INFO_PERIOD:
+ ret = bmi323_read_ext_reg(data,
+ reg + BMI323_MO3_OFFSET,
+ &reg_value);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMI323_MO3_DURA_MSK, reg_value);
+ *val = raw / BMI323_MOTION_DURAT_SCALE;
+ *val2 = BMI323_RAW_TO_MICRO(raw,
+ BMI323_MOTION_DURAT_SCALE);
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_EV_INFO_HYSTERESIS:
+ ret = bmi323_read_ext_reg(data,
+ reg + BMI323_MO2_OFFSET,
+ &reg_value);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMI323_MO2_HYSTR_MSK, reg_value);
+ *val = raw / BMI323_MOTION_HYSTR_SCALE;
+ *val2 = BMI323_RAW_TO_MICRO(raw,
+ BMI323_MOTION_HYSTR_SCALE);
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_EV_TYPE_CHANGE:
+ ret = bmi323_read_ext_reg(data, BMI323_STEP_SC1_REG,
+ &reg_value);
+ if (ret)
+ return ret;
+
+ raw = FIELD_GET(BMI323_STEP_SC1_WTRMRK_MSK, reg_value);
+ *val = raw * 20;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int __bmi323_fifo_flush(struct iio_dev *indio_dev)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+ int i, ret, fifo_lvl, frame_count, bit, index;
+ __le16 *frame, *pchannels;
+ u64 sample_period;
+ s64 tstamp;
+
+ guard(mutex)(&data->mutex);
+ ret = regmap_read(data->regmap, BMI323_FIFO_FILL_LEVEL_REG, &fifo_lvl);
+ if (ret)
+ return ret;
+
+ fifo_lvl = min(fifo_lvl, BMI323_FIFO_FULL_IN_WORDS);
+
+ frame_count = fifo_lvl / BMI323_FIFO_FRAME_LENGTH;
+ if (!frame_count)
+ return -EINVAL;
+
+ if (fifo_lvl % BMI323_FIFO_FRAME_LENGTH)
+ dev_warn(data->dev, "Bad FIFO alignment\n");
+
+ /*
+ * Approximate timestamps for each of the sample based on the sampling
+ * frequency, timestamp for last sample and number of samples.
+ */
+ if (data->old_fifo_tstamp) {
+ sample_period = data->fifo_tstamp - data->old_fifo_tstamp;
+ do_div(sample_period, frame_count);
+ } else {
+ sample_period = data->odrns[BMI323_ACCEL];
+ }
+
+ tstamp = data->fifo_tstamp - (frame_count - 1) * sample_period;
+
+ ret = regmap_noinc_read(data->regmap, BMI323_FIFO_DATA_REG,
+ &data->fifo_buff[0],
+ fifo_lvl * BMI323_BYTES_PER_SAMPLE);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < frame_count; i++) {
+ frame = &data->fifo_buff[i * BMI323_FIFO_FRAME_LENGTH];
+ pchannels = &data->buffer.channels[0];
+
+ index = 0;
+ for_each_set_bit(bit, indio_dev->active_scan_mask,
+ BMI323_CHAN_MAX)
+ pchannels[index++] = frame[bit];
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer,
+ tstamp);
+
+ tstamp += sample_period;
+ }
+
+ return frame_count;
+}
+
+static int bmi323_set_watermark(struct iio_dev *indio_dev, unsigned int val)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+
+ val = min(val, (u32)BMI323_FIFO_FULL_IN_FRAMES);
+
+ guard(mutex)(&data->mutex);
+ data->watermark = val;
+
+ return 0;
+}
+
+static int bmi323_fifo_disable(struct bmi323_data *data)
+{
+ int ret;
+
+ guard(mutex)(&data->mutex);
+ ret = regmap_write(data->regmap, BMI323_FIFO_CONF_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
+ BMI323_FIFO_WTRMRK_MSK,
+ FIELD_PREP(BMI323_FIFO_WTRMRK_MSK, 0));
+ if (ret)
+ return ret;
+
+ data->fifo_tstamp = 0;
+ data->state = BMI323_IDLE;
+
+ return 0;
+}
+
+static int bmi323_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+
+ if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED)
+ return 0;
+
+ return bmi323_fifo_disable(data);
+}
+
+static int bmi323_update_watermark(struct bmi323_data *data)
+{
+ int wtrmrk;
+
+ wtrmrk = data->watermark * BMI323_FIFO_FRAME_LENGTH;
+
+ return regmap_write(data->regmap, BMI323_FIFO_WTRMRK_REG, wtrmrk);
+}
+
+static int bmi323_fifo_enable(struct bmi323_data *data)
+{
+ int ret;
+
+ guard(mutex)(&data->mutex);
+ ret = regmap_update_bits(data->regmap, BMI323_FIFO_CONF_REG,
+ BMI323_FIFO_CONF_ACC_GYR_EN_MSK,
+ FIELD_PREP(BMI323_FIFO_CONF_ACC_GYR_EN_MSK,
+ BMI323_FIFO_ACC_GYR_MSK));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
+ BMI323_FIFO_WTRMRK_MSK,
+ FIELD_PREP(BMI323_FIFO_WTRMRK_MSK,
+ data->irq_pin));
+ if (ret)
+ return ret;
+
+ ret = bmi323_update_watermark(data);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, BMI323_FIFO_CTRL_REG,
+ BMI323_FIFO_FLUSH_MSK);
+ if (ret)
+ return ret;
+
+ data->state = BMI323_BUFFER_FIFO;
+
+ return 0;
+}
+
+static int bmi323_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+
+ guard(mutex)(&data->mutex);
+ /*
+ * When the ODR of the accelerometer and gyroscope do not match, the
+ * maximum ODR value between the accelerometer and gyroscope is used
+ * for FIFO and the signal with lower ODR will insert dummy frame.
+ * So allow buffer read only when ODR's of accelero and gyro are equal.
+ * See datasheet section 5.7 "FIFO Data Buffering".
+ */
+ if (data->odrns[BMI323_ACCEL] != data->odrns[BMI323_GYRO]) {
+ dev_err(data->dev, "Accelero and Gyro ODR doesn't match\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bmi323_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+
+ if (iio_device_get_current_mode(indio_dev) == INDIO_BUFFER_TRIGGERED)
+ return 0;
+
+ return bmi323_fifo_enable(data);
+}
+
+static ssize_t hwfifo_watermark_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmi323_data *data = iio_priv(indio_dev);
+ int wm;
+
+ scoped_guard(mutex, &data->mutex)
+ wm = data->watermark;
+
+ return sysfs_emit(buf, "%d\n", wm);
+}
+static IIO_DEVICE_ATTR_RO(hwfifo_watermark, 0);
+
+static ssize_t hwfifo_enabled_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct bmi323_data *data = iio_priv(indio_dev);
+ bool state;
+
+ scoped_guard(mutex, &data->mutex)
+ state = data->state == BMI323_BUFFER_FIFO;
+
+ return sysfs_emit(buf, "%d\n", state);
+}
+static IIO_DEVICE_ATTR_RO(hwfifo_enabled, 0);
+
+static const struct iio_dev_attr *bmi323_fifo_attributes[] = {
+ &iio_dev_attr_hwfifo_watermark,
+ &iio_dev_attr_hwfifo_enabled,
+ NULL
+};
+
+static const struct iio_buffer_setup_ops bmi323_buffer_ops = {
+ .preenable = bmi323_buffer_preenable,
+ .postenable = bmi323_buffer_postenable,
+ .predisable = bmi323_buffer_predisable,
+};
+
+static irqreturn_t bmi323_irq_thread_handler(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct bmi323_data *data = iio_priv(indio_dev);
+ unsigned int status_addr, status, feature_event;
+ s64 timestamp = iio_get_time_ns(indio_dev);
+ int ret;
+
+ if (data->irq_pin == BMI323_IRQ_INT1)
+ status_addr = BMI323_STATUS_INT1_REG;
+ else
+ status_addr = BMI323_STATUS_INT2_REG;
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = regmap_read(data->regmap, status_addr, &status);
+ if (ret)
+ return IRQ_NONE;
+ }
+
+ if (!status || FIELD_GET(BMI323_STATUS_ERROR_MSK, status))
+ return IRQ_NONE;
+
+ if (FIELD_GET(BMI323_STATUS_FIFO_WTRMRK_MSK, status)) {
+ data->old_fifo_tstamp = data->fifo_tstamp;
+ data->fifo_tstamp = iio_get_time_ns(indio_dev);
+ ret = __bmi323_fifo_flush(indio_dev);
+ if (ret < 0)
+ return IRQ_NONE;
+ }
+
+ if (FIELD_GET(BMI323_STATUS_ACC_GYR_DRDY_MSK, status))
+ iio_trigger_poll_nested(data->trig);
+
+ if (FIELD_GET(BMI323_STATUS_MOTION_MSK, status))
+ iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_RISING),
+ timestamp);
+
+ if (FIELD_GET(BMI323_STATUS_NOMOTION_MSK, status))
+ iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_MAG,
+ IIO_EV_DIR_FALLING),
+ timestamp);
+
+ if (FIELD_GET(BMI323_STATUS_STP_WTR_MSK, status))
+ iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_STEPS, 0,
+ IIO_NO_MOD,
+ IIO_EV_TYPE_CHANGE,
+ IIO_EV_DIR_NONE),
+ timestamp);
+
+ if (FIELD_GET(BMI323_STATUS_TAP_MSK, status)) {
+ scoped_guard(mutex, &data->mutex) {
+ ret = regmap_read(data->regmap,
+ BMI323_FEAT_EVNT_EXT_REG,
+ &feature_event);
+ if (ret)
+ return IRQ_NONE;
+ }
+
+ if (FIELD_GET(BMI323_FEAT_EVNT_EXT_S_MSK, feature_event)) {
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_SINGLETAP),
+ timestamp);
+ }
+
+ if (FIELD_GET(BMI323_FEAT_EVNT_EXT_D_MSK, feature_event))
+ iio_push_event(indio_dev,
+ IIO_MOD_EVENT_CODE(IIO_ACCEL, 0,
+ IIO_MOD_X_OR_Y_OR_Z,
+ IIO_EV_TYPE_GESTURE,
+ IIO_EV_DIR_DOUBLETAP),
+ timestamp);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int bmi323_set_drdy_irq(struct bmi323_data *data,
+ enum bmi323_irq_pin irq_pin)
+{
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
+ BMI323_GYR_DRDY_MSK,
+ FIELD_PREP(BMI323_GYR_DRDY_MSK, irq_pin));
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(data->regmap, BMI323_INT_MAP2_REG,
+ BMI323_ACC_DRDY_MSK,
+ FIELD_PREP(BMI323_ACC_DRDY_MSK, irq_pin));
+}
+
+static int bmi323_data_rdy_trigger_set_state(struct iio_trigger *trig,
+ bool state)
+{
+ struct bmi323_data *data = iio_trigger_get_drvdata(trig);
+ enum bmi323_irq_pin irq_pin;
+
+ guard(mutex)(&data->mutex);
+
+ if (data->state == BMI323_BUFFER_FIFO) {
+ dev_warn(data->dev, "Can't set trigger when FIFO enabled\n");
+ return -EBUSY;
+ }
+
+ if (state) {
+ data->state = BMI323_BUFFER_DRDY_TRIGGERED;
+ irq_pin = data->irq_pin;
+ } else {
+ data->state = BMI323_IDLE;
+ irq_pin = BMI323_IRQ_DISABLED;
+ }
+
+ return bmi323_set_drdy_irq(data, irq_pin);
+}
+
+static const struct iio_trigger_ops bmi323_trigger_ops = {
+ .set_trigger_state = &bmi323_data_rdy_trigger_set_state,
+};
+
+static irqreturn_t bmi323_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct bmi323_data *data = iio_priv(indio_dev);
+ int ret, bit, index = 0;
+
+ /* Lock to protect the data->buffer */
+ guard(mutex)(&data->mutex);
+
+ if (*indio_dev->active_scan_mask == BMI323_ALL_CHAN_MSK) {
+ ret = regmap_bulk_read(data->regmap, BMI323_ACCEL_X_REG,
+ &data->buffer.channels,
+ ARRAY_SIZE(data->buffer.channels));
+ if (ret)
+ return IRQ_NONE;
+ } else {
+ for_each_set_bit(bit, indio_dev->active_scan_mask,
+ BMI323_CHAN_MAX) {
+ ret = regmap_raw_read(data->regmap,
+ BMI323_ACCEL_X_REG + bit,
+ &data->buffer.channels[index++],
+ BMI323_BYTES_PER_SAMPLE);
+ if (ret)
+ return IRQ_NONE;
+ }
+ }
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->buffer,
+ iio_get_time_ns(indio_dev));
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int bmi323_set_average(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor, int avg)
+{
+ int raw = ARRAY_SIZE(bmi323_accel_gyro_avrg);
+
+ while (raw--)
+ if (avg == bmi323_accel_gyro_avrg[raw])
+ break;
+ if (raw < 0)
+ return -EINVAL;
+
+ guard(mutex)(&data->mutex);
+ return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
+ BMI323_ACC_GYRO_CONF_AVG_MSK,
+ FIELD_PREP(BMI323_ACC_GYRO_CONF_AVG_MSK,
+ raw));
+}
+
+static int bmi323_get_average(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor, int *avg)
+{
+ int ret, value, raw;
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value);
+ if (ret)
+ return ret;
+ }
+
+ raw = FIELD_GET(BMI323_ACC_GYRO_CONF_AVG_MSK, value);
+ *avg = bmi323_accel_gyro_avrg[raw];
+
+ return IIO_VAL_INT;
+}
+
+static int bmi323_enable_steps(struct bmi323_data *data, int val)
+{
+ int ret;
+
+ guard(mutex)(&data->mutex);
+ if (data->odrhz[BMI323_ACCEL] < 200) {
+ dev_err(data->dev, "Invalid accelerometer parameter\n");
+ return -EINVAL;
+ }
+
+ ret = bmi323_feature_engine_events(data, BMI323_FEAT_IO0_STP_CNT_MSK,
+ val ? 1 : 0);
+ if (ret)
+ return ret;
+
+ set_mask_bits(&data->feature_events, BMI323_FEAT_IO0_STP_CNT_MSK,
+ FIELD_PREP(BMI323_FEAT_IO0_STP_CNT_MSK, val ? 1 : 0));
+
+ return 0;
+}
+
+static int bmi323_read_steps(struct bmi323_data *data, int *val)
+{
+ int ret;
+
+ guard(mutex)(&data->mutex);
+ if (!FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK, data->feature_events))
+ return -EINVAL;
+
+ ret = regmap_bulk_read(data->regmap, BMI323_FEAT_IO2_REG,
+ data->steps_count,
+ ARRAY_SIZE(data->steps_count));
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_le32(data->steps_count);
+
+ return IIO_VAL_INT;
+}
+
+static int bmi323_read_axis(struct bmi323_data *data,
+ struct iio_chan_spec const *chan, int *val)
+{
+ enum bmi323_sensor_type sensor;
+ unsigned int value;
+ u8 addr;
+ int ret;
+
+ ret = bmi323_get_error_status(data);
+ if (ret)
+ return -EINVAL;
+
+ sensor = bmi323_iio_to_sensor(chan->type);
+ addr = bmi323_hw[sensor].data + (chan->channel2 - IIO_MOD_X);
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = regmap_read(data->regmap, addr, &value);
+ if (ret)
+ return ret;
+ }
+
+ *val = sign_extend32(value, chan->scan_type.realbits - 1);
+
+ return IIO_VAL_INT;
+}
+
+static int bmi323_get_temp_data(struct bmi323_data *data, int *val)
+{
+ unsigned int value;
+ int ret;
+
+ ret = bmi323_get_error_status(data);
+ if (ret)
+ return -EINVAL;
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = regmap_read(data->regmap, BMI323_TEMP_REG, &value);
+ if (ret)
+ return ret;
+ }
+
+ *val = sign_extend32(value, 15);
+
+ return IIO_VAL_INT;
+}
+
+static int bmi323_get_odr(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor, int *odr, int *uodr)
+{
+ int ret, value, odr_raw;
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = regmap_read(data->regmap, bmi323_hw[sensor].config, &value);
+ if (ret)
+ return ret;
+ }
+
+ odr_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_ODR_MSK, value);
+ *odr = bmi323_acc_gyro_odr[odr_raw - 1][0];
+ *uodr = bmi323_acc_gyro_odr[odr_raw - 1][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int bmi323_configure_power_mode(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor,
+ int odr_index)
+{
+ enum bmi323_opr_mode mode;
+
+ if (bmi323_acc_gyro_odr[odr_index][0] > 25)
+ mode = ACC_GYRO_MODE_CONTINOUS;
+ else
+ mode = ACC_GYRO_MODE_DUTYCYCLE;
+
+ return bmi323_set_mode(data, sensor, mode);
+}
+
+static int bmi323_set_odr(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor, int odr, int uodr)
+{
+ int odr_raw, ret;
+
+ odr_raw = ARRAY_SIZE(bmi323_acc_gyro_odr);
+
+ while (odr_raw--)
+ if (odr == bmi323_acc_gyro_odr[odr_raw][0] &&
+ uodr == bmi323_acc_gyro_odr[odr_raw][1])
+ break;
+ if (odr_raw < 0)
+ return -EINVAL;
+
+ ret = bmi323_configure_power_mode(data, sensor, odr_raw);
+ if (ret)
+ return -EINVAL;
+
+ guard(mutex)(&data->mutex);
+ data->odrhz[sensor] = bmi323_acc_gyro_odr[odr_raw][0];
+ data->odrns[sensor] = bmi323_acc_gyro_odrns[odr_raw];
+
+ odr_raw++;
+
+ return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
+ BMI323_ACC_GYRO_CONF_ODR_MSK,
+ FIELD_PREP(BMI323_ACC_GYRO_CONF_ODR_MSK,
+ odr_raw));
+}
+
+static int bmi323_get_scale(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor, int *val2)
+{
+ int ret, value, scale_raw;
+
+ scoped_guard(mutex, &data->mutex) {
+ ret = regmap_read(data->regmap, bmi323_hw[sensor].config,
+ &value);
+ if (ret)
+ return ret;
+ }
+
+ scale_raw = FIELD_GET(BMI323_ACC_GYRO_CONF_SCL_MSK, value);
+ *val2 = bmi323_hw[sensor].scale_table[scale_raw][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int bmi323_set_scale(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor, int val, int val2)
+{
+ int scale_raw;
+
+ scale_raw = bmi323_hw[sensor].scale_table_len;
+
+ while (scale_raw--)
+ if (val == bmi323_hw[sensor].scale_table[scale_raw][0] &&
+ val2 == bmi323_hw[sensor].scale_table[scale_raw][1])
+ break;
+ if (scale_raw < 0)
+ return -EINVAL;
+
+ guard(mutex)(&data->mutex);
+ return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
+ BMI323_ACC_GYRO_CONF_SCL_MSK,
+ FIELD_PREP(BMI323_ACC_GYRO_CONF_SCL_MSK,
+ scale_raw));
+}
+
+static int bmi323_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length,
+ long mask)
+{
+ enum bmi323_sensor_type sensor;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *vals = (const int *)bmi323_acc_gyro_odr;
+ *length = ARRAY_SIZE(bmi323_acc_gyro_odr) * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SCALE:
+ sensor = bmi323_iio_to_sensor(chan->type);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *vals = (const int *)bmi323_hw[sensor].scale_table;
+ *length = bmi323_hw[sensor].scale_table_len * 2;
+ return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *type = IIO_VAL_INT;
+ *vals = (const int *)bmi323_accel_gyro_avrg;
+ *length = ARRAY_SIZE(bmi323_accel_gyro_avrg);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi323_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = bmi323_set_odr(data, bmi323_iio_to_sensor(chan->type),
+ val, val2);
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_SCALE:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = bmi323_set_scale(data, bmi323_iio_to_sensor(chan->type),
+ val, val2);
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = bmi323_set_average(data, bmi323_iio_to_sensor(chan->type),
+ val);
+
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ case IIO_CHAN_INFO_ENABLE:
+ return bmi323_enable_steps(data, val);
+ case IIO_CHAN_INFO_PROCESSED:
+ scoped_guard(mutex, &data->mutex) {
+ if (val || !FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK,
+ data->feature_events))
+ return -EINVAL;
+
+ /* Clear step counter value */
+ ret = bmi323_update_ext_reg(data, BMI323_STEP_SC1_REG,
+ BMI323_STEP_SC1_RST_CNT_MSK,
+ FIELD_PREP(BMI323_STEP_SC1_RST_CNT_MSK,
+ 1));
+ }
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bmi323_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct bmi323_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ return bmi323_read_steps(data, val);
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ case IIO_ANGL_VEL:
+ ret = iio_device_claim_direct_mode(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = bmi323_read_axis(data, chan, val);
+
+ iio_device_release_direct_mode(indio_dev);
+ return ret;
+ case IIO_TEMP:
+ return bmi323_get_temp_data(data, val);
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return bmi323_get_odr(data, bmi323_iio_to_sensor(chan->type),
+ val, val2);
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_ACCEL:
+ case IIO_ANGL_VEL:
+ *val = 0;
+ return bmi323_get_scale(data,
+ bmi323_iio_to_sensor(chan->type),
+ val2);
+ case IIO_TEMP:
+ *val = BMI323_TEMP_SCALE / MEGA;
+ *val2 = BMI323_TEMP_SCALE % MEGA;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ return bmi323_get_average(data,
+ bmi323_iio_to_sensor(chan->type),
+ val);
+ case IIO_CHAN_INFO_OFFSET:
+ switch (chan->type) {
+ case IIO_TEMP:
+ *val = BMI323_TEMP_OFFSET;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_ENABLE:
+ scoped_guard(mutex, &data->mutex)
+ *val = FIELD_GET(BMI323_FEAT_IO0_STP_CNT_MSK,
+ data->feature_events);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info bmi323_info = {
+ .read_raw = bmi323_read_raw,
+ .write_raw = bmi323_write_raw,
+ .read_avail = bmi323_read_avail,
+ .hwfifo_set_watermark = bmi323_set_watermark,
+ .write_event_config = bmi323_write_event_config,
+ .read_event_config = bmi323_read_event_config,
+ .write_event_value = bmi323_write_event_value,
+ .read_event_value = bmi323_read_event_value,
+ .event_attrs = &bmi323_event_attribute_group,
+};
+
+#define BMI323_SCAN_MASK_ACCEL_3AXIS \
+ (BIT(BMI323_ACCEL_X) | BIT(BMI323_ACCEL_Y) | BIT(BMI323_ACCEL_Z))
+
+#define BMI323_SCAN_MASK_GYRO_3AXIS \
+ (BIT(BMI323_GYRO_X) | BIT(BMI323_GYRO_Y) | BIT(BMI323_GYRO_Z))
+
+static const unsigned long bmi323_avail_scan_masks[] = {
+ /* 3-axis accel */
+ BMI323_SCAN_MASK_ACCEL_3AXIS,
+ /* 3-axis gyro */
+ BMI323_SCAN_MASK_GYRO_3AXIS,
+ /* 3-axis accel + 3-axis gyro */
+ BMI323_SCAN_MASK_ACCEL_3AXIS | BMI323_SCAN_MASK_GYRO_3AXIS,
+ 0
+};
+
+static int bmi323_int_pin_config(struct bmi323_data *data,
+ enum bmi323_irq_pin irq_pin,
+ bool active_high, bool open_drain, bool latch)
+{
+ unsigned int mask, field_value;
+ int ret;
+
+ ret = regmap_update_bits(data->regmap, BMI323_IO_INT_CONF_REG,
+ BMI323_IO_INT_LTCH_MSK,
+ FIELD_PREP(BMI323_IO_INT_LTCH_MSK, latch));
+ if (ret)
+ return ret;
+
+ ret = bmi323_update_ext_reg(data, BMI323_GEN_SET1_REG,
+ BMI323_GEN_HOLD_DUR_MSK,
+ FIELD_PREP(BMI323_GEN_HOLD_DUR_MSK, 0));
+ if (ret)
+ return ret;
+
+ switch (irq_pin) {
+ case BMI323_IRQ_INT1:
+ mask = BMI323_IO_INT1_LVL_OD_OP_MSK;
+
+ field_value = FIELD_PREP(BMI323_IO_INT1_LVL_MSK, active_high) |
+ FIELD_PREP(BMI323_IO_INT1_OD_MSK, open_drain) |
+ FIELD_PREP(BMI323_IO_INT1_OP_EN_MSK, 1);
+ break;
+ case BMI323_IRQ_INT2:
+ mask = BMI323_IO_INT2_LVL_OD_OP_MSK;
+
+ field_value = FIELD_PREP(BMI323_IO_INT2_LVL_MSK, active_high) |
+ FIELD_PREP(BMI323_IO_INT2_OD_MSK, open_drain) |
+ FIELD_PREP(BMI323_IO_INT2_OP_EN_MSK, 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return regmap_update_bits(data->regmap, BMI323_IO_INT_CTR_REG, mask,
+ field_value);
+}
+
+static int bmi323_trigger_probe(struct bmi323_data *data,
+ struct iio_dev *indio_dev)
+{
+ bool open_drain, active_high, latch;
+ struct fwnode_handle *fwnode;
+ enum bmi323_irq_pin irq_pin;
+ int ret, irq, irq_type;
+ struct irq_data *desc;
+
+ fwnode = dev_fwnode(data->dev);
+ if (!fwnode)
+ return -ENODEV;
+
+ irq = fwnode_irq_get_byname(fwnode, "INT1");
+ if (irq > 0) {
+ irq_pin = BMI323_IRQ_INT1;
+ } else {
+ irq = fwnode_irq_get_byname(fwnode, "INT2");
+ if (irq < 0)
+ return 0;
+
+ irq_pin = BMI323_IRQ_INT2;
+ }
+
+ desc = irq_get_irq_data(irq);
+ if (!desc)
+ return dev_err_probe(data->dev, -EINVAL,
+ "Could not find IRQ %d\n", irq);
+
+ 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 dev_err_probe(data->dev, -EINVAL,
+ "Invalid interrupt type 0x%x specified\n",
+ irq_type);
+ }
+
+ open_drain = fwnode_property_read_bool(fwnode, "drive-open-drain");
+
+ ret = bmi323_int_pin_config(data, irq_pin, active_high, open_drain,
+ latch);
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "Failed to configure irq line\n");
+
+ data->trig = devm_iio_trigger_alloc(data->dev, "%s-trig-%d",
+ indio_dev->name, irq_pin);
+ if (!data->trig)
+ return -ENOMEM;
+
+ data->trig->ops = &bmi323_trigger_ops;
+ iio_trigger_set_drvdata(data->trig, data);
+
+ ret = devm_request_threaded_irq(data->dev, irq, NULL,
+ bmi323_irq_thread_handler,
+ IRQF_ONESHOT, "bmi323-int", indio_dev);
+ if (ret)
+ return dev_err_probe(data->dev, ret, "Failed to request IRQ\n");
+
+ ret = devm_iio_trigger_register(data->dev, data->trig);
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "Trigger registration failed\n");
+
+ data->irq_pin = irq_pin;
+
+ return 0;
+}
+
+static int bmi323_feature_engine_enable(struct bmi323_data *data, bool en)
+{
+ unsigned int feature_status;
+ int ret;
+
+ if (!en)
+ return regmap_write(data->regmap, BMI323_FEAT_CTRL_REG, 0);
+
+ ret = regmap_write(data->regmap, BMI323_FEAT_IO2_REG, 0x012c);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, BMI323_FEAT_IO_STATUS_REG,
+ BMI323_FEAT_IO_STATUS_MSK);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, BMI323_FEAT_CTRL_REG,
+ BMI323_FEAT_ENG_EN_MSK);
+ if (ret)
+ return ret;
+
+ /*
+ * It takes around 4 msec to enable the Feature engine, so check
+ * the status of the feature engine every 2 msec for a maximum
+ * of 5 trials.
+ */
+ ret = regmap_read_poll_timeout(data->regmap, BMI323_FEAT_IO1_REG,
+ feature_status,
+ FIELD_GET(BMI323_FEAT_IO1_ERR_MSK,
+ feature_status) == 1,
+ BMI323_FEAT_ENG_POLL,
+ BMI323_FEAT_ENG_TIMEOUT);
+ if (ret)
+ return dev_err_probe(data->dev, -EINVAL,
+ "Failed to enable feature engine\n");
+
+ return 0;
+}
+
+static void bmi323_disable(void *data_ptr)
+{
+ struct bmi323_data *data = data_ptr;
+
+ bmi323_set_mode(data, BMI323_ACCEL, ACC_GYRO_MODE_DISABLE);
+ bmi323_set_mode(data, BMI323_GYRO, ACC_GYRO_MODE_DISABLE);
+}
+
+static int bmi323_set_bw(struct bmi323_data *data,
+ enum bmi323_sensor_type sensor, enum bmi323_3db_bw bw)
+{
+ return regmap_update_bits(data->regmap, bmi323_hw[sensor].config,
+ BMI323_ACC_GYRO_CONF_BW_MSK,
+ FIELD_PREP(BMI323_ACC_GYRO_CONF_BW_MSK, bw));
+}
+
+static int bmi323_init(struct bmi323_data *data)
+{
+ int ret, val;
+
+ /*
+ * Perform soft reset to make sure the device is in a known state after
+ * start up. A delay of 1.5 ms is required after reset.
+ * See datasheet section 5.17 "Soft Reset".
+ */
+ ret = regmap_write(data->regmap, BMI323_CMD_REG, BMI323_RST_VAL);
+ if (ret)
+ return ret;
+
+ usleep_range(1500, 2000);
+
+ /*
+ * Dummy read is required to enable SPI interface after reset.
+ * See datasheet section 7.2.1 "Protocol Selection".
+ */
+ regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val);
+
+ ret = regmap_read(data->regmap, BMI323_STATUS_REG, &val);
+ if (ret)
+ return ret;
+
+ if (!FIELD_GET(BMI323_STATUS_POR_MSK, val))
+ return dev_err_probe(data->dev, -EINVAL,
+ "Sensor initialization error\n");
+
+ ret = regmap_read(data->regmap, BMI323_CHIP_ID_REG, &val);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(BMI323_CHIP_ID_MSK, val) != BMI323_CHIP_ID_VAL)
+ return dev_err_probe(data->dev, -EINVAL, "Chip ID mismatch\n");
+
+ ret = bmi323_feature_engine_enable(data, true);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, BMI323_ERR_REG, &val);
+ if (ret)
+ return ret;
+
+ if (val)
+ return dev_err_probe(data->dev, -EINVAL,
+ "Sensor power error = 0x%x\n", val);
+
+ /*
+ * Set the Bandwidth coefficient which defines the 3 dB cutoff
+ * frequency in relation to the ODR.
+ */
+ ret = bmi323_set_bw(data, BMI323_ACCEL, BMI323_BW_ODR_BY_2);
+ if (ret)
+ return ret;
+
+ ret = bmi323_set_bw(data, BMI323_GYRO, BMI323_BW_ODR_BY_2);
+ if (ret)
+ return ret;
+
+ ret = bmi323_set_odr(data, BMI323_ACCEL, 25, 0);
+ if (ret)
+ return ret;
+
+ ret = bmi323_set_odr(data, BMI323_GYRO, 25, 0);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(data->dev, bmi323_disable, data);
+}
+
+int bmi323_core_probe(struct device *dev)
+{
+ static const char * const regulator_names[] = { "vdd", "vddio" };
+ struct iio_dev *indio_dev;
+ struct bmi323_data *data;
+ struct regmap *regmap;
+ int ret;
+
+ regmap = dev_get_regmap(dev, NULL);
+ if (!regmap)
+ return dev_err_probe(dev, -ENODEV, "Failed to get regmap\n");
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate device\n");
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
+ regulator_names);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulators\n");
+
+ data = iio_priv(indio_dev);
+ data->dev = dev;
+ data->regmap = regmap;
+ mutex_init(&data->mutex);
+
+ ret = bmi323_init(data);
+ if (ret)
+ return -EINVAL;
+
+ ret = iio_read_mount_matrix(dev, &data->orientation);
+ if (ret)
+ return ret;
+
+ indio_dev->name = "bmi323-imu";
+ indio_dev->info = &bmi323_info;
+ indio_dev->channels = bmi323_channels;
+ indio_dev->num_channels = ARRAY_SIZE(bmi323_channels);
+ indio_dev->available_scan_masks = bmi323_avail_scan_masks;
+ indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_SOFTWARE;
+ dev_set_drvdata(data->dev, indio_dev);
+
+ ret = bmi323_trigger_probe(data, indio_dev);
+ if (ret)
+ return -EINVAL;
+
+ ret = devm_iio_triggered_buffer_setup_ext(data->dev, indio_dev,
+ &iio_pollfunc_store_time,
+ bmi323_trigger_handler,
+ IIO_BUFFER_DIRECTION_IN,
+ &bmi323_buffer_ops,
+ bmi323_fifo_attributes);
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "Failed to setup trigger buffer\n");
+
+ ret = devm_iio_device_register(data->dev, indio_dev);
+ if (ret)
+ return dev_err_probe(data->dev, ret,
+ "Unable to register iio device\n");
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(bmi323_core_probe, IIO_BMI323);
+
+MODULE_DESCRIPTION("Bosch BMI323 IMU driver");
+MODULE_AUTHOR("Jagath Jog J <jagathjog1996@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/imu/bmi323/bmi323_i2c.c b/drivers/iio/imu/bmi323/bmi323_i2c.c
new file mode 100644
index 0000000000..20a8001b99
--- /dev/null
+++ b/drivers/iio/imu/bmi323/bmi323_i2c.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I2C driver for Bosch BMI323 6-Axis IMU.
+ *
+ * Copyright (C) 2023, Jagath Jog J <jagathjog1996@gmail.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "bmi323.h"
+
+struct bmi323_i2c_priv {
+ struct i2c_client *i2c;
+ u8 i2c_rx_buffer[BMI323_FIFO_LENGTH_IN_BYTES + BMI323_I2C_DUMMY];
+};
+
+/*
+ * From BMI323 datasheet section 4: Notes on the Serial Interface Support.
+ * Each I2C register read operation requires to read two dummy bytes before
+ * the actual payload.
+ */
+static int bmi323_regmap_i2c_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf,
+ size_t val_size)
+{
+ struct bmi323_i2c_priv *priv = context;
+ struct i2c_msg msgs[2];
+ int ret;
+
+ msgs[0].addr = priv->i2c->addr;
+ msgs[0].flags = priv->i2c->flags;
+ msgs[0].len = reg_size;
+ msgs[0].buf = (u8 *)reg_buf;
+
+ msgs[1].addr = priv->i2c->addr;
+ msgs[1].len = val_size + BMI323_I2C_DUMMY;
+ msgs[1].buf = priv->i2c_rx_buffer;
+ msgs[1].flags = priv->i2c->flags | I2C_M_RD;
+
+ ret = i2c_transfer(priv->i2c->adapter, msgs, ARRAY_SIZE(msgs));
+ if (ret < 0)
+ return -EIO;
+
+ memcpy(val_buf, priv->i2c_rx_buffer + BMI323_I2C_DUMMY, val_size);
+
+ return 0;
+}
+
+static int bmi323_regmap_i2c_write(void *context, const void *data,
+ size_t count)
+{
+ struct bmi323_i2c_priv *priv = context;
+ u8 reg;
+
+ reg = *(u8 *)data;
+ return i2c_smbus_write_i2c_block_data(priv->i2c, reg,
+ count - sizeof(u8),
+ data + sizeof(u8));
+}
+
+static struct regmap_bus bmi323_regmap_bus = {
+ .read = bmi323_regmap_i2c_read,
+ .write = bmi323_regmap_i2c_write,
+};
+
+static const struct regmap_config bmi323_i2c_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = BMI323_CFG_RES_REG,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static int bmi323_i2c_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct bmi323_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, &bmi323_regmap_bus, priv,
+ &bmi323_i2c_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to initialize I2C Regmap\n");
+
+ return bmi323_core_probe(dev);
+}
+
+static const struct i2c_device_id bmi323_i2c_ids[] = {
+ { "bmi323" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, bmi323_i2c_ids);
+
+static const struct of_device_id bmi323_of_i2c_match[] = {
+ { .compatible = "bosch,bmi323" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bmi323_of_i2c_match);
+
+static struct i2c_driver bmi323_i2c_driver = {
+ .driver = {
+ .name = "bmi323",
+ .of_match_table = bmi323_of_i2c_match,
+ },
+ .probe = bmi323_i2c_probe,
+ .id_table = bmi323_i2c_ids,
+};
+module_i2c_driver(bmi323_i2c_driver);
+
+MODULE_DESCRIPTION("Bosch BMI323 IMU driver");
+MODULE_AUTHOR("Jagath Jog J <jagathjog1996@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_BMI323);
diff --git a/drivers/iio/imu/bmi323/bmi323_spi.c b/drivers/iio/imu/bmi323/bmi323_spi.c
new file mode 100644
index 0000000000..7b1e8127d0
--- /dev/null
+++ b/drivers/iio/imu/bmi323/bmi323_spi.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SPI driver for Bosch BMI323 6-Axis IMU.
+ *
+ * Copyright (C) 2023, Jagath Jog J <jagathjog1996@gmail.com>
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "bmi323.h"
+
+/*
+ * From BMI323 datasheet section 4: Notes on the Serial Interface Support.
+ * Each SPI register read operation requires to read one dummy byte before
+ * the actual payload.
+ */
+static int bmi323_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;
+
+ return spi_write_then_read(spi, reg_buf, reg_size, val_buf, val_size);
+}
+
+static int bmi323_regmap_spi_write(void *context, const void *data,
+ size_t count)
+{
+ struct spi_device *spi = context;
+ u8 *data_buff = (u8 *)data;
+
+ data_buff[1] = data_buff[0];
+ return spi_write(spi, data_buff + 1, count - 1);
+}
+
+static struct regmap_bus bmi323_regmap_bus = {
+ .read = bmi323_regmap_spi_read,
+ .write = bmi323_regmap_spi_write,
+};
+
+static const struct regmap_config bmi323_spi_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .pad_bits = 8,
+ .read_flag_mask = BIT(7),
+ .max_register = BMI323_CFG_RES_REG,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static int bmi323_spi_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init(dev, &bmi323_regmap_bus, dev,
+ &bmi323_spi_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap),
+ "Failed to initialize SPI Regmap\n");
+
+ return bmi323_core_probe(dev);
+}
+
+static const struct spi_device_id bmi323_spi_ids[] = {
+ { "bmi323" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, bmi323_spi_ids);
+
+static const struct of_device_id bmi323_of_spi_match[] = {
+ { .compatible = "bosch,bmi323" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bmi323_of_spi_match);
+
+static struct spi_driver bmi323_spi_driver = {
+ .driver = {
+ .name = "bmi323",
+ .of_match_table = bmi323_of_spi_match,
+ },
+ .probe = bmi323_spi_probe,
+ .id_table = bmi323_spi_ids,
+};
+module_spi_driver(bmi323_spi_driver);
+
+MODULE_DESCRIPTION("Bosch BMI323 IMU driver");
+MODULE_AUTHOR("Jagath Jog J <jagathjog1996@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_BMI323);
diff --git a/drivers/iio/imu/bno055/bno055_ser_core.c b/drivers/iio/imu/bno055/bno055_ser_core.c
index 57728a5684..5677bdf4f8 100644
--- a/drivers/iio/imu/bno055/bno055_ser_core.c
+++ b/drivers/iio/imu/bno055/bno055_ser_core.c
@@ -378,12 +378,12 @@ static void bno055_ser_handle_rx(struct bno055_ser_priv *priv, int status)
* Also, we assume to RX one pkt per time (i.e. the HW doesn't send anything
* unless we require to AND we don't queue more than one request per time).
*/
-static int bno055_ser_receive_buf(struct serdev_device *serdev,
- const unsigned char *buf, size_t size)
+static ssize_t bno055_ser_receive_buf(struct serdev_device *serdev,
+ const u8 *buf, size_t size)
{
int status;
struct bno055_ser_priv *priv = serdev_device_get_drvdata(serdev);
- int remaining = size;
+ size_t remaining = size;
if (size == 0)
return 0;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
index b1e4fde27d..f67bd5a39b 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_accel.c
@@ -137,10 +137,7 @@ static int inv_icm42600_accel_update_scan_mode(struct iio_dev *indio_dev,
out_unlock:
mutex_unlock(&st->lock);
/* sleep maximum required time */
- if (sleep_accel > sleep_temp)
- sleep = sleep_accel;
- else
- sleep = sleep_temp;
+ sleep = max(sleep_accel, sleep_temp);
if (sleep)
msleep(sleep);
return ret;
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
index 6ef1df9d60..b52f328fd2 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_buffer.c
@@ -424,10 +424,7 @@ out_unlock:
mutex_unlock(&st->lock);
/* sleep maximum required time */
- if (sleep_sensor > sleep_temp)
- sleep = sleep_sensor;
- else
- sleep = sleep_temp;
+ sleep = max(sleep_sensor, sleep_temp);
if (sleep)
msleep(sleep);
diff --git a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
index 3bf946e56e..3df0a715e8 100644
--- a/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
+++ b/drivers/iio/imu/inv_icm42600/inv_icm42600_gyro.c
@@ -137,10 +137,7 @@ static int inv_icm42600_gyro_update_scan_mode(struct iio_dev *indio_dev,
out_unlock:
mutex_unlock(&st->lock);
/* sleep maximum required time */
- if (sleep_gyro > sleep_temp)
- sleep = sleep_gyro;
- else
- sleep = sleep_temp;
+ sleep = max(sleep_gyro, sleep_temp);
if (sleep)
msleep(sleep);
return ret;
diff --git a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
index 6b034dccc3..0e94e5335e 100644
--- a/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
+++ b/drivers/iio/imu/inv_mpu6050/inv_mpu_core.c
@@ -567,15 +567,12 @@ static int inv_mpu6050_init_config(struct iio_dev *indio_dev)
static int inv_mpu6050_sensor_set(struct inv_mpu6050_state *st, int reg,
int axis, int val)
{
- int ind, result;
+ int ind;
__be16 d = cpu_to_be16(val);
ind = (axis - IIO_MOD_X) * 2;
- result = regmap_bulk_write(st->map, reg + ind, &d, sizeof(d));
- if (result)
- return -EINVAL;
- return 0;
+ return regmap_bulk_write(st->map, reg + ind, &d, sizeof(d));
}
static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg,
@@ -587,7 +584,7 @@ static int inv_mpu6050_sensor_show(struct inv_mpu6050_state *st, int reg,
ind = (axis - IIO_MOD_X) * 2;
result = regmap_bulk_read(st->map, reg + ind, &d, sizeof(d));
if (result)
- return -EINVAL;
+ return result;
*val = (short)be16_to_cpup(&d);
return IIO_VAL_INT;
diff --git a/drivers/iio/industrialio-buffer.c b/drivers/iio/industrialio-buffer.c
index 176d31d9f9..b581a7e805 100644
--- a/drivers/iio/industrialio-buffer.c
+++ b/drivers/iio/industrialio-buffer.c
@@ -413,6 +413,22 @@ static const unsigned long *iio_scan_mask_match(const unsigned long *av_masks,
{
if (bitmap_empty(mask, masklength))
return NULL;
+ /*
+ * The condition here do not handle multi-long masks correctly.
+ * It only checks the first long to be zero, and will use such mask
+ * as a terminator even if there was bits set after the first long.
+ *
+ * Correct check would require using:
+ * while (!bitmap_empty(av_masks, masklength))
+ * instead. This is potentially hazardous because the
+ * avaliable_scan_masks is a zero terminated array of longs - and
+ * using the proper bitmap_empty() check for multi-long wide masks
+ * would require the array to be terminated with multiple zero longs -
+ * which is not such an usual pattern.
+ *
+ * As writing of this no multi-long wide masks were found in-tree, so
+ * the simple while (*av_masks) check is working.
+ */
while (*av_masks) {
if (strict) {
if (bitmap_equal(mask, av_masks, masklength))
@@ -600,7 +616,7 @@ static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev,
&iio_show_fixed_type,
NULL,
0,
- 0,
+ IIO_SEPARATE,
&indio_dev->dev,
buffer,
&buffer->buffer_attr_list);
@@ -613,7 +629,7 @@ static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev,
&iio_scan_el_show,
&iio_scan_el_store,
chan->scan_index,
- 0,
+ IIO_SEPARATE,
&indio_dev->dev,
buffer,
&buffer->buffer_attr_list);
@@ -623,7 +639,7 @@ static int iio_buffer_add_channel_sysfs(struct iio_dev *indio_dev,
&iio_scan_el_ts_show,
&iio_scan_el_ts_store,
chan->scan_index,
- 0,
+ IIO_SEPARATE,
&indio_dev->dev,
buffer,
&buffer->buffer_attr_list);
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index e6d3d07a4c..173dc00762 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -117,6 +117,8 @@ static const char * const iio_modifier_names[] = {
[IIO_MOD_LIGHT_GREEN] = "green",
[IIO_MOD_LIGHT_BLUE] = "blue",
[IIO_MOD_LIGHT_UV] = "uv",
+ [IIO_MOD_LIGHT_UVA] = "uva",
+ [IIO_MOD_LIGHT_UVB] = "uvb",
[IIO_MOD_LIGHT_DUV] = "duv",
[IIO_MOD_QUATERNION] = "quaternion",
[IIO_MOD_TEMP_AMBIENT] = "ambient",
@@ -182,6 +184,7 @@ static const char * const iio_chan_info_postfix[] = {
[IIO_CHAN_INFO_THERMOCOUPLE_TYPE] = "thermocouple_type",
[IIO_CHAN_INFO_CALIBAMBIENT] = "calibambient",
[IIO_CHAN_INFO_ZEROPOINT] = "zeropoint",
+ [IIO_CHAN_INFO_TROUGH] = "trough_raw",
};
/**
* iio_device_id() - query the unique ID for the device
@@ -1899,6 +1902,66 @@ static int iio_check_extended_name(const struct iio_dev *indio_dev)
static const struct iio_buffer_setup_ops noop_ring_setup_ops;
+static void iio_sanity_check_avail_scan_masks(struct iio_dev *indio_dev)
+{
+ unsigned int num_masks, masklength, longs_per_mask;
+ const unsigned long *av_masks;
+ int i;
+
+ av_masks = indio_dev->available_scan_masks;
+ masklength = indio_dev->masklength;
+ longs_per_mask = BITS_TO_LONGS(masklength);
+
+ /*
+ * The code determining how many available_scan_masks is in the array
+ * will be assuming the end of masks when first long with all bits
+ * zeroed is encountered. This is incorrect for masks where mask
+ * consists of more than one long, and where some of the available masks
+ * has long worth of bits zeroed (but has subsequent bit(s) set). This
+ * is a safety measure against bug where array of masks is terminated by
+ * a single zero while mask width is greater than width of a long.
+ */
+ if (longs_per_mask > 1)
+ dev_warn(indio_dev->dev.parent,
+ "multi long available scan masks not fully supported\n");
+
+ if (bitmap_empty(av_masks, masklength))
+ dev_warn(indio_dev->dev.parent, "empty scan mask\n");
+
+ for (num_masks = 0; *av_masks; num_masks++)
+ av_masks += longs_per_mask;
+
+ if (num_masks < 2)
+ return;
+
+ av_masks = indio_dev->available_scan_masks;
+
+ /*
+ * Go through all the masks from first to one before the last, and see
+ * that no mask found later from the available_scan_masks array is a
+ * subset of mask found earlier. If this happens, then the mask found
+ * later will never get used because scanning the array is stopped when
+ * the first suitable mask is found. Drivers should order the array of
+ * available masks in the order of preference (presumably the least
+ * costy to access masks first).
+ */
+ for (i = 0; i < num_masks - 1; i++) {
+ const unsigned long *mask1;
+ int j;
+
+ mask1 = av_masks + i * longs_per_mask;
+ for (j = i + 1; j < num_masks; j++) {
+ const unsigned long *mask2;
+
+ mask2 = av_masks + j * longs_per_mask;
+ if (bitmap_subset(mask2, mask1, masklength))
+ dev_warn(indio_dev->dev.parent,
+ "available_scan_mask %d subset of %d. Never used\n",
+ j, i);
+ }
+ }
+}
+
int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
{
struct iio_dev_opaque *iio_dev_opaque = to_iio_dev_opaque(indio_dev);
@@ -1937,6 +2000,9 @@ int __iio_device_register(struct iio_dev *indio_dev, struct module *this_mod)
goto error_unreg_debugfs;
}
+ if (indio_dev->available_scan_masks)
+ iio_sanity_check_avail_scan_masks(indio_dev);
+
ret = iio_device_register_sysfs(indio_dev);
if (ret) {
dev_err(indio_dev->dev.parent,
diff --git a/drivers/iio/light/Kconfig b/drivers/iio/light/Kconfig
index 45edba797e..143003232d 100644
--- a/drivers/iio/light/Kconfig
+++ b/drivers/iio/light/Kconfig
@@ -252,6 +252,21 @@ config ISL29125
To compile this driver as a module, choose M here: the module will be
called isl29125.
+config ISL76682
+ tristate "Intersil ISL76682 Light Sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here if you want to build a driver for the Intersil ISL76682
+ Ambient Light Sensor and IR Intensity sensor. This driver provides
+ the readouts via standard IIO sysfs and device interface. Both ALS
+ illuminance and IR illuminance are provided raw with separate scale
+ setting which can be configured via sysfs, the default scale is 1000
+ lux, other options are 4000/16000/64000 lux.
+
+ To compile this driver as a module, choose M here: the module will be
+ called isl76682.
+
config HID_SENSOR_ALS
depends on HID_SENSOR_HUB
select IIO_BUFFER
@@ -347,6 +362,17 @@ config SENSORS_LM3533
changes. The ALS-control output values can be set per zone for the
three current output channels.
+config LTR390
+ tristate "LTR-390UV-01 ambient light and UV sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Lite-On LTR-390UV-01
+ ambient light and UV sensor.
+
+ This driver can also be built as a module. If so, the module
+ will be called ltr390.
+
config LTR501
tristate "LTR-501ALS-01 light sensor"
depends on I2C
@@ -637,6 +663,17 @@ config VEML6070
To compile this driver as a module, choose M here: the
module will be called veml6070.
+config VEML6075
+ tristate "VEML6075 UVA and UVB light sensor"
+ select REGMAP_I2C
+ depends on I2C
+ help
+ Say Y here if you want to build a driver for the Vishay VEML6075 UVA
+ and UVB light sensor.
+
+ To compile this driver as a module, choose M here: the
+ module will be called veml6075.
+
config VL6180
tristate "VL6180 ALS, range and proximity sensor"
depends on I2C
diff --git a/drivers/iio/light/Makefile b/drivers/iio/light/Makefile
index c0db4c4c36..2e5fdb33e0 100644
--- a/drivers/iio/light/Makefile
+++ b/drivers/iio/light/Makefile
@@ -28,8 +28,10 @@ obj-$(CONFIG_IQS621_ALS) += iqs621-als.o
obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o
obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o
obj-$(CONFIG_ISL29125) += isl29125.o
+obj-$(CONFIG_ISL76682) += isl76682.o
obj-$(CONFIG_JSA1212) += jsa1212.o
obj-$(CONFIG_SENSORS_LM3533) += lm3533-als.o
+obj-$(CONFIG_LTR390) += ltr390.o
obj-$(CONFIG_LTR501) += ltr501.o
obj-$(CONFIG_LTRF216A) += ltrf216a.o
obj-$(CONFIG_LV0104CS) += lv0104cs.o
@@ -60,5 +62,6 @@ obj-$(CONFIG_VCNL4000) += vcnl4000.o
obj-$(CONFIG_VCNL4035) += vcnl4035.o
obj-$(CONFIG_VEML6030) += veml6030.o
obj-$(CONFIG_VEML6070) += veml6070.o
+obj-$(CONFIG_VEML6075) += veml6075.o
obj-$(CONFIG_VL6180) += vl6180.o
obj-$(CONFIG_ZOPT2201) += zopt2201.o
diff --git a/drivers/iio/light/isl76682.c b/drivers/iio/light/isl76682.c
new file mode 100644
index 0000000000..cf6ddee44f
--- /dev/null
+++ b/drivers/iio/light/isl76682.c
@@ -0,0 +1,345 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IIO driver for the light sensor ISL76682.
+ * ISL76682 is Ambient Light Sensor
+ *
+ * Copyright (c) 2023 Marek Vasut <marex@denx.de>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <linux/iio/iio.h>
+
+#define ISL76682_REG_COMMAND 0x00
+
+#define ISL76682_COMMAND_EN BIT(7)
+#define ISL76682_COMMAND_MODE_CONTINUOUS BIT(6)
+#define ISL76682_COMMAND_LIGHT_IR BIT(5)
+
+#define ISL76682_COMMAND_RANGE_LUX_1K 0x0
+#define ISL76682_COMMAND_RANGE_LUX_4K 0x1
+#define ISL76682_COMMAND_RANGE_LUX_16K 0x2
+#define ISL76682_COMMAND_RANGE_LUX_64K 0x3
+#define ISL76682_COMMAND_RANGE_LUX_MASK GENMASK(1, 0)
+
+#define ISL76682_REG_ALSIR_L 0x01
+
+#define ISL76682_REG_ALSIR_U 0x02
+
+#define ISL76682_NUM_REGS (ISL76682_REG_ALSIR_U + 1)
+
+#define ISL76682_CONV_TIME_MS 100
+#define ISL76682_INT_TIME_US 90000
+
+#define ISL76682_ADC_MAX (BIT(16) - 1)
+
+struct isl76682_chip {
+ /*
+ * Lock to synchronize access to device command register
+ * and the content of range variable below.
+ */
+ struct mutex lock;
+ struct regmap *regmap;
+ u8 range;
+ u8 command;
+};
+
+struct isl76682_range {
+ u8 range;
+ u32 als;
+ u32 ir;
+};
+
+static struct isl76682_range isl76682_range_table[] = {
+ { ISL76682_COMMAND_RANGE_LUX_1K, 15000, 10500 },
+ { ISL76682_COMMAND_RANGE_LUX_4K, 60000, 42000 },
+ { ISL76682_COMMAND_RANGE_LUX_16K, 240000, 168000 },
+ { ISL76682_COMMAND_RANGE_LUX_64K, 960000, 673000 }
+};
+
+static int isl76682_get(struct isl76682_chip *chip, bool mode_ir, int *data)
+{
+ u8 command;
+ int ret;
+
+ command = ISL76682_COMMAND_EN | ISL76682_COMMAND_MODE_CONTINUOUS |
+ chip->range;
+
+ if (mode_ir)
+ command |= ISL76682_COMMAND_LIGHT_IR;
+
+ if (command != chip->command) {
+ ret = regmap_write(chip->regmap, ISL76682_REG_COMMAND, command);
+ if (ret)
+ return ret;
+
+ /* Need to wait for conversion time if ALS/IR mode enabled */
+ msleep(ISL76682_CONV_TIME_MS);
+
+ chip->command = command;
+ }
+
+ ret = regmap_bulk_read(chip->regmap, ISL76682_REG_ALSIR_L, data, 2);
+ *data &= ISL76682_ADC_MAX;
+ return ret;
+}
+
+static int isl76682_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct isl76682_chip *chip = iio_priv(indio_dev);
+ int i;
+
+ if (mask != IIO_CHAN_INFO_SCALE)
+ return -EINVAL;
+
+ if (val != 0)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(isl76682_range_table); i++) {
+ if (chan->type == IIO_LIGHT && val2 != isl76682_range_table[i].als)
+ continue;
+ if (chan->type == IIO_INTENSITY && val2 != isl76682_range_table[i].ir)
+ continue;
+
+ scoped_guard(mutex, &chip->lock)
+ chip->range = isl76682_range_table[i].range;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int isl76682_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct isl76682_chip *chip = iio_priv(indio_dev);
+ int ret;
+ int i;
+
+ guard(mutex)(&chip->lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ ret = isl76682_get(chip, false, val);
+ return (ret < 0) ? ret : IIO_VAL_INT;
+ case IIO_INTENSITY:
+ ret = isl76682_get(chip, true, val);
+ return (ret < 0) ? ret : IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SCALE:
+ for (i = 0; i < ARRAY_SIZE(isl76682_range_table); i++) {
+ if (chip->range != isl76682_range_table[i].range)
+ continue;
+
+ *val = 0;
+ switch (chan->type) {
+ case IIO_LIGHT:
+ *val2 = isl76682_range_table[i].als;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_INTENSITY:
+ *val2 = isl76682_range_table[i].ir;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ }
+ return -EINVAL;
+ case IIO_CHAN_INFO_INT_TIME:
+ *val = 0;
+ *val2 = ISL76682_INT_TIME_US;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int illuminance_scale_available[] = {
+ 0, 15000,
+ 0, 60000,
+ 0, 240000,
+ 0, 960000,
+};
+
+static int intensity_scale_available[] = {
+ 0, 10500,
+ 0, 42000,
+ 0, 168000,
+ 0, 673000,
+};
+
+static int isl76682_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:
+ switch (chan->type) {
+ case IIO_LIGHT:
+ *vals = illuminance_scale_available;
+ *length = ARRAY_SIZE(illuminance_scale_available);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ case IIO_INTENSITY:
+ *vals = intensity_scale_available;
+ *length = ARRAY_SIZE(intensity_scale_available);
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec isl76682_channels[] = {
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+ }, {
+ .type = IIO_INTENSITY,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_type_available = BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+ }
+};
+
+static const struct iio_info isl76682_info = {
+ .read_avail = isl76682_read_avail,
+ .read_raw = isl76682_read_raw,
+ .write_raw = isl76682_write_raw,
+};
+
+static int isl76682_clear_configure_reg(struct isl76682_chip *chip)
+{
+ struct device *dev = regmap_get_device(chip->regmap);
+ int ret;
+
+ ret = regmap_write(chip->regmap, ISL76682_REG_COMMAND, 0x0);
+ if (ret < 0)
+ dev_err(dev, "Error %d clearing the CONFIGURE register\n", ret);
+
+ /*
+ * In the success case, the command register was zeroed out.
+ *
+ * In the error case, we do not know in which state the command
+ * register is, so we assume it is zeroed out, so that it would
+ * be reprogrammed at the next data read out, and at that time
+ * we hope it would be reprogrammed successfully. That is very
+ * much a best effort approach.
+ */
+ chip->command = 0;
+
+ return ret;
+}
+
+static void isl76682_reset_action(void *chip)
+{
+ isl76682_clear_configure_reg(chip);
+}
+
+static bool isl76682_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ISL76682_REG_ALSIR_L:
+ case ISL76682_REG_ALSIR_U:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config isl76682_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_reg = isl76682_is_volatile_reg,
+ .max_register = ISL76682_NUM_REGS - 1,
+ .num_reg_defaults_raw = ISL76682_NUM_REGS,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int isl76682_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct isl76682_chip *chip;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*chip));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ chip = iio_priv(indio_dev);
+
+ mutex_init(&chip->lock);
+
+ chip->regmap = devm_regmap_init_i2c(client, &isl76682_regmap_config);
+ ret = PTR_ERR_OR_ZERO(chip->regmap);
+ if (ret)
+ return dev_err_probe(dev, ret, "Error initializing regmap\n");
+
+ chip->range = ISL76682_COMMAND_RANGE_LUX_1K;
+
+ ret = isl76682_clear_configure_reg(chip);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, isl76682_reset_action, chip);
+ if (ret)
+ return ret;
+
+ indio_dev->info = &isl76682_info;
+ indio_dev->channels = isl76682_channels;
+ indio_dev->num_channels = ARRAY_SIZE(isl76682_channels);
+ indio_dev->name = "isl76682";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id isl76682_id[] = {
+ { "isl76682" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, isl76682_id);
+
+static const struct of_device_id isl76682_of_match[] = {
+ { .compatible = "isil,isl76682" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, isl76682_of_match);
+
+static struct i2c_driver isl76682_driver = {
+ .driver = {
+ .name = "isl76682",
+ .of_match_table = isl76682_of_match,
+ },
+ .probe = isl76682_probe,
+ .id_table = isl76682_id,
+};
+module_i2c_driver(isl76682_driver);
+
+MODULE_DESCRIPTION("ISL76682 Ambient Light Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
diff --git a/drivers/iio/light/ltr390.c b/drivers/iio/light/ltr390.c
new file mode 100644
index 0000000000..fff1e89909
--- /dev/null
+++ b/drivers/iio/light/ltr390.c
@@ -0,0 +1,196 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * IIO driver for Lite-On LTR390 ALS and UV sensor
+ * (7-bit I2C slave address 0x53)
+ *
+ * Based on the work of:
+ * Shreeya Patel and Shi Zhigang (LTRF216 Driver)
+ *
+ * Copyright (C) 2023 Anshul Dalal <anshulusr@gmail.com>
+ *
+ * Datasheet:
+ * https://optoelectronics.liteon.com/upload/download/DS86-2015-0004/LTR-390UV_Final_%20DS_V1%201.pdf
+ *
+ * TODO:
+ * - Support for configurable gain and resolution
+ * - Sensor suspend/resume support
+ * - Add support for reading the ALS
+ * - Interrupt support
+ */
+
+#include <linux/i2c.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+
+#include <asm/unaligned.h>
+
+#define LTR390_MAIN_CTRL 0x00
+#define LTR390_PART_ID 0x06
+#define LTR390_UVS_DATA 0x10
+
+#define LTR390_SW_RESET BIT(4)
+#define LTR390_UVS_MODE BIT(3)
+#define LTR390_SENSOR_ENABLE BIT(1)
+
+#define LTR390_PART_NUMBER_ID 0xb
+
+/*
+ * At 20-bit resolution (integration time: 400ms) and 18x gain, 2300 counts of
+ * the sensor are equal to 1 UV Index [Datasheet Page#8].
+ *
+ * For the default resolution of 18-bit (integration time: 100ms) and default
+ * gain of 3x, the counts/uvi are calculated as follows:
+ * 2300 / ((3/18) * (100/400)) = 95.83
+ */
+#define LTR390_COUNTS_PER_UVI 96
+
+/*
+ * Window Factor is needed when the device is under Window glass with coated
+ * tinted ink. This is to compensate for the light loss due to the lower
+ * transmission rate of the window glass and helps * in calculating lux.
+ */
+#define LTR390_WINDOW_FACTOR 1
+
+struct ltr390_data {
+ struct regmap *regmap;
+ struct i2c_client *client;
+ /* Protects device from simulataneous reads */
+ struct mutex lock;
+};
+
+static const struct regmap_config ltr390_regmap_config = {
+ .name = "ltr390",
+ .reg_bits = 8,
+ .reg_stride = 1,
+ .val_bits = 8,
+};
+
+static int ltr390_register_read(struct ltr390_data *data, u8 register_address)
+{
+ struct device *dev = &data->client->dev;
+ int ret;
+ u8 recieve_buffer[3];
+
+ guard(mutex)(&data->lock);
+
+ ret = regmap_bulk_read(data->regmap, register_address, recieve_buffer,
+ sizeof(recieve_buffer));
+ if (ret) {
+ dev_err(dev, "failed to read measurement data");
+ return ret;
+ }
+
+ return get_unaligned_le24(recieve_buffer);
+}
+
+static int ltr390_read_raw(struct iio_dev *iio_device,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ int ret;
+ struct ltr390_data *data = iio_priv(iio_device);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ltr390_register_read(data, LTR390_UVS_DATA);
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = LTR390_WINDOW_FACTOR;
+ *val2 = LTR390_COUNTS_PER_UVI;
+ return IIO_VAL_FRACTIONAL;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ltr390_info = {
+ .read_raw = ltr390_read_raw,
+};
+
+static const struct iio_chan_spec ltr390_channel = {
+ .type = IIO_UVINDEX,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE)
+};
+
+static int ltr390_probe(struct i2c_client *client)
+{
+ struct ltr390_data *data;
+ struct iio_dev *indio_dev;
+ struct device *dev;
+ int ret, part_number;
+
+ dev = &client->dev;
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+
+ data->regmap = devm_regmap_init_i2c(client, &ltr390_regmap_config);
+ if (IS_ERR(data->regmap))
+ return dev_err_probe(dev, PTR_ERR(data->regmap),
+ "regmap initialization failed\n");
+
+ data->client = client;
+ mutex_init(&data->lock);
+
+ indio_dev->info = &ltr390_info;
+ indio_dev->channels = &ltr390_channel;
+ indio_dev->num_channels = 1;
+ indio_dev->name = "ltr390";
+
+ ret = regmap_read(data->regmap, LTR390_PART_ID, &part_number);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get sensor's part id\n");
+ /* Lower 4 bits of `part_number` change with hardware revisions */
+ if (part_number >> 4 != LTR390_PART_NUMBER_ID)
+ dev_info(dev, "received invalid product id: 0x%x", part_number);
+ dev_dbg(dev, "LTR390, product id: 0x%x\n", part_number);
+
+ /* reset sensor, chip fails to respond to this, so ignore any errors */
+ regmap_set_bits(data->regmap, LTR390_MAIN_CTRL, LTR390_SW_RESET);
+
+ /* Wait for the registers to reset before proceeding */
+ usleep_range(1000, 2000);
+
+ ret = regmap_set_bits(data->regmap, LTR390_MAIN_CTRL,
+ LTR390_SENSOR_ENABLE | LTR390_UVS_MODE);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable the sensor\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct i2c_device_id ltr390_id[] = {
+ { "ltr390" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ltr390_id);
+
+static const struct of_device_id ltr390_of_table[] = {
+ { .compatible = "liteon,ltr390" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ltr390_of_table);
+
+static struct i2c_driver ltr390_driver = {
+ .driver = {
+ .name = "ltr390",
+ .of_match_table = ltr390_of_table,
+ },
+ .probe = ltr390_probe,
+ .id_table = ltr390_id,
+};
+module_i2c_driver(ltr390_driver);
+
+MODULE_AUTHOR("Anshul Dalal <anshulusr@gmail.com>");
+MODULE_DESCRIPTION("Lite-On LTR390 ALS and UV sensor Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/light/ltrf216a.c b/drivers/iio/light/ltrf216a.c
index 8de4dd8499..68dc48420a 100644
--- a/drivers/iio/light/ltrf216a.c
+++ b/drivers/iio/light/ltrf216a.c
@@ -234,7 +234,7 @@ static int ltrf216a_read_data(struct ltrf216a_data *data, u8 addr)
static int ltrf216a_get_lux(struct ltrf216a_data *data)
{
int ret, greendata;
- u64 lux, div;
+ u64 lux;
ret = ltrf216a_set_power_state(data, true);
if (ret)
@@ -246,10 +246,9 @@ static int ltrf216a_get_lux(struct ltrf216a_data *data)
ltrf216a_set_power_state(data, false);
- lux = greendata * 45 * LTRF216A_WIN_FAC * 100;
- div = data->als_gain_fac * data->int_time_fac * 100;
+ lux = greendata * 45 * LTRF216A_WIN_FAC;
- return div_u64(lux, div);
+ return lux;
}
static int ltrf216a_read_raw(struct iio_dev *indio_dev,
@@ -279,7 +278,8 @@ static int ltrf216a_read_raw(struct iio_dev *indio_dev,
if (ret < 0)
return ret;
*val = ret;
- return IIO_VAL_INT;
+ *val2 = data->als_gain_fac * data->int_time_fac;
+ return IIO_VAL_FRACTIONAL;
case IIO_CHAN_INFO_INT_TIME:
mutex_lock(&data->lock);
ret = ltrf216a_get_int_time(data, val, val2);
diff --git a/drivers/iio/light/pa12203001.c b/drivers/iio/light/pa12203001.c
index ed241598ae..636432c456 100644
--- a/drivers/iio/light/pa12203001.c
+++ b/drivers/iio/light/pa12203001.c
@@ -472,7 +472,7 @@ static struct i2c_driver pa12203001_driver = {
.driver = {
.name = PA12203001_DRIVER_NAME,
.pm = &pa12203001_pm_ops,
- .acpi_match_table = ACPI_PTR(pa12203001_acpi_match),
+ .acpi_match_table = pa12203001_acpi_match,
},
.probe = pa12203001_probe,
.remove = pa12203001_remove,
diff --git a/drivers/iio/light/rohm-bu27008.c b/drivers/iio/light/rohm-bu27008.c
index 6a6d778050..0f010eff19 100644
--- a/drivers/iio/light/rohm-bu27008.c
+++ b/drivers/iio/light/rohm-bu27008.c
@@ -130,6 +130,7 @@
* @BU27008_BLUE: Blue channel. Via data2 (when used).
* @BU27008_CLEAR: Clear channel. Via data2 or data3 (when used).
* @BU27008_IR: IR channel. Via data3 (when used).
+ * @BU27008_LUX: Illuminance channel, computed using RGB and IR.
* @BU27008_NUM_CHANS: Number of channel types.
*/
enum bu27008_chan_type {
@@ -138,6 +139,7 @@ enum bu27008_chan_type {
BU27008_BLUE,
BU27008_CLEAR,
BU27008_IR,
+ BU27008_LUX,
BU27008_NUM_CHANS
};
@@ -172,6 +174,8 @@ static const unsigned long bu27008_scan_masks[] = {
ALWAYS_SCANNABLE | BIT(BU27008_CLEAR) | BIT(BU27008_IR),
/* buffer is R, G, B, IR */
ALWAYS_SCANNABLE | BIT(BU27008_BLUE) | BIT(BU27008_IR),
+ /* buffer is R, G, B, IR, LUX */
+ ALWAYS_SCANNABLE | BIT(BU27008_BLUE) | BIT(BU27008_IR) | BIT(BU27008_LUX),
0
};
@@ -331,6 +335,19 @@ static const struct iio_chan_spec bu27008_channels[] = {
* Hence we don't advertise available ones either.
*/
BU27008_CHAN(IR, DATA3, 0),
+ {
+ .type = IIO_LIGHT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .channel = BU27008_LUX,
+ .scan_index = BU27008_LUX,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 64,
+ .storagebits = 64,
+ .endianness = IIO_CPU,
+ },
+ },
IIO_CHAN_SOFT_TIMESTAMP(BU27008_NUM_CHANS),
};
@@ -1004,6 +1021,169 @@ static int bu27008_read_one(struct bu27008_data *data, struct iio_dev *idev,
return ret;
}
+#define BU27008_LUX_DATA_RED 0
+#define BU27008_LUX_DATA_GREEN 1
+#define BU27008_LUX_DATA_BLUE 2
+#define BU27008_LUX_DATA_IR 3
+#define LUX_DATA_SIZE (BU27008_NUM_HW_CHANS * sizeof(__le16))
+
+static int bu27008_read_lux_chans(struct bu27008_data *data, unsigned int time,
+ __le16 *chan_data)
+{
+ int ret, chan_sel, tmpret, valid;
+
+ chan_sel = BU27008_BLUE2_IR3 << (ffs(data->cd->chan_sel_mask) - 1);
+
+ ret = regmap_update_bits(data->regmap, data->cd->chan_sel_reg,
+ data->cd->chan_sel_mask, chan_sel);
+ if (ret)
+ return ret;
+
+ ret = bu27008_meas_set(data, true);
+ if (ret)
+ return ret;
+
+ msleep(time / USEC_PER_MSEC);
+
+ ret = regmap_read_poll_timeout(data->regmap, data->cd->valid_reg,
+ valid, (valid & BU27008_MASK_VALID),
+ BU27008_VALID_RESULT_WAIT_QUANTA_US,
+ BU27008_MAX_VALID_RESULT_WAIT_US);
+ if (ret)
+ goto out;
+
+ ret = regmap_bulk_read(data->regmap, BU27008_REG_DATA0_LO, chan_data,
+ LUX_DATA_SIZE);
+ if (ret)
+ goto out;
+out:
+ tmpret = bu27008_meas_set(data, false);
+ if (tmpret)
+ dev_warn(data->dev, "Stopping measurement failed\n");
+
+ return ret;
+}
+
+/*
+ * Following equation for computing lux out of register values was given by
+ * ROHM HW colleagues;
+ *
+ * Red = RedData*1024 / Gain * 20 / meas_mode
+ * Green = GreenData* 1024 / Gain * 20 / meas_mode
+ * Blue = BlueData* 1024 / Gain * 20 / meas_mode
+ * IR = IrData* 1024 / Gain * 20 / meas_mode
+ *
+ * where meas_mode is the integration time in mS / 10
+ *
+ * IRratio = (IR > 0.18 * Green) ? 0 : 1
+ *
+ * Lx = max(c1*Red + c2*Green + c3*Blue,0)
+ *
+ * for
+ * IRratio 0: c1 = -0.00002237, c2 = 0.0003219, c3 = -0.000120371
+ * IRratio 1: c1 = -0.00001074, c2 = 0.000305415, c3 = -0.000129367
+ */
+
+/*
+ * The max chan data is 0xffff. When we multiply it by 1024 * 20, we'll get
+ * 0x4FFFB000 which still fits in 32-bit integer. This won't overflow.
+ */
+#define NORM_CHAN_DATA_FOR_LX_CALC(chan, gain, time) (le16_to_cpu(chan) * \
+ 1024 * 20 / (gain) / (time))
+static u64 bu27008_calc_nlux(struct bu27008_data *data, __le16 *lux_data,
+ unsigned int gain, unsigned int gain_ir, unsigned int time)
+{
+ unsigned int red, green, blue, ir;
+ s64 c1, c2, c3, nlux;
+
+ time /= 10000;
+ ir = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_IR], gain_ir, time);
+ red = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_RED], gain, time);
+ green = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_GREEN], gain, time);
+ blue = NORM_CHAN_DATA_FOR_LX_CALC(lux_data[BU27008_LUX_DATA_BLUE], gain, time);
+
+ if ((u64)ir * 100LLU > (u64)green * 18LLU) {
+ c1 = -22370;
+ c2 = 321900;
+ c3 = -120371;
+ } else {
+ c1 = -10740;
+ c2 = 305415;
+ c3 = -129367;
+ }
+ nlux = c1 * red + c2 * green + c3 * blue;
+
+ return max_t(s64, 0, nlux);
+}
+
+static int bu27008_get_time_n_gains(struct bu27008_data *data,
+ unsigned int *gain, unsigned int *gain_ir, unsigned int *time)
+{
+ int ret;
+
+ ret = bu27008_get_gain(data, &data->gts, gain);
+ if (ret < 0)
+ return ret;
+
+ ret = bu27008_get_gain(data, &data->gts_ir, gain_ir);
+ if (ret < 0)
+ return ret;
+
+ ret = bu27008_get_int_time_us(data);
+ if (ret < 0)
+ return ret;
+
+ /* Max integration time is 400000. Fits in signed int. */
+ *time = ret;
+
+ return 0;
+}
+
+struct bu27008_buf {
+ __le16 chan[BU27008_NUM_HW_CHANS];
+ u64 lux __aligned(8);
+ s64 ts __aligned(8);
+};
+
+static int bu27008_buffer_fill_lux(struct bu27008_data *data,
+ struct bu27008_buf *raw)
+{
+ unsigned int gain, gain_ir, time;
+ int ret;
+
+ ret = bu27008_get_time_n_gains(data, &gain, &gain_ir, &time);
+ if (ret)
+ return ret;
+
+ raw->lux = bu27008_calc_nlux(data, raw->chan, gain, gain_ir, time);
+
+ return 0;
+}
+
+static int bu27008_read_lux(struct bu27008_data *data, struct iio_dev *idev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2)
+{
+ __le16 lux_data[BU27008_NUM_HW_CHANS];
+ unsigned int gain, gain_ir, time;
+ u64 nlux;
+ int ret;
+
+ ret = bu27008_get_time_n_gains(data, &gain, &gain_ir, &time);
+ if (ret)
+ return ret;
+
+ ret = bu27008_read_lux_chans(data, time, lux_data);
+ if (ret)
+ return ret;
+
+ nlux = bu27008_calc_nlux(data, lux_data, gain, gain_ir, time);
+ *val = (int)nlux;
+ *val2 = nlux >> 32LLU;
+
+ return IIO_VAL_INT_64;
+}
+
static int bu27008_read_raw(struct iio_dev *idev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -1018,7 +1198,10 @@ static int bu27008_read_raw(struct iio_dev *idev,
return -EBUSY;
mutex_lock(&data->mutex);
- ret = bu27008_read_one(data, idev, chan, val, val2);
+ if (chan->type == IIO_LIGHT)
+ ret = bu27008_read_lux(data, idev, chan, val, val2);
+ else
+ ret = bu27008_read_one(data, idev, chan, val, val2);
mutex_unlock(&data->mutex);
iio_device_release_direct_mode(idev);
@@ -1026,6 +1209,11 @@ static int bu27008_read_raw(struct iio_dev *idev,
return ret;
case IIO_CHAN_INFO_SCALE:
+ if (chan->type == IIO_LIGHT) {
+ *val = 0;
+ *val2 = 1;
+ return IIO_VAL_INT_PLUS_NANO;
+ }
ret = bu27008_get_scale(data, chan->scan_index == BU27008_IR,
val, val2);
if (ret)
@@ -1236,10 +1424,7 @@ static irqreturn_t bu27008_trigger_handler(int irq, void *p)
struct iio_poll_func *pf = p;
struct iio_dev *idev = pf->indio_dev;
struct bu27008_data *data = iio_priv(idev);
- struct {
- __le16 chan[BU27008_NUM_HW_CHANS];
- s64 ts __aligned(8);
- } raw;
+ struct bu27008_buf raw;
int ret, dummy;
memset(&raw, 0, sizeof(raw));
@@ -1257,6 +1442,12 @@ static irqreturn_t bu27008_trigger_handler(int irq, void *p)
if (ret < 0)
goto err_read;
+ if (test_bit(BU27008_LUX, idev->active_scan_mask)) {
+ ret = bu27008_buffer_fill_lux(data, &raw);
+ if (ret)
+ goto err_read;
+ }
+
iio_push_to_buffers_with_timestamp(idev, &raw, pf->timestamp);
err_read:
iio_trigger_notify_done(idev->trig);
diff --git a/drivers/iio/light/veml6075.c b/drivers/iio/light/veml6075.c
new file mode 100644
index 0000000000..05d4c0e901
--- /dev/null
+++ b/drivers/iio/light/veml6075.c
@@ -0,0 +1,474 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Vishay VEML6075 UVA and UVB light sensor
+ *
+ * Copyright 2023 Javier Carrasco <javier.carrasco.cruz@gmail.com>
+ *
+ * 7-bit I2C slave, address 0x10
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+
+#define VEML6075_CMD_CONF 0x00 /* configuration register */
+#define VEML6075_CMD_UVA 0x07 /* UVA channel */
+#define VEML6075_CMD_UVB 0x09 /* UVB channel */
+#define VEML6075_CMD_COMP1 0x0A /* visible light compensation */
+#define VEML6075_CMD_COMP2 0x0B /* infrarred light compensation */
+#define VEML6075_CMD_ID 0x0C /* device ID */
+
+#define VEML6075_CONF_IT GENMASK(6, 4) /* intregration time */
+#define VEML6075_CONF_HD BIT(3) /* dynamic setting */
+#define VEML6075_CONF_TRIG BIT(2) /* trigger */
+#define VEML6075_CONF_AF BIT(1) /* active force enable */
+#define VEML6075_CONF_SD BIT(0) /* shutdown */
+
+#define VEML6075_IT_50_MS 0x00
+#define VEML6075_IT_100_MS 0x01
+#define VEML6075_IT_200_MS 0x02
+#define VEML6075_IT_400_MS 0x03
+#define VEML6075_IT_800_MS 0x04
+
+#define VEML6075_AF_DISABLE 0x00
+#define VEML6075_AF_ENABLE 0x01
+
+#define VEML6075_SD_DISABLE 0x00
+#define VEML6075_SD_ENABLE 0x01
+
+/* Open-air coefficients and responsivity */
+#define VEML6075_A_COEF 2220
+#define VEML6075_B_COEF 1330
+#define VEML6075_C_COEF 2950
+#define VEML6075_D_COEF 1740
+#define VEML6075_UVA_RESP 1461
+#define VEML6075_UVB_RESP 2591
+
+static const int veml6075_it_ms[] = { 50, 100, 200, 400, 800 };
+
+struct veml6075_data {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ /*
+ * prevent integration time modification and triggering
+ * measurements while a measurement is underway.
+ */
+ struct mutex lock;
+};
+
+/* channel number */
+enum veml6075_chan {
+ CH_UVA,
+ CH_UVB,
+};
+
+static const struct iio_chan_spec veml6075_channels[] = {
+ {
+ .type = IIO_INTENSITY,
+ .channel = CH_UVA,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_UVA,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ },
+ {
+ .type = IIO_INTENSITY,
+ .channel = CH_UVB,
+ .modified = 1,
+ .channel2 = IIO_MOD_LIGHT_UVB,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ },
+ {
+ .type = IIO_UVINDEX,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_INT_TIME),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_INT_TIME),
+ },
+};
+
+static int veml6075_request_measurement(struct veml6075_data *data)
+{
+ int ret, conf, int_time;
+
+ ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf);
+ if (ret < 0)
+ return ret;
+
+ /* disable shutdown and trigger measurement */
+ ret = regmap_write(data->regmap, VEML6075_CMD_CONF,
+ (conf | VEML6075_CONF_TRIG) & ~VEML6075_CONF_SD);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * A measurement requires between 1.30 and 1.40 times the integration
+ * time for all possible configurations. Using a 1.50 factor simplifies
+ * operations and ensures reliability under all circumstances.
+ */
+ int_time = veml6075_it_ms[FIELD_GET(VEML6075_CONF_IT, conf)];
+ msleep(int_time + (int_time / 2));
+
+ /* shutdown again, data registers are still accessible */
+ return regmap_update_bits(data->regmap, VEML6075_CMD_CONF,
+ VEML6075_CONF_SD, VEML6075_CONF_SD);
+}
+
+static int veml6075_uva_comp(int raw_uva, int comp1, int comp2)
+{
+ int comp1a_c, comp2a_c, uva_comp;
+
+ comp1a_c = (comp1 * VEML6075_A_COEF) / 1000U;
+ comp2a_c = (comp2 * VEML6075_B_COEF) / 1000U;
+ uva_comp = raw_uva - comp1a_c - comp2a_c;
+
+ return clamp_val(uva_comp, 0, U16_MAX);
+}
+
+static int veml6075_uvb_comp(int raw_uvb, int comp1, int comp2)
+{
+ int comp1b_c, comp2b_c, uvb_comp;
+
+ comp1b_c = (comp1 * VEML6075_C_COEF) / 1000U;
+ comp2b_c = (comp2 * VEML6075_D_COEF) / 1000U;
+ uvb_comp = raw_uvb - comp1b_c - comp2b_c;
+
+ return clamp_val(uvb_comp, 0, U16_MAX);
+}
+
+static int veml6075_read_comp(struct veml6075_data *data, int *c1, int *c2)
+{
+ int ret;
+
+ ret = regmap_read(data->regmap, VEML6075_CMD_COMP1, c1);
+ if (ret < 0)
+ return ret;
+
+ return regmap_read(data->regmap, VEML6075_CMD_COMP2, c2);
+}
+
+static int veml6075_read_uv_direct(struct veml6075_data *data, int chan,
+ int *val)
+{
+ int c1, c2, ret;
+
+ guard(mutex)(&data->lock);
+
+ ret = veml6075_request_measurement(data);
+ if (ret < 0)
+ return ret;
+
+ ret = veml6075_read_comp(data, &c1, &c2);
+ if (ret < 0)
+ return ret;
+
+ switch (chan) {
+ case CH_UVA:
+ ret = regmap_read(data->regmap, VEML6075_CMD_UVA, val);
+ if (ret < 0)
+ return ret;
+
+ *val = veml6075_uva_comp(*val, c1, c2);
+ return IIO_VAL_INT;
+ case CH_UVB:
+ ret = regmap_read(data->regmap, VEML6075_CMD_UVB, val);
+ if (ret < 0)
+ return ret;
+
+ *val = veml6075_uvb_comp(*val, c1, c2);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6075_read_int_time_index(struct veml6075_data *data)
+{
+ int ret, conf;
+
+ ret = regmap_read(data->regmap, VEML6075_CMD_CONF, &conf);
+ if (ret < 0)
+ return ret;
+
+ return FIELD_GET(VEML6075_CONF_IT, conf);
+}
+
+static int veml6075_read_int_time_ms(struct veml6075_data *data, int *val)
+{
+ int int_index;
+
+ guard(mutex)(&data->lock);
+ int_index = veml6075_read_int_time_index(data);
+ if (int_index < 0)
+ return int_index;
+
+ *val = veml6075_it_ms[int_index];
+
+ return IIO_VAL_INT;
+}
+
+static int veml6075_get_uvi_micro(struct veml6075_data *data, int uva_comp,
+ int uvb_comp)
+{
+ int uvia_micro = uva_comp * VEML6075_UVA_RESP;
+ int uvib_micro = uvb_comp * VEML6075_UVB_RESP;
+ int int_index;
+
+ int_index = veml6075_read_int_time_index(data);
+ if (int_index < 0)
+ return int_index;
+
+ switch (int_index) {
+ case VEML6075_IT_50_MS:
+ return uvia_micro + uvib_micro;
+ case VEML6075_IT_100_MS:
+ case VEML6075_IT_200_MS:
+ case VEML6075_IT_400_MS:
+ case VEML6075_IT_800_MS:
+ return (uvia_micro + uvib_micro) / (2 << int_index);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6075_read_uvi(struct veml6075_data *data, int *val, int *val2)
+{
+ int ret, c1, c2, uva, uvb, uvi_micro;
+
+ guard(mutex)(&data->lock);
+
+ ret = veml6075_request_measurement(data);
+ if (ret < 0)
+ return ret;
+
+ ret = veml6075_read_comp(data, &c1, &c2);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(data->regmap, VEML6075_CMD_UVA, &uva);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(data->regmap, VEML6075_CMD_UVB, &uvb);
+ if (ret < 0)
+ return ret;
+
+ uvi_micro = veml6075_get_uvi_micro(data, veml6075_uva_comp(uva, c1, c2),
+ veml6075_uvb_comp(uvb, c1, c2));
+ if (uvi_micro < 0)
+ return uvi_micro;
+
+ *val = uvi_micro / MICRO;
+ *val2 = uvi_micro % MICRO;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+}
+
+static int veml6075_read_responsivity(int chan, int *val, int *val2)
+{
+ /* scale = 1 / resp */
+ switch (chan) {
+ case CH_UVA:
+ /* resp = 0.93 c/uW/cm2: scale = 1.75268817 */
+ *val = 1;
+ *val2 = 75268817;
+ return IIO_VAL_INT_PLUS_NANO;
+ case CH_UVB:
+ /* resp = 2.1 c/uW/cm2: scale = 0.476190476 */
+ *val = 0;
+ *val2 = 476190476;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6075_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_INT_TIME:
+ *length = ARRAY_SIZE(veml6075_it_ms);
+ *vals = veml6075_it_ms;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_LIST;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6075_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct veml6075_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ return veml6075_read_uv_direct(data, chan->channel, val);
+ case IIO_CHAN_INFO_PROCESSED:
+ return veml6075_read_uvi(data, val, val2);
+ case IIO_CHAN_INFO_INT_TIME:
+ return veml6075_read_int_time_ms(data, val);
+ case IIO_CHAN_INFO_SCALE:
+ return veml6075_read_responsivity(chan->channel, val, val2);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int veml6075_write_int_time_ms(struct veml6075_data *data, int val)
+{
+ int i = ARRAY_SIZE(veml6075_it_ms);
+
+ guard(mutex)(&data->lock);
+
+ while (i-- > 0) {
+ if (val == veml6075_it_ms[i])
+ break;
+ }
+ if (i < 0)
+ return -EINVAL;
+
+ return regmap_update_bits(data->regmap, VEML6075_CMD_CONF,
+ VEML6075_CONF_IT,
+ FIELD_PREP(VEML6075_CONF_IT, i));
+}
+
+static int veml6075_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct veml6075_data *data = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_INT_TIME:
+ return veml6075_write_int_time_ms(data, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info veml6075_info = {
+ .read_avail = veml6075_read_avail,
+ .read_raw = veml6075_read_raw,
+ .write_raw = veml6075_write_raw,
+};
+
+static bool veml6075_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case VEML6075_CMD_CONF:
+ case VEML6075_CMD_UVA:
+ case VEML6075_CMD_UVB:
+ case VEML6075_CMD_COMP1:
+ case VEML6075_CMD_COMP2:
+ case VEML6075_CMD_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool veml6075_writable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case VEML6075_CMD_CONF:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config veml6075_regmap_config = {
+ .name = "veml6075",
+ .reg_bits = 8,
+ .val_bits = 16,
+ .max_register = VEML6075_CMD_ID,
+ .readable_reg = veml6075_readable_reg,
+ .writeable_reg = veml6075_writable_reg,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static int veml6075_probe(struct i2c_client *client)
+{
+ struct veml6075_data *data;
+ struct iio_dev *indio_dev;
+ struct regmap *regmap;
+ int config, ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(client, &veml6075_regmap_config);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+ data->regmap = regmap;
+
+ mutex_init(&data->lock);
+
+ indio_dev->name = "veml6075";
+ indio_dev->info = &veml6075_info;
+ indio_dev->channels = veml6075_channels;
+ indio_dev->num_channels = ARRAY_SIZE(veml6075_channels);
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ ret = devm_regulator_get_enable(&client->dev, "vdd");
+ if (ret < 0)
+ return ret;
+
+ /* default: 100ms integration time, active force enable, shutdown */
+ config = FIELD_PREP(VEML6075_CONF_IT, VEML6075_IT_100_MS) |
+ FIELD_PREP(VEML6075_CONF_AF, VEML6075_AF_ENABLE) |
+ FIELD_PREP(VEML6075_CONF_SD, VEML6075_SD_ENABLE);
+ ret = regmap_write(data->regmap, VEML6075_CMD_CONF, config);
+ if (ret < 0)
+ return ret;
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id veml6075_id[] = {
+ { "veml6075" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, veml6075_id);
+
+static const struct of_device_id veml6075_of_match[] = {
+ { .compatible = "vishay,veml6075" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, veml6075_of_match);
+
+static struct i2c_driver veml6075_driver = {
+ .driver = {
+ .name = "veml6075",
+ .of_match_table = veml6075_of_match,
+ },
+ .probe = veml6075_probe,
+ .id_table = veml6075_id,
+};
+
+module_i2c_driver(veml6075_driver);
+
+MODULE_AUTHOR("Javier Carrasco <javier.carrasco.cruz@gmail.com>");
+MODULE_DESCRIPTION("Vishay VEML6075 UVA and UVB light sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/magnetometer/tmag5273.c b/drivers/iio/magnetometer/tmag5273.c
index e8c4ca142d..218b1ce076 100644
--- a/drivers/iio/magnetometer/tmag5273.c
+++ b/drivers/iio/magnetometer/tmag5273.c
@@ -497,17 +497,13 @@ static int tmag5273_set_operating_mode(struct tmag5273_data *data,
static void tmag5273_read_device_property(struct tmag5273_data *data)
{
struct device *dev = data->dev;
- const char *str;
int ret;
data->angle_measurement = TMAG5273_ANGLE_EN_X_Y;
- ret = device_property_read_string(dev, "ti,angle-measurement", &str);
- if (ret)
- return;
-
- ret = match_string(tmag5273_angle_names,
- ARRAY_SIZE(tmag5273_angle_names), str);
+ ret = device_property_match_property_string(dev, "ti,angle-measurement",
+ tmag5273_angle_names,
+ ARRAY_SIZE(tmag5273_angle_names));
if (ret >= 0)
data->angle_measurement = ret;
}
diff --git a/drivers/iio/pressure/Kconfig b/drivers/iio/pressure/Kconfig
index 95efa32e42..79adfd059c 100644
--- a/drivers/iio/pressure/Kconfig
+++ b/drivers/iio/pressure/Kconfig
@@ -109,6 +109,28 @@ config HP03
To compile this driver as a module, choose M here: the module
will be called hp03.
+config HSC030PA
+ tristate "Honeywell HSC/SSC TruStability pressure sensor series"
+ depends on (I2C || SPI_MASTER)
+ select HSC030PA_I2C if I2C
+ select HSC030PA_SPI if SPI_MASTER
+ help
+ Say Y here to build support for the Honeywell TruStability
+ HSC and SSC pressure and temperature sensor series.
+
+ To compile this driver as a module, choose M here: the module
+ will be called hsc030pa.
+
+config HSC030PA_I2C
+ tristate
+ depends on HSC030PA
+ depends on I2C
+
+config HSC030PA_SPI
+ tristate
+ depends on HSC030PA
+ depends on SPI_MASTER
+
config ICP10100
tristate "InvenSense ICP-101xx pressure and temperature sensor"
depends on I2C
diff --git a/drivers/iio/pressure/Makefile b/drivers/iio/pressure/Makefile
index 436aec7e65..b0f8b94662 100644
--- a/drivers/iio/pressure/Makefile
+++ b/drivers/iio/pressure/Makefile
@@ -15,6 +15,9 @@ 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_HSC030PA) += hsc030pa.o
+obj-$(CONFIG_HSC030PA_I2C) += hsc030pa_i2c.o
+obj-$(CONFIG_HSC030PA_SPI) += hsc030pa_spi.o
obj-$(CONFIG_ICP10100) += icp10100.o
obj-$(CONFIG_MPL115) += mpl115.o
obj-$(CONFIG_MPL115_I2C) += mpl115_i2c.o
diff --git a/drivers/iio/pressure/bmp280-core.c b/drivers/iio/pressure/bmp280-core.c
index a2ef1373a2..fe8734468e 100644
--- a/drivers/iio/pressure/bmp280-core.c
+++ b/drivers/iio/pressure/bmp280-core.c
@@ -13,6 +13,7 @@
* https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
* https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bme280-ds002.pdf
* https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf
+ * https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp390-ds002.pdf
* https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp581-ds004.pdf
*
* Notice:
@@ -794,10 +795,12 @@ static int bmp280_chip_config(struct bmp280_data *data)
}
static const int bmp280_oversampling_avail[] = { 1, 2, 4, 8, 16 };
+static const u8 bmp280_chip_ids[] = { BMP280_CHIP_ID };
const struct bmp280_chip_info bmp280_chip_info = {
.id_reg = BMP280_REG_ID,
- .chip_id = BMP280_CHIP_ID,
+ .chip_id = bmp280_chip_ids,
+ .num_chip_id = ARRAY_SIZE(bmp280_chip_ids),
.regmap_config = &bmp280_regmap_config,
.start_up_time = 2000,
.channels = bmp280_channels,
@@ -846,9 +849,12 @@ static int bme280_chip_config(struct bmp280_data *data)
return bmp280_chip_config(data);
}
+static const u8 bme280_chip_ids[] = { BME280_CHIP_ID };
+
const struct bmp280_chip_info bme280_chip_info = {
.id_reg = BMP280_REG_ID,
- .chip_id = BME280_CHIP_ID,
+ .chip_id = bme280_chip_ids,
+ .num_chip_id = ARRAY_SIZE(bme280_chip_ids),
.regmap_config = &bmp280_regmap_config,
.start_up_time = 2000,
.channels = bmp280_channels,
@@ -920,7 +926,7 @@ static int bmp380_cmd(struct bmp280_data *data, u8 cmd)
}
/*
- * Returns temperature in Celsius dregrees, resolution is 0.01º C. Output value of
+ * Returns temperature in Celsius degrees, resolution is 0.01º C. Output value of
* "5123" equals 51.2º C. t_fine carries fine temperature as global value.
*
* Taken from datasheet, Section Appendix 9, "Compensation formula" and repo
@@ -1220,10 +1226,12 @@ static int bmp380_chip_config(struct bmp280_data *data)
static const int bmp380_oversampling_avail[] = { 1, 2, 4, 8, 16, 32 };
static const int bmp380_iir_filter_coeffs_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128};
+static const u8 bmp380_chip_ids[] = { BMP380_CHIP_ID, BMP390_CHIP_ID };
const struct bmp280_chip_info bmp380_chip_info = {
.id_reg = BMP380_REG_ID,
- .chip_id = BMP380_CHIP_ID,
+ .chip_id = bmp380_chip_ids,
+ .num_chip_id = ARRAY_SIZE(bmp380_chip_ids),
.regmap_config = &bmp380_regmap_config,
.start_up_time = 2000,
.channels = bmp380_channels,
@@ -1385,7 +1393,7 @@ static int bmp580_read_temp(struct bmp280_data *data, int *val, int *val2)
/*
* Temperature is returned in Celsius degrees in fractional
- * form down 2^16. We reescale by x1000 to return milli Celsius
+ * form down 2^16. We rescale by x1000 to return milli Celsius
* to respect IIO ABI.
*/
*val = raw_temp * 1000;
@@ -1412,7 +1420,7 @@ static int bmp580_read_press(struct bmp280_data *data, int *val, int *val2)
}
/*
* Pressure is returned in Pascals in fractional form down 2^16.
- * We reescale /1000 to convert to kilopascal to respect IIO ABI.
+ * We rescale /1000 to convert to kilopascal to respect IIO ABI.
*/
*val = raw_press;
*val2 = 64000; /* 2^6 * 1000 */
@@ -1720,10 +1728,12 @@ static int bmp580_chip_config(struct bmp280_data *data)
}
static const int bmp580_oversampling_avail[] = { 1, 2, 4, 8, 16, 32, 64, 128 };
+static const u8 bmp580_chip_ids[] = { BMP580_CHIP_ID, BMP580_CHIP_ID_ALT };
const struct bmp280_chip_info bmp580_chip_info = {
.id_reg = BMP580_REG_CHIP_ID,
- .chip_id = BMP580_CHIP_ID,
+ .chip_id = bmp580_chip_ids,
+ .num_chip_id = ARRAY_SIZE(bmp580_chip_ids),
.regmap_config = &bmp580_regmap_config,
.start_up_time = 2000,
.channels = bmp380_channels,
@@ -1983,10 +1993,12 @@ static int bmp180_chip_config(struct bmp280_data *data)
static const int bmp180_oversampling_temp_avail[] = { 1 };
static const int bmp180_oversampling_press_avail[] = { 1, 2, 4, 8 };
+static const u8 bmp180_chip_ids[] = { BMP180_CHIP_ID };
const struct bmp280_chip_info bmp180_chip_info = {
.id_reg = BMP280_REG_ID,
- .chip_id = BMP180_CHIP_ID,
+ .chip_id = bmp180_chip_ids,
+ .num_chip_id = ARRAY_SIZE(bmp180_chip_ids),
.regmap_config = &bmp180_regmap_config,
.start_up_time = 2000,
.channels = bmp280_channels,
@@ -2077,6 +2089,7 @@ int bmp280_common_probe(struct device *dev,
struct bmp280_data *data;
struct gpio_desc *gpiod;
unsigned int chip_id;
+ unsigned int i;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
@@ -2142,12 +2155,17 @@ int bmp280_common_probe(struct device *dev,
ret = regmap_read(regmap, data->chip_info->id_reg, &chip_id);
if (ret < 0)
return ret;
- if (chip_id != data->chip_info->chip_id) {
- dev_err(dev, "bad chip id: expected %x got %x\n",
- data->chip_info->chip_id, chip_id);
- return -EINVAL;
+
+ for (i = 0; i < data->chip_info->num_chip_id; i++) {
+ if (chip_id == data->chip_info->chip_id[i]) {
+ dev_info(dev, "0x%x is a known chip id for %s\n", chip_id, name);
+ break;
+ }
}
+ if (i == data->chip_info->num_chip_id)
+ dev_warn(dev, "bad chip id: 0x%x is not a known chip id\n", chip_id);
+
if (data->chip_info->preinit) {
ret = data->chip_info->preinit(data);
if (ret)
diff --git a/drivers/iio/pressure/bmp280-i2c.c b/drivers/iio/pressure/bmp280-i2c.c
index dbe630ad05..34e3bc7584 100644
--- a/drivers/iio/pressure/bmp280-i2c.c
+++ b/drivers/iio/pressure/bmp280-i2c.c
@@ -7,13 +7,11 @@
static int bmp280_i2c_probe(struct i2c_client *client)
{
- struct regmap *regmap;
- const struct bmp280_chip_info *chip_info;
const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ const struct bmp280_chip_info *chip_info;
+ struct regmap *regmap;
- chip_info = device_get_match_data(&client->dev);
- if (!chip_info)
- chip_info = (const struct bmp280_chip_info *) id->driver_data;
+ chip_info = i2c_get_match_data(client);
regmap = devm_regmap_init_i2c(client, chip_info->regmap_config);
if (IS_ERR(regmap)) {
diff --git a/drivers/iio/pressure/bmp280-spi.c b/drivers/iio/pressure/bmp280-spi.c
index 967de99c1b..a444d4b297 100644
--- a/drivers/iio/pressure/bmp280-spi.c
+++ b/drivers/iio/pressure/bmp280-spi.c
@@ -4,6 +4,7 @@
*
* Inspired by the older BMP085 driver drivers/misc/bmp085-spi.c
*/
+#include <linux/bits.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/err.h>
@@ -14,8 +15,7 @@
static int bmp280_regmap_spi_write(void *context, const void *data,
size_t count)
{
- struct device *dev = context;
- struct spi_device *spi = to_spi_device(dev);
+ struct spi_device *spi = to_spi_device(context);
u8 buf[2];
memcpy(buf, data, 2);
@@ -31,12 +31,39 @@ static int bmp280_regmap_spi_write(void *context, const void *data,
static int bmp280_regmap_spi_read(void *context, const void *reg,
size_t reg_size, void *val, size_t val_size)
{
- struct device *dev = context;
- struct spi_device *spi = to_spi_device(dev);
+ struct spi_device *spi = to_spi_device(context);
return spi_write_then_read(spi, reg, reg_size, val, val_size);
}
+static int bmp380_regmap_spi_read(void *context, const void *reg,
+ size_t reg_size, void *val, size_t val_size)
+{
+ struct spi_device *spi = to_spi_device(context);
+ u8 rx_buf[4];
+ ssize_t status;
+
+ /*
+ * Maximum number of consecutive bytes read for a temperature or
+ * pressure measurement is 3.
+ */
+ if (val_size > 3)
+ return -EINVAL;
+
+ /*
+ * According to the BMP3xx datasheets, for a basic SPI read opertion,
+ * the first byte needs to be dropped and the rest are the requested
+ * data.
+ */
+ status = spi_write_then_read(spi, reg, 1, rx_buf, val_size + 1);
+ if (status)
+ return status;
+
+ memcpy(val, rx_buf + 1, val_size);
+
+ return 0;
+}
+
static struct regmap_bus bmp280_regmap_bus = {
.write = bmp280_regmap_spi_write,
.read = bmp280_regmap_spi_read,
@@ -44,10 +71,19 @@ static struct regmap_bus bmp280_regmap_bus = {
.val_format_endian_default = REGMAP_ENDIAN_BIG,
};
+static struct regmap_bus bmp380_regmap_bus = {
+ .write = bmp280_regmap_spi_write,
+ .read = bmp380_regmap_spi_read,
+ .read_flag_mask = BIT(7),
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+};
+
static int bmp280_spi_probe(struct spi_device *spi)
{
const struct spi_device_id *id = spi_get_device_id(spi);
const struct bmp280_chip_info *chip_info;
+ struct regmap_bus *bmp_regmap_bus;
struct regmap *regmap;
int ret;
@@ -58,12 +94,20 @@ static int bmp280_spi_probe(struct spi_device *spi)
return ret;
}
- chip_info = device_get_match_data(&spi->dev);
- if (!chip_info)
- chip_info = (const struct bmp280_chip_info *) id->driver_data;
+ chip_info = spi_get_device_match_data(spi);
+
+ switch (chip_info->chip_id[0]) {
+ case BMP380_CHIP_ID:
+ case BMP390_CHIP_ID:
+ bmp_regmap_bus = &bmp380_regmap_bus;
+ break;
+ default:
+ bmp_regmap_bus = &bmp280_regmap_bus;
+ break;
+ }
regmap = devm_regmap_init(&spi->dev,
- &bmp280_regmap_bus,
+ bmp_regmap_bus,
&spi->dev,
chip_info->regmap_config);
if (IS_ERR(regmap)) {
diff --git a/drivers/iio/pressure/bmp280.h b/drivers/iio/pressure/bmp280.h
index 5c0563ce75..4012387d79 100644
--- a/drivers/iio/pressure/bmp280.h
+++ b/drivers/iio/pressure/bmp280.h
@@ -292,6 +292,7 @@
#define BMP580_CHIP_ID_ALT 0x51
#define BMP180_CHIP_ID 0x55
#define BMP280_CHIP_ID 0x58
+#define BMP390_CHIP_ID 0x60
#define BME280_CHIP_ID 0x60
#define BMP280_SOFT_RESET_VAL 0xB6
@@ -410,7 +411,7 @@ struct bmp280_data {
__le16 bmp280_cal_buf[BMP280_CONTIGUOUS_CALIB_REGS / 2];
__be16 bmp180_cal_buf[BMP180_REG_CALIB_COUNT / 2];
u8 bmp380_cal_buf[BMP380_CALIB_REG_COUNT];
- /* Miscellaneous, endianess-aware data buffers */
+ /* Miscellaneous, endianness-aware data buffers */
__le16 le16;
__be16 be16;
} __aligned(IIO_DMA_MINALIGN);
@@ -418,7 +419,8 @@ struct bmp280_data {
struct bmp280_chip_info {
unsigned int id_reg;
- const unsigned int chip_id;
+ const u8 *chip_id;
+ int num_chip_id;
const struct regmap_config *regmap_config;
diff --git a/drivers/iio/pressure/dlhl60d.c b/drivers/iio/pressure/dlhl60d.c
index 28c8269ba6..0bba4c5a8d 100644
--- a/drivers/iio/pressure/dlhl60d.c
+++ b/drivers/iio/pressure/dlhl60d.c
@@ -250,18 +250,17 @@ static irqreturn_t dlh_trigger_handler(int irq, void *private)
struct dlh_state *st = iio_priv(indio_dev);
int ret;
unsigned int chn, i = 0;
- __be32 tmp_buf[2];
+ __be32 tmp_buf[2] = { };
ret = dlh_start_capture_and_read(st);
if (ret)
goto out;
for_each_set_bit(chn, indio_dev->active_scan_mask,
- indio_dev->masklength) {
- memcpy(tmp_buf + i,
+ indio_dev->masklength) {
+ memcpy(&tmp_buf[i++],
&st->rx_buf[1] + chn * DLH_NUM_DATA_BYTES,
DLH_NUM_DATA_BYTES);
- i++;
}
iio_push_to_buffers(indio_dev, tmp_buf);
diff --git a/drivers/iio/pressure/hsc030pa.c b/drivers/iio/pressure/hsc030pa.c
new file mode 100644
index 0000000000..d6a51f0c33
--- /dev/null
+++ b/drivers/iio/pressure/hsc030pa.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Honeywell TruStability HSC Series pressure/temperature sensor
+ *
+ * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro>
+ *
+ * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/trustability-hsc-series/documents/sps-siot-trustability-hsc-series-high-accuracy-board-mount-pressure-sensors-50099148-a-en-ciid-151133.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/init.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+
+#include <asm/unaligned.h>
+
+#include "hsc030pa.h"
+
+/*
+ * HSC_PRESSURE_TRIPLET_LEN - length for the string that defines the
+ * pressure range, measurement unit and type as per the part nomenclature.
+ * Consult honeywell,pressure-triplet in the bindings file for details.
+ */
+#define HSC_PRESSURE_TRIPLET_LEN 6
+#define HSC_STATUS_MASK GENMASK(7, 6)
+#define HSC_TEMPERATURE_MASK GENMASK(15, 5)
+#define HSC_PRESSURE_MASK GENMASK(29, 16)
+
+struct hsc_func_spec {
+ u32 output_min;
+ u32 output_max;
+};
+
+/*
+ * function A: 10% - 90% of 2^14
+ * function B: 5% - 95% of 2^14
+ * function C: 5% - 85% of 2^14
+ * function F: 4% - 94% of 2^14
+ */
+static const struct hsc_func_spec hsc_func_spec[] = {
+ [HSC_FUNCTION_A] = { .output_min = 1638, .output_max = 14746 },
+ [HSC_FUNCTION_B] = { .output_min = 819, .output_max = 15565 },
+ [HSC_FUNCTION_C] = { .output_min = 819, .output_max = 13926 },
+ [HSC_FUNCTION_F] = { .output_min = 655, .output_max = 15401 },
+};
+
+enum hsc_variants {
+ HSC001BA = 0x00, HSC1_6BA = 0x01, HSC2_5BA = 0x02, HSC004BA = 0x03,
+ HSC006BA = 0x04, HSC010BA = 0x05, HSC1_6MD = 0x06, HSC2_5MD = 0x07,
+ HSC004MD = 0x08, HSC006MD = 0x09, HSC010MD = 0x0a, HSC016MD = 0x0b,
+ HSC025MD = 0x0c, HSC040MD = 0x0d, HSC060MD = 0x0e, HSC100MD = 0x0f,
+ HSC160MD = 0x10, HSC250MD = 0x11, HSC400MD = 0x12, HSC600MD = 0x13,
+ HSC001BD = 0x14, HSC1_6BD = 0x15, HSC2_5BD = 0x16, HSC004BD = 0x17,
+ HSC2_5MG = 0x18, HSC004MG = 0x19, HSC006MG = 0x1a, HSC010MG = 0x1b,
+ HSC016MG = 0x1c, HSC025MG = 0x1d, HSC040MG = 0x1e, HSC060MG = 0x1f,
+ HSC100MG = 0x20, HSC160MG = 0x21, HSC250MG = 0x22, HSC400MG = 0x23,
+ HSC600MG = 0x24, HSC001BG = 0x25, HSC1_6BG = 0x26, HSC2_5BG = 0x27,
+ HSC004BG = 0x28, HSC006BG = 0x29, HSC010BG = 0x2a, HSC100KA = 0x2b,
+ HSC160KA = 0x2c, HSC250KA = 0x2d, HSC400KA = 0x2e, HSC600KA = 0x2f,
+ HSC001GA = 0x30, HSC160LD = 0x31, HSC250LD = 0x32, HSC400LD = 0x33,
+ HSC600LD = 0x34, HSC001KD = 0x35, HSC1_6KD = 0x36, HSC2_5KD = 0x37,
+ HSC004KD = 0x38, HSC006KD = 0x39, HSC010KD = 0x3a, HSC016KD = 0x3b,
+ HSC025KD = 0x3c, HSC040KD = 0x3d, HSC060KD = 0x3e, HSC100KD = 0x3f,
+ HSC160KD = 0x40, HSC250KD = 0x41, HSC400KD = 0x42, HSC250LG = 0x43,
+ HSC400LG = 0x44, HSC600LG = 0x45, HSC001KG = 0x46, HSC1_6KG = 0x47,
+ HSC2_5KG = 0x48, HSC004KG = 0x49, HSC006KG = 0x4a, HSC010KG = 0x4b,
+ HSC016KG = 0x4c, HSC025KG = 0x4d, HSC040KG = 0x4e, HSC060KG = 0x4f,
+ HSC100KG = 0x50, HSC160KG = 0x51, HSC250KG = 0x52, HSC400KG = 0x53,
+ HSC600KG = 0x54, HSC001GG = 0x55, HSC015PA = 0x56, HSC030PA = 0x57,
+ HSC060PA = 0x58, HSC100PA = 0x59, HSC150PA = 0x5a, HSC0_5ND = 0x5b,
+ HSC001ND = 0x5c, HSC002ND = 0x5d, HSC004ND = 0x5e, HSC005ND = 0x5f,
+ HSC010ND = 0x60, HSC020ND = 0x61, HSC030ND = 0x62, HSC001PD = 0x63,
+ HSC005PD = 0x64, HSC015PD = 0x65, HSC030PD = 0x66, HSC060PD = 0x67,
+ HSC001NG = 0x68, HSC002NG = 0x69, HSC004NG = 0x6a, HSC005NG = 0x6b,
+ HSC010NG = 0x6c, HSC020NG = 0x6d, HSC030NG = 0x6e, HSC001PG = 0x6f,
+ HSC005PG = 0x70, HSC015PG = 0x71, HSC030PG = 0x72, HSC060PG = 0x73,
+ HSC100PG = 0x74, HSC150PG = 0x75, HSC_VARIANTS_MAX
+};
+
+static const char * const hsc_triplet_variants[HSC_VARIANTS_MAX] = {
+ [HSC001BA] = "001BA", [HSC1_6BA] = "1.6BA", [HSC2_5BA] = "2.5BA",
+ [HSC004BA] = "004BA", [HSC006BA] = "006BA", [HSC010BA] = "010BA",
+ [HSC1_6MD] = "1.6MD", [HSC2_5MD] = "2.5MD", [HSC004MD] = "004MD",
+ [HSC006MD] = "006MD", [HSC010MD] = "010MD", [HSC016MD] = "016MD",
+ [HSC025MD] = "025MD", [HSC040MD] = "040MD", [HSC060MD] = "060MD",
+ [HSC100MD] = "100MD", [HSC160MD] = "160MD", [HSC250MD] = "250MD",
+ [HSC400MD] = "400MD", [HSC600MD] = "600MD", [HSC001BD] = "001BD",
+ [HSC1_6BD] = "1.6BD", [HSC2_5BD] = "2.5BD", [HSC004BD] = "004BD",
+ [HSC2_5MG] = "2.5MG", [HSC004MG] = "004MG", [HSC006MG] = "006MG",
+ [HSC010MG] = "010MG", [HSC016MG] = "016MG", [HSC025MG] = "025MG",
+ [HSC040MG] = "040MG", [HSC060MG] = "060MG", [HSC100MG] = "100MG",
+ [HSC160MG] = "160MG", [HSC250MG] = "250MG", [HSC400MG] = "400MG",
+ [HSC600MG] = "600MG", [HSC001BG] = "001BG", [HSC1_6BG] = "1.6BG",
+ [HSC2_5BG] = "2.5BG", [HSC004BG] = "004BG", [HSC006BG] = "006BG",
+ [HSC010BG] = "010BG", [HSC100KA] = "100KA", [HSC160KA] = "160KA",
+ [HSC250KA] = "250KA", [HSC400KA] = "400KA", [HSC600KA] = "600KA",
+ [HSC001GA] = "001GA", [HSC160LD] = "160LD", [HSC250LD] = "250LD",
+ [HSC400LD] = "400LD", [HSC600LD] = "600LD", [HSC001KD] = "001KD",
+ [HSC1_6KD] = "1.6KD", [HSC2_5KD] = "2.5KD", [HSC004KD] = "004KD",
+ [HSC006KD] = "006KD", [HSC010KD] = "010KD", [HSC016KD] = "016KD",
+ [HSC025KD] = "025KD", [HSC040KD] = "040KD", [HSC060KD] = "060KD",
+ [HSC100KD] = "100KD", [HSC160KD] = "160KD", [HSC250KD] = "250KD",
+ [HSC400KD] = "400KD", [HSC250LG] = "250LG", [HSC400LG] = "400LG",
+ [HSC600LG] = "600LG", [HSC001KG] = "001KG", [HSC1_6KG] = "1.6KG",
+ [HSC2_5KG] = "2.5KG", [HSC004KG] = "004KG", [HSC006KG] = "006KG",
+ [HSC010KG] = "010KG", [HSC016KG] = "016KG", [HSC025KG] = "025KG",
+ [HSC040KG] = "040KG", [HSC060KG] = "060KG", [HSC100KG] = "100KG",
+ [HSC160KG] = "160KG", [HSC250KG] = "250KG", [HSC400KG] = "400KG",
+ [HSC600KG] = "600KG", [HSC001GG] = "001GG", [HSC015PA] = "015PA",
+ [HSC030PA] = "030PA", [HSC060PA] = "060PA", [HSC100PA] = "100PA",
+ [HSC150PA] = "150PA", [HSC0_5ND] = "0.5ND", [HSC001ND] = "001ND",
+ [HSC002ND] = "002ND", [HSC004ND] = "004ND", [HSC005ND] = "005ND",
+ [HSC010ND] = "010ND", [HSC020ND] = "020ND", [HSC030ND] = "030ND",
+ [HSC001PD] = "001PD", [HSC005PD] = "005PD", [HSC015PD] = "015PD",
+ [HSC030PD] = "030PD", [HSC060PD] = "060PD", [HSC001NG] = "001NG",
+ [HSC002NG] = "002NG", [HSC004NG] = "004NG", [HSC005NG] = "005NG",
+ [HSC010NG] = "010NG", [HSC020NG] = "020NG", [HSC030NG] = "030NG",
+ [HSC001PG] = "001PG", [HSC005PG] = "005PG", [HSC015PG] = "015PG",
+ [HSC030PG] = "030PG", [HSC060PG] = "060PG", [HSC100PG] = "100PG",
+ [HSC150PG] = "150PG",
+};
+
+/**
+ * struct hsc_range_config - list of pressure ranges based on nomenclature
+ * @pmin: lowest pressure that can be measured
+ * @pmax: highest pressure that can be measured
+ */
+struct hsc_range_config {
+ const s32 pmin;
+ const s32 pmax;
+};
+
+/* All min max limits have been converted to pascals */
+static const struct hsc_range_config hsc_range_config[HSC_VARIANTS_MAX] = {
+ [HSC001BA] = { .pmin = 0, .pmax = 100000 },
+ [HSC1_6BA] = { .pmin = 0, .pmax = 160000 },
+ [HSC2_5BA] = { .pmin = 0, .pmax = 250000 },
+ [HSC004BA] = { .pmin = 0, .pmax = 400000 },
+ [HSC006BA] = { .pmin = 0, .pmax = 600000 },
+ [HSC010BA] = { .pmin = 0, .pmax = 1000000 },
+ [HSC1_6MD] = { .pmin = -160, .pmax = 160 },
+ [HSC2_5MD] = { .pmin = -250, .pmax = 250 },
+ [HSC004MD] = { .pmin = -400, .pmax = 400 },
+ [HSC006MD] = { .pmin = -600, .pmax = 600 },
+ [HSC010MD] = { .pmin = -1000, .pmax = 1000 },
+ [HSC016MD] = { .pmin = -1600, .pmax = 1600 },
+ [HSC025MD] = { .pmin = -2500, .pmax = 2500 },
+ [HSC040MD] = { .pmin = -4000, .pmax = 4000 },
+ [HSC060MD] = { .pmin = -6000, .pmax = 6000 },
+ [HSC100MD] = { .pmin = -10000, .pmax = 10000 },
+ [HSC160MD] = { .pmin = -16000, .pmax = 16000 },
+ [HSC250MD] = { .pmin = -25000, .pmax = 25000 },
+ [HSC400MD] = { .pmin = -40000, .pmax = 40000 },
+ [HSC600MD] = { .pmin = -60000, .pmax = 60000 },
+ [HSC001BD] = { .pmin = -100000, .pmax = 100000 },
+ [HSC1_6BD] = { .pmin = -160000, .pmax = 160000 },
+ [HSC2_5BD] = { .pmin = -250000, .pmax = 250000 },
+ [HSC004BD] = { .pmin = -400000, .pmax = 400000 },
+ [HSC2_5MG] = { .pmin = 0, .pmax = 250 },
+ [HSC004MG] = { .pmin = 0, .pmax = 400 },
+ [HSC006MG] = { .pmin = 0, .pmax = 600 },
+ [HSC010MG] = { .pmin = 0, .pmax = 1000 },
+ [HSC016MG] = { .pmin = 0, .pmax = 1600 },
+ [HSC025MG] = { .pmin = 0, .pmax = 2500 },
+ [HSC040MG] = { .pmin = 0, .pmax = 4000 },
+ [HSC060MG] = { .pmin = 0, .pmax = 6000 },
+ [HSC100MG] = { .pmin = 0, .pmax = 10000 },
+ [HSC160MG] = { .pmin = 0, .pmax = 16000 },
+ [HSC250MG] = { .pmin = 0, .pmax = 25000 },
+ [HSC400MG] = { .pmin = 0, .pmax = 40000 },
+ [HSC600MG] = { .pmin = 0, .pmax = 60000 },
+ [HSC001BG] = { .pmin = 0, .pmax = 100000 },
+ [HSC1_6BG] = { .pmin = 0, .pmax = 160000 },
+ [HSC2_5BG] = { .pmin = 0, .pmax = 250000 },
+ [HSC004BG] = { .pmin = 0, .pmax = 400000 },
+ [HSC006BG] = { .pmin = 0, .pmax = 600000 },
+ [HSC010BG] = { .pmin = 0, .pmax = 1000000 },
+ [HSC100KA] = { .pmin = 0, .pmax = 100000 },
+ [HSC160KA] = { .pmin = 0, .pmax = 160000 },
+ [HSC250KA] = { .pmin = 0, .pmax = 250000 },
+ [HSC400KA] = { .pmin = 0, .pmax = 400000 },
+ [HSC600KA] = { .pmin = 0, .pmax = 600000 },
+ [HSC001GA] = { .pmin = 0, .pmax = 1000000 },
+ [HSC160LD] = { .pmin = -160, .pmax = 160 },
+ [HSC250LD] = { .pmin = -250, .pmax = 250 },
+ [HSC400LD] = { .pmin = -400, .pmax = 400 },
+ [HSC600LD] = { .pmin = -600, .pmax = 600 },
+ [HSC001KD] = { .pmin = -1000, .pmax = 1000 },
+ [HSC1_6KD] = { .pmin = -1600, .pmax = 1600 },
+ [HSC2_5KD] = { .pmin = -2500, .pmax = 2500 },
+ [HSC004KD] = { .pmin = -4000, .pmax = 4000 },
+ [HSC006KD] = { .pmin = -6000, .pmax = 6000 },
+ [HSC010KD] = { .pmin = -10000, .pmax = 10000 },
+ [HSC016KD] = { .pmin = -16000, .pmax = 16000 },
+ [HSC025KD] = { .pmin = -25000, .pmax = 25000 },
+ [HSC040KD] = { .pmin = -40000, .pmax = 40000 },
+ [HSC060KD] = { .pmin = -60000, .pmax = 60000 },
+ [HSC100KD] = { .pmin = -100000, .pmax = 100000 },
+ [HSC160KD] = { .pmin = -160000, .pmax = 160000 },
+ [HSC250KD] = { .pmin = -250000, .pmax = 250000 },
+ [HSC400KD] = { .pmin = -400000, .pmax = 400000 },
+ [HSC250LG] = { .pmin = 0, .pmax = 250 },
+ [HSC400LG] = { .pmin = 0, .pmax = 400 },
+ [HSC600LG] = { .pmin = 0, .pmax = 600 },
+ [HSC001KG] = { .pmin = 0, .pmax = 1000 },
+ [HSC1_6KG] = { .pmin = 0, .pmax = 1600 },
+ [HSC2_5KG] = { .pmin = 0, .pmax = 2500 },
+ [HSC004KG] = { .pmin = 0, .pmax = 4000 },
+ [HSC006KG] = { .pmin = 0, .pmax = 6000 },
+ [HSC010KG] = { .pmin = 0, .pmax = 10000 },
+ [HSC016KG] = { .pmin = 0, .pmax = 16000 },
+ [HSC025KG] = { .pmin = 0, .pmax = 25000 },
+ [HSC040KG] = { .pmin = 0, .pmax = 40000 },
+ [HSC060KG] = { .pmin = 0, .pmax = 60000 },
+ [HSC100KG] = { .pmin = 0, .pmax = 100000 },
+ [HSC160KG] = { .pmin = 0, .pmax = 160000 },
+ [HSC250KG] = { .pmin = 0, .pmax = 250000 },
+ [HSC400KG] = { .pmin = 0, .pmax = 400000 },
+ [HSC600KG] = { .pmin = 0, .pmax = 600000 },
+ [HSC001GG] = { .pmin = 0, .pmax = 1000000 },
+ [HSC015PA] = { .pmin = 0, .pmax = 103421 },
+ [HSC030PA] = { .pmin = 0, .pmax = 206843 },
+ [HSC060PA] = { .pmin = 0, .pmax = 413685 },
+ [HSC100PA] = { .pmin = 0, .pmax = 689476 },
+ [HSC150PA] = { .pmin = 0, .pmax = 1034214 },
+ [HSC0_5ND] = { .pmin = -125, .pmax = 125 },
+ [HSC001ND] = { .pmin = -249, .pmax = 249 },
+ [HSC002ND] = { .pmin = -498, .pmax = 498 },
+ [HSC004ND] = { .pmin = -996, .pmax = 996 },
+ [HSC005ND] = { .pmin = -1245, .pmax = 1245 },
+ [HSC010ND] = { .pmin = -2491, .pmax = 2491 },
+ [HSC020ND] = { .pmin = -4982, .pmax = 4982 },
+ [HSC030ND] = { .pmin = -7473, .pmax = 7473 },
+ [HSC001PD] = { .pmin = -6895, .pmax = 6895 },
+ [HSC005PD] = { .pmin = -34474, .pmax = 34474 },
+ [HSC015PD] = { .pmin = -103421, .pmax = 103421 },
+ [HSC030PD] = { .pmin = -206843, .pmax = 206843 },
+ [HSC060PD] = { .pmin = -413685, .pmax = 413685 },
+ [HSC001NG] = { .pmin = 0, .pmax = 249 },
+ [HSC002NG] = { .pmin = 0, .pmax = 498 },
+ [HSC004NG] = { .pmin = 0, .pmax = 996 },
+ [HSC005NG] = { .pmin = 0, .pmax = 1245 },
+ [HSC010NG] = { .pmin = 0, .pmax = 2491 },
+ [HSC020NG] = { .pmin = 0, .pmax = 4982 },
+ [HSC030NG] = { .pmin = 0, .pmax = 7473 },
+ [HSC001PG] = { .pmin = 0, .pmax = 6895 },
+ [HSC005PG] = { .pmin = 0, .pmax = 34474 },
+ [HSC015PG] = { .pmin = 0, .pmax = 103421 },
+ [HSC030PG] = { .pmin = 0, .pmax = 206843 },
+ [HSC060PG] = { .pmin = 0, .pmax = 413685 },
+ [HSC100PG] = { .pmin = 0, .pmax = 689476 },
+ [HSC150PG] = { .pmin = 0, .pmax = 1034214 },
+};
+
+/**
+ * hsc_measurement_is_valid() - validate last conversion via status bits
+ * @data: structure containing instantiated sensor data
+ * Return: true only if both status bits are zero
+ *
+ * the two MSB from the first transfered byte contain a status code
+ * 00 - normal operation, valid data
+ * 01 - device in factory programming mode
+ * 10 - stale data
+ * 11 - diagnostic condition
+ */
+static bool hsc_measurement_is_valid(struct hsc_data *data)
+{
+ return !(data->buffer[0] & HSC_STATUS_MASK);
+}
+
+static int hsc_get_measurement(struct hsc_data *data)
+{
+ const struct hsc_chip_data *chip = data->chip;
+ int ret;
+
+ ret = data->recv_cb(data);
+ if (ret < 0)
+ return ret;
+
+ data->is_valid = chip->valid(data);
+ if (!data->is_valid)
+ return -EAGAIN;
+
+ return 0;
+}
+
+/*
+ * IIO ABI expects
+ * value = (conv + offset) * scale
+ *
+ * datasheet provides the following formula for determining the temperature
+ * temp[C] = conv * a + b
+ * where a = 200/2047; b = -50
+ *
+ * temp[C] = (conv + (b/a)) * a * (1000)
+ * =>
+ * scale = a * 1000 = .097703957 * 1000 = 97.703957
+ * offset = b/a = -50 / .097703957 = -50000000 / 97704
+ *
+ * based on the datasheet
+ * pressure = (conv - Omin) * Q + Pmin =
+ * ((conv - Omin) + Pmin/Q) * Q
+ * =>
+ * scale = Q = (Pmax - Pmin) / (Omax - Omin)
+ * offset = Pmin/Q - Omin = Pmin * (Omax - Omin) / (Pmax - Pmin) - Omin
+ */
+static int hsc_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct hsc_data *data = iio_priv(indio_dev);
+ int ret;
+ u32 recvd;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = hsc_get_measurement(data);
+ if (ret)
+ return ret;
+
+ recvd = get_unaligned_be32(data->buffer);
+ switch (channel->type) {
+ case IIO_PRESSURE:
+ *val = FIELD_GET(HSC_PRESSURE_MASK, recvd);
+ return IIO_VAL_INT;
+ case IIO_TEMP:
+ *val = FIELD_GET(HSC_TEMPERATURE_MASK, recvd);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_CHAN_INFO_SCALE:
+ switch (channel->type) {
+ case IIO_TEMP:
+ *val = 97;
+ *val2 = 703957;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_PRESSURE:
+ *val = data->p_scale;
+ *val2 = data->p_scale_dec;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+
+ case IIO_CHAN_INFO_OFFSET:
+ switch (channel->type) {
+ case IIO_TEMP:
+ *val = -50000000;
+ *val2 = 97704;
+ return IIO_VAL_FRACTIONAL;
+ case IIO_PRESSURE:
+ *val = data->p_offset;
+ *val2 = data->p_offset_dec;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec hsc_channels[] = {
+ {
+ .type = IIO_PRESSURE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ },
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE) |
+ BIT(IIO_CHAN_INFO_OFFSET),
+ },
+};
+
+static const struct iio_info hsc_info = {
+ .read_raw = hsc_read_raw,
+};
+
+static const struct hsc_chip_data hsc_chip = {
+ .valid = hsc_measurement_is_valid,
+ .channels = hsc_channels,
+ .num_channels = ARRAY_SIZE(hsc_channels),
+};
+
+int hsc_common_probe(struct device *dev, hsc_recv_fn recv)
+{
+ struct hsc_data *hsc;
+ struct iio_dev *indio_dev;
+ const char *triplet;
+ u64 tmp;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*hsc));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ hsc = iio_priv(indio_dev);
+
+ hsc->chip = &hsc_chip;
+ hsc->recv_cb = recv;
+ hsc->dev = dev;
+
+ ret = device_property_read_u32(dev, "honeywell,transfer-function",
+ &hsc->function);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "honeywell,transfer-function could not be read\n");
+ if (hsc->function > HSC_FUNCTION_F)
+ return dev_err_probe(dev, -EINVAL,
+ "honeywell,transfer-function %d invalid\n",
+ hsc->function);
+
+ ret = device_property_read_string(dev, "honeywell,pressure-triplet",
+ &triplet);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "honeywell,pressure-triplet could not be read\n");
+
+ if (str_has_prefix(triplet, "NA")) {
+ ret = device_property_read_u32(dev, "honeywell,pmin-pascal",
+ &hsc->pmin);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "honeywell,pmin-pascal could not be read\n");
+
+ ret = device_property_read_u32(dev, "honeywell,pmax-pascal",
+ &hsc->pmax);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "honeywell,pmax-pascal could not be read\n");
+ } else {
+ ret = device_property_match_property_string(dev,
+ "honeywell,pressure-triplet",
+ hsc_triplet_variants,
+ HSC_VARIANTS_MAX);
+ if (ret < 0)
+ return dev_err_probe(dev, -EINVAL,
+ "honeywell,pressure-triplet is invalid\n");
+
+ hsc->pmin = hsc_range_config[ret].pmin;
+ hsc->pmax = hsc_range_config[ret].pmax;
+ }
+
+ if (hsc->pmin >= hsc->pmax)
+ return dev_err_probe(dev, -EINVAL,
+ "pressure limits are invalid\n");
+
+ ret = devm_regulator_get_enable(dev, "vdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "can't get vdd supply\n");
+
+ hsc->outmin = hsc_func_spec[hsc->function].output_min;
+ hsc->outmax = hsc_func_spec[hsc->function].output_max;
+
+ tmp = div_s64(((s64)(hsc->pmax - hsc->pmin)) * MICRO,
+ hsc->outmax - hsc->outmin);
+ hsc->p_scale = div_s64_rem(tmp, NANO, &hsc->p_scale_dec);
+ tmp = div_s64(((s64)hsc->pmin * (s64)(hsc->outmax - hsc->outmin)) * MICRO,
+ hsc->pmax - hsc->pmin);
+ tmp -= (s64)hsc->outmin * MICRO;
+ hsc->p_offset = div_s64_rem(tmp, MICRO, &hsc->p_offset_dec);
+
+ indio_dev->name = "hsc030pa";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &hsc_info;
+ indio_dev->channels = hsc->chip->channels;
+ indio_dev->num_channels = hsc->chip->num_channels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS(hsc_common_probe, IIO_HONEYWELL_HSC030PA);
+
+MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>");
+MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor core driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/pressure/hsc030pa.h b/drivers/iio/pressure/hsc030pa.h
new file mode 100644
index 0000000000..d20420dba4
--- /dev/null
+++ b/drivers/iio/pressure/hsc030pa.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Honeywell TruStability HSC Series pressure/temperature sensor
+ *
+ * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro>
+ */
+
+#ifndef _HSC030PA_H
+#define _HSC030PA_H
+
+#include <linux/types.h>
+
+#define HSC_REG_MEASUREMENT_RD_SIZE 4
+
+struct device;
+
+struct iio_chan_spec;
+struct iio_dev;
+
+struct hsc_data;
+struct hsc_chip_data;
+
+typedef int (*hsc_recv_fn)(struct hsc_data *);
+
+/**
+ * struct hsc_data
+ * @dev: current device structure
+ * @chip: structure containing chip's channel properties
+ * @recv_cb: function that implements the chip reads
+ * @is_valid: true if last transfer has been validated
+ * @pmin: minimum measurable pressure limit
+ * @pmax: maximum measurable pressure limit
+ * @outmin: minimum raw pressure in counts (based on transfer function)
+ * @outmax: maximum raw pressure in counts (based on transfer function)
+ * @function: transfer function
+ * @p_scale: pressure scale
+ * @p_scale_dec: pressure scale, decimal places
+ * @p_offset: pressure offset
+ * @p_offset_dec: pressure offset, decimal places
+ * @buffer: raw conversion data
+ */
+struct hsc_data {
+ struct device *dev;
+ const struct hsc_chip_data *chip;
+ hsc_recv_fn recv_cb;
+ bool is_valid;
+ s32 pmin;
+ s32 pmax;
+ u32 outmin;
+ u32 outmax;
+ u32 function;
+ s64 p_scale;
+ s32 p_scale_dec;
+ s64 p_offset;
+ s32 p_offset_dec;
+ u8 buffer[HSC_REG_MEASUREMENT_RD_SIZE] __aligned(IIO_DMA_MINALIGN);
+};
+
+struct hsc_chip_data {
+ bool (*valid)(struct hsc_data *data);
+ const struct iio_chan_spec *channels;
+ u8 num_channels;
+};
+
+enum hsc_func_id {
+ HSC_FUNCTION_A,
+ HSC_FUNCTION_B,
+ HSC_FUNCTION_C,
+ HSC_FUNCTION_F,
+};
+
+int hsc_common_probe(struct device *dev, hsc_recv_fn recv);
+
+#endif
diff --git a/drivers/iio/pressure/hsc030pa_i2c.c b/drivers/iio/pressure/hsc030pa_i2c.c
new file mode 100644
index 0000000000..e2b524b364
--- /dev/null
+++ b/drivers/iio/pressure/hsc030pa_i2c.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Honeywell TruStability HSC Series pressure/temperature sensor
+ *
+ * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro>
+ *
+ * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/trustability-hsc-series/documents/sps-siot-trustability-hsc-series-high-accuracy-board-mount-pressure-sensors-50099148-a-en-ciid-151133.pdf [hsc]
+ * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/common/documents/sps-siot-i2c-comms-digital-output-pressure-sensors-tn-008201-3-en-ciid-45841.pdf [i2c related]
+ */
+
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+
+#include "hsc030pa.h"
+
+static int hsc_i2c_recv(struct hsc_data *data)
+{
+ struct i2c_client *client = to_i2c_client(data->dev);
+ struct i2c_msg msg;
+ int ret;
+
+ msg.addr = client->addr;
+ msg.flags = client->flags | I2C_M_RD;
+ msg.len = HSC_REG_MEASUREMENT_RD_SIZE;
+ msg.buf = data->buffer;
+
+ ret = i2c_transfer(client->adapter, &msg, 1);
+
+ return (ret == 2) ? 0 : ret;
+}
+
+static int hsc_i2c_probe(struct i2c_client *client)
+{
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EOPNOTSUPP;
+
+ return hsc_common_probe(&client->dev, hsc_i2c_recv);
+}
+
+static const struct of_device_id hsc_i2c_match[] = {
+ { .compatible = "honeywell,hsc030pa" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, hsc_i2c_match);
+
+static const struct i2c_device_id hsc_i2c_id[] = {
+ { "hsc030pa" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, hsc_i2c_id);
+
+static struct i2c_driver hsc_i2c_driver = {
+ .driver = {
+ .name = "hsc030pa",
+ .of_match_table = hsc_i2c_match,
+ },
+ .probe = hsc_i2c_probe,
+ .id_table = hsc_i2c_id,
+};
+module_i2c_driver(hsc_i2c_driver);
+
+MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>");
+MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor i2c driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_HONEYWELL_HSC030PA);
diff --git a/drivers/iio/pressure/hsc030pa_spi.c b/drivers/iio/pressure/hsc030pa_spi.c
new file mode 100644
index 0000000000..a719bade83
--- /dev/null
+++ b/drivers/iio/pressure/hsc030pa_spi.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Honeywell TruStability HSC Series pressure/temperature sensor
+ *
+ * Copyright (c) 2023 Petre Rodan <petre.rodan@subdimension.ro>
+ *
+ * Datasheet: https://prod-edam.honeywell.com/content/dam/honeywell-edam/sps/siot/en-us/products/sensors/pressure-sensors/board-mount-pressure-sensors/trustability-hsc-series/documents/sps-siot-trustability-hsc-series-high-accuracy-board-mount-pressure-sensors-50099148-a-en-ciid-151133.pdf
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/stddef.h>
+
+#include <linux/iio/iio.h>
+
+#include "hsc030pa.h"
+
+static int hsc_spi_recv(struct hsc_data *data)
+{
+ struct spi_device *spi = to_spi_device(data->dev);
+ struct spi_transfer xfer = {
+ .tx_buf = NULL,
+ .rx_buf = data->buffer,
+ .len = HSC_REG_MEASUREMENT_RD_SIZE,
+ };
+
+ return spi_sync_transfer(spi, &xfer, 1);
+}
+
+static int hsc_spi_probe(struct spi_device *spi)
+{
+ return hsc_common_probe(&spi->dev, hsc_spi_recv);
+}
+
+static const struct of_device_id hsc_spi_match[] = {
+ { .compatible = "honeywell,hsc030pa" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, hsc_spi_match);
+
+static const struct spi_device_id hsc_spi_id[] = {
+ { "hsc030pa" },
+ {}
+};
+MODULE_DEVICE_TABLE(spi, hsc_spi_id);
+
+static struct spi_driver hsc_spi_driver = {
+ .driver = {
+ .name = "hsc030pa",
+ .of_match_table = hsc_spi_match,
+ },
+ .probe = hsc_spi_probe,
+ .id_table = hsc_spi_id,
+};
+module_spi_driver(hsc_spi_driver);
+
+MODULE_AUTHOR("Petre Rodan <petre.rodan@subdimension.ro>");
+MODULE_DESCRIPTION("Honeywell HSC and SSC pressure sensor spi driver");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS(IIO_HONEYWELL_HSC030PA);
diff --git a/drivers/iio/proximity/irsd200.c b/drivers/iio/proximity/irsd200.c
index bdff91f6b1..323ac6dac9 100644
--- a/drivers/iio/proximity/irsd200.c
+++ b/drivers/iio/proximity/irsd200.c
@@ -7,7 +7,6 @@
#include <asm/unaligned.h>
#include <linux/bitfield.h>
-#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/regmap.h>
diff --git a/drivers/iio/proximity/sx9324.c b/drivers/iio/proximity/sx9324.c
index 438f9c9aba..ac2ed2da21 100644
--- a/drivers/iio/proximity/sx9324.c
+++ b/drivers/iio/proximity/sx9324.c
@@ -888,7 +888,6 @@ sx9324_get_default_reg(struct device *dev, int idx,
char prop[] = SX9324_PROXRAW_DEF;
u32 start = 0, raw = 0, pos = 0;
int ret, count, ph, pin;
- const char *res;
memcpy(reg_def, &sx9324_default_regs[idx], sizeof(*reg_def));
@@ -915,24 +914,21 @@ sx9324_get_default_reg(struct device *dev, int idx,
reg_def->def = raw;
break;
case SX9324_REG_AFE_CTRL0:
- ret = device_property_read_string(dev,
- "semtech,cs-idle-sleep", &res);
- if (!ret)
- ret = match_string(sx9324_csidle, ARRAY_SIZE(sx9324_csidle), res);
+ ret = device_property_match_property_string(dev, "semtech,cs-idle-sleep",
+ sx9324_csidle,
+ ARRAY_SIZE(sx9324_csidle));
if (ret >= 0) {
reg_def->def &= ~SX9324_REG_AFE_CTRL0_CSIDLE_MASK;
reg_def->def |= ret << SX9324_REG_AFE_CTRL0_CSIDLE_SHIFT;
}
- ret = device_property_read_string(dev,
- "semtech,int-comp-resistor", &res);
- if (ret)
- break;
- ret = match_string(sx9324_rints, ARRAY_SIZE(sx9324_rints), res);
- if (ret < 0)
- break;
- reg_def->def &= ~SX9324_REG_AFE_CTRL0_RINT_MASK;
- reg_def->def |= ret << SX9324_REG_AFE_CTRL0_RINT_SHIFT;
+ ret = device_property_match_property_string(dev, "semtech,int-comp-resistor",
+ sx9324_rints,
+ ARRAY_SIZE(sx9324_rints));
+ if (ret >= 0) {
+ reg_def->def &= ~SX9324_REG_AFE_CTRL0_RINT_MASK;
+ reg_def->def |= ret << SX9324_REG_AFE_CTRL0_RINT_SHIFT;
+ }
break;
case SX9324_REG_AFE_CTRL4:
case SX9324_REG_AFE_CTRL7:
diff --git a/drivers/iio/resolver/ad2s1210.c b/drivers/iio/resolver/ad2s1210.c
index 1bd1b950e7..a414eef12e 100644
--- a/drivers/iio/resolver/ad2s1210.c
+++ b/drivers/iio/resolver/ad2s1210.c
@@ -141,7 +141,7 @@ struct ad2s1210_state {
struct spi_device *sdev;
/** GPIO pin connected to SAMPLE line. */
struct gpio_desc *sample_gpio;
- /** GPIO pins connected to A0 and A1 lines. */
+ /** GPIO pins connected to A0 and A1 lines (optional). */
struct gpio_descs *mode_gpios;
/** Used to access config registers. */
struct regmap *regmap;
@@ -149,6 +149,8 @@ struct ad2s1210_state {
unsigned long clkin_hz;
/** Available raw hysteresis values based on resolution. */
int hysteresis_available[2];
+ /* adi,fixed-mode property - only valid when mode_gpios == NULL. */
+ enum ad2s1210_mode fixed_mode;
/** The selected resolution */
enum ad2s1210_resolution resolution;
/** Copy of fault register from the previous read. */
@@ -175,6 +177,9 @@ static int ad2s1210_set_mode(struct ad2s1210_state *st, enum ad2s1210_mode mode)
struct gpio_descs *gpios = st->mode_gpios;
DECLARE_BITMAP(bitmap, 2);
+ if (!gpios)
+ return mode == st->fixed_mode ? 0 : -EOPNOTSUPP;
+
bitmap[0] = mode;
return gpiod_set_array_value(gpios->ndescs, gpios->desc, gpios->info,
@@ -276,7 +281,8 @@ static int ad2s1210_regmap_reg_read(void *context, unsigned int reg,
* parity error. The fault register is read-only and the D7 bit means
* something else there.
*/
- if (reg != AD2S1210_REG_FAULT && st->rx[1] & AD2S1210_ADDRESS_DATA)
+ if ((reg > AD2S1210_REG_VELOCITY_LSB && reg != AD2S1210_REG_FAULT)
+ && st->rx[1] & AD2S1210_ADDRESS_DATA)
return -EBADMSG;
*val = st->rx[1];
@@ -450,21 +456,53 @@ static int ad2s1210_single_conversion(struct iio_dev *indio_dev,
ad2s1210_toggle_sample_line(st);
timestamp = iio_get_time_ns(indio_dev);
- switch (chan->type) {
- case IIO_ANGL:
- ret = ad2s1210_set_mode(st, MOD_POS);
- break;
- case IIO_ANGL_VEL:
- ret = ad2s1210_set_mode(st, MOD_VEL);
- break;
- default:
- return -EINVAL;
+ if (st->fixed_mode == MOD_CONFIG) {
+ unsigned int reg_val;
+
+ switch (chan->type) {
+ case IIO_ANGL:
+ ret = regmap_bulk_read(st->regmap,
+ AD2S1210_REG_POSITION_MSB,
+ &st->sample.raw, 2);
+ if (ret < 0)
+ return ret;
+
+ break;
+ case IIO_ANGL_VEL:
+ ret = regmap_bulk_read(st->regmap,
+ AD2S1210_REG_VELOCITY_MSB,
+ &st->sample.raw, 2);
+ if (ret < 0)
+ return ret;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, &reg_val);
+ if (ret < 0)
+ return ret;
+
+ st->sample.fault = reg_val;
+ } else {
+ switch (chan->type) {
+ case IIO_ANGL:
+ ret = ad2s1210_set_mode(st, MOD_POS);
+ break;
+ case IIO_ANGL_VEL:
+ ret = ad2s1210_set_mode(st, MOD_VEL);
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (ret < 0)
+ return ret;
+
+ ret = spi_read(st->sdev, &st->sample, 3);
+ if (ret < 0)
+ return ret;
}
- if (ret < 0)
- return ret;
- ret = spi_read(st->sdev, &st->sample, 3);
- if (ret < 0)
- return ret;
switch (chan->type) {
case IIO_ANGL:
@@ -1252,27 +1290,53 @@ static irqreturn_t ad2s1210_trigger_handler(int irq, void *p)
ad2s1210_toggle_sample_line(st);
if (test_bit(0, indio_dev->active_scan_mask)) {
- ret = ad2s1210_set_mode(st, MOD_POS);
- if (ret < 0)
- goto error_ret;
-
- ret = spi_read(st->sdev, &st->sample, 3);
- if (ret < 0)
- goto error_ret;
+ if (st->fixed_mode == MOD_CONFIG) {
+ ret = regmap_bulk_read(st->regmap,
+ AD2S1210_REG_POSITION_MSB,
+ &st->sample.raw, 2);
+ if (ret < 0)
+ goto error_ret;
+ } else {
+ ret = ad2s1210_set_mode(st, MOD_POS);
+ if (ret < 0)
+ goto error_ret;
+
+ ret = spi_read(st->sdev, &st->sample, 3);
+ if (ret < 0)
+ goto error_ret;
+ }
memcpy(&st->scan.chan[chan++], &st->sample.raw, 2);
}
if (test_bit(1, indio_dev->active_scan_mask)) {
- ret = ad2s1210_set_mode(st, MOD_VEL);
- if (ret < 0)
- goto error_ret;
+ if (st->fixed_mode == MOD_CONFIG) {
+ ret = regmap_bulk_read(st->regmap,
+ AD2S1210_REG_VELOCITY_MSB,
+ &st->sample.raw, 2);
+ if (ret < 0)
+ goto error_ret;
+ } else {
+ ret = ad2s1210_set_mode(st, MOD_VEL);
+ if (ret < 0)
+ goto error_ret;
+
+ ret = spi_read(st->sdev, &st->sample, 3);
+ if (ret < 0)
+ goto error_ret;
+ }
- ret = spi_read(st->sdev, &st->sample, 3);
+ memcpy(&st->scan.chan[chan++], &st->sample.raw, 2);
+ }
+
+ if (st->fixed_mode == MOD_CONFIG) {
+ unsigned int reg_val;
+
+ ret = regmap_read(st->regmap, AD2S1210_REG_FAULT, &reg_val);
if (ret < 0)
- goto error_ret;
+ return ret;
- memcpy(&st->scan.chan[chan++], &st->sample.raw, 2);
+ st->sample.fault = reg_val;
}
ad2s1210_push_events(indio_dev, st->sample.fault, pf->timestamp);
@@ -1299,9 +1363,24 @@ static const struct iio_info ad2s1210_info = {
static int ad2s1210_setup_properties(struct ad2s1210_state *st)
{
struct device *dev = &st->sdev->dev;
+ const char *str_val;
u32 val;
int ret;
+ ret = device_property_read_string(dev, "adi,fixed-mode", &str_val);
+ if (ret == -EINVAL)
+ st->fixed_mode = -1;
+ else if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to read adi,fixed-mode property\n");
+ else {
+ if (strcmp(str_val, "config"))
+ return dev_err_probe(dev, -EINVAL,
+ "only adi,fixed-mode=\"config\" is supported\n");
+
+ st->fixed_mode = MOD_CONFIG;
+ }
+
ret = device_property_read_u32(dev, "assigned-resolution-bits", &val);
if (ret < 0)
return dev_err_probe(dev, ret,
@@ -1347,6 +1426,7 @@ static int ad2s1210_setup_gpios(struct ad2s1210_state *st)
{
struct device *dev = &st->sdev->dev;
struct gpio_descs *resolution_gpios;
+ struct gpio_desc *reset_gpio;
DECLARE_BITMAP(bitmap, 2);
int ret;
@@ -1357,12 +1437,21 @@ static int ad2s1210_setup_gpios(struct ad2s1210_state *st)
"failed to request sample GPIO\n");
/* both pins high means that we start in config mode */
- st->mode_gpios = devm_gpiod_get_array(dev, "mode", GPIOD_OUT_HIGH);
+ st->mode_gpios = devm_gpiod_get_array_optional(dev, "mode",
+ GPIOD_OUT_HIGH);
if (IS_ERR(st->mode_gpios))
return dev_err_probe(dev, PTR_ERR(st->mode_gpios),
"failed to request mode GPIOs\n");
- if (st->mode_gpios->ndescs != 2)
+ if (!st->mode_gpios && st->fixed_mode == -1)
+ return dev_err_probe(dev, -EINVAL,
+ "must specify either adi,fixed-mode or mode-gpios\n");
+
+ if (st->mode_gpios && st->fixed_mode != -1)
+ return dev_err_probe(dev, -EINVAL,
+ "must specify only one of adi,fixed-mode or mode-gpios\n");
+
+ if (st->mode_gpios && st->mode_gpios->ndescs != 2)
return dev_err_probe(dev, -EINVAL,
"requires exactly 2 mode-gpios\n");
@@ -1393,6 +1482,17 @@ static int ad2s1210_setup_gpios(struct ad2s1210_state *st)
"failed to set resolution gpios\n");
}
+ /* If the optional reset GPIO is present, toggle it to do a hard reset. */
+ reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(reset_gpio),
+ "failed to request reset GPIO\n");
+
+ if (reset_gpio) {
+ udelay(10);
+ gpiod_set_value(reset_gpio, 0);
+ }
+
return 0;
}
diff --git a/drivers/iio/temperature/Kconfig b/drivers/iio/temperature/Kconfig
index ed384f33e0..ed0e496336 100644
--- a/drivers/iio/temperature/Kconfig
+++ b/drivers/iio/temperature/Kconfig
@@ -76,6 +76,18 @@ config MLX90632
This driver can also be built as a module. If so, the module will
be called mlx90632.
+config MLX90635
+ tristate "MLX90635 contact-less infrared sensor with medical accuracy"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Melexis
+ MLX90635 contact-less infrared sensor with medical accuracy
+ connected with I2C.
+
+ This driver can also be built as a module. If so, the module will
+ be called mlx90635.
+
config TMP006
tristate "TMP006 infrared thermopile sensor"
depends on I2C
@@ -158,4 +170,14 @@ config MAX31865
This driver can also be build as a module. If so, the module
will be called max31865.
+config MCP9600
+ tristate "MCP9600 thermocouple EMF converter"
+ depends on I2C
+ help
+ If you say yes here you get support for MCP9600
+ thermocouple EMF converter connected via I2C.
+
+ This driver can also be built as a module. If so, the module
+ will be called mcp9600.
+
endmenu
diff --git a/drivers/iio/temperature/Makefile b/drivers/iio/temperature/Makefile
index dfec8c6d30..07d6e65709 100644
--- a/drivers/iio/temperature/Makefile
+++ b/drivers/iio/temperature/Makefile
@@ -10,8 +10,10 @@ obj-$(CONFIG_MAXIM_THERMOCOUPLE) += maxim_thermocouple.o
obj-$(CONFIG_MAX30208) += max30208.o
obj-$(CONFIG_MAX31856) += max31856.o
obj-$(CONFIG_MAX31865) += max31865.o
+obj-$(CONFIG_MCP9600) += mcp9600.o
obj-$(CONFIG_MLX90614) += mlx90614.o
obj-$(CONFIG_MLX90632) += mlx90632.o
+obj-$(CONFIG_MLX90632) += mlx90635.o
obj-$(CONFIG_TMP006) += tmp006.o
obj-$(CONFIG_TMP007) += tmp007.o
obj-$(CONFIG_TMP117) += tmp117.o
diff --git a/drivers/iio/temperature/mcp9600.c b/drivers/iio/temperature/mcp9600.c
new file mode 100644
index 0000000000..4684580429
--- /dev/null
+++ b/drivers/iio/temperature/mcp9600.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * mcp9600.c - Support for Microchip MCP9600 thermocouple EMF converter
+ *
+ * Copyright (c) 2022 Andrew Hepp
+ * Author: <andrew.hepp@ahepp.dev>
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+
+#include <linux/iio/iio.h>
+
+/* MCP9600 registers */
+#define MCP9600_HOT_JUNCTION 0x0
+#define MCP9600_COLD_JUNCTION 0x2
+#define MCP9600_DEVICE_ID 0x20
+
+/* MCP9600 device id value */
+#define MCP9600_DEVICE_ID_MCP9600 0x40
+
+static const struct iio_chan_spec mcp9600_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .address = MCP9600_HOT_JUNCTION,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ },
+ {
+ .type = IIO_TEMP,
+ .address = MCP9600_COLD_JUNCTION,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .modified = 1,
+ .info_mask_separate =
+ BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE),
+ },
+};
+
+struct mcp9600_data {
+ struct i2c_client *client;
+};
+
+static int mcp9600_read(struct mcp9600_data *data,
+ struct iio_chan_spec const *chan, int *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_word_swapped(data->client, chan->address);
+
+ if (ret < 0)
+ return ret;
+ *val = ret;
+
+ return 0;
+}
+
+static int mcp9600_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct mcp9600_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = mcp9600_read(data, chan, val);
+ if (ret)
+ return ret;
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = 62;
+ *val2 = 500000;
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info mcp9600_info = {
+ .read_raw = mcp9600_read_raw,
+};
+
+static int mcp9600_probe(struct i2c_client *client)
+{
+ struct iio_dev *indio_dev;
+ struct mcp9600_data *data;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, MCP9600_DEVICE_ID);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "Failed to read device ID\n");
+ if (ret != MCP9600_DEVICE_ID_MCP9600)
+ dev_warn(&client->dev, "Expected ID %x, got %x\n",
+ MCP9600_DEVICE_ID_MCP9600, ret);
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->client = client;
+
+ indio_dev->info = &mcp9600_info;
+ indio_dev->name = "mcp9600";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = mcp9600_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mcp9600_channels);
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id mcp9600_id[] = {
+ { "mcp9600" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, mcp9600_id);
+
+static const struct of_device_id mcp9600_of_match[] = {
+ { .compatible = "microchip,mcp9600" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mcp9600_of_match);
+
+static struct i2c_driver mcp9600_driver = {
+ .driver = {
+ .name = "mcp9600",
+ .of_match_table = mcp9600_of_match,
+ },
+ .probe = mcp9600_probe,
+ .id_table = mcp9600_id
+};
+module_i2c_driver(mcp9600_driver);
+
+MODULE_AUTHOR("Andrew Hepp <andrew.hepp@ahepp.dev>");
+MODULE_DESCRIPTION("Microchip MCP9600 thermocouple EMF converter driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/temperature/mlx90635.c b/drivers/iio/temperature/mlx90635.c
new file mode 100644
index 0000000000..1f5c962c18
--- /dev/null
+++ b/drivers/iio/temperature/mlx90635.c
@@ -0,0 +1,1097 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mlx90635.c - Melexis MLX90635 contactless IR temperature sensor
+ *
+ * Copyright (c) 2023 Melexis <cmo@melexis.com>
+ *
+ * Driver for the Melexis MLX90635 I2C 16-bit IR thermopile sensor
+ */
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/limits.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/math64.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
+
+/* Memory sections addresses */
+#define MLX90635_ADDR_RAM 0x0000 /* Start address of ram */
+#define MLX90635_ADDR_EEPROM 0x0018 /* Start address of user eeprom */
+
+/* EEPROM addresses - used at startup */
+#define MLX90635_EE_I2C_CFG 0x0018 /* I2C address register initial value */
+#define MLX90635_EE_CTRL1 0x001A /* Control register1 initial value */
+#define MLX90635_EE_CTRL2 0x001C /* Control register2 initial value */
+
+#define MLX90635_EE_Ha 0x001E /* Ha customer calib value reg 16bit */
+#define MLX90635_EE_Hb 0x0020 /* Hb customer calib value reg 16bit */
+#define MLX90635_EE_Fa 0x0026 /* Fa calibration register 32bit */
+#define MLX90635_EE_FASCALE 0x002A /* Scaling coefficient for Fa register 16bit */
+#define MLX90635_EE_Ga 0x002C /* Ga calibration register 16bit */
+#define MLX90635_EE_Fb 0x002E /* Fb calibration register 16bit */
+#define MLX90635_EE_Ea 0x0030 /* Ea calibration register 32bit */
+#define MLX90635_EE_Eb 0x0034 /* Eb calibration register 32bit */
+#define MLX90635_EE_P_G 0x0038 /* P_G calibration register 16bit */
+#define MLX90635_EE_P_O 0x003A /* P_O calibration register 16bit */
+#define MLX90635_EE_Aa 0x003C /* Aa calibration register 16bit */
+#define MLX90635_EE_VERSION 0x003E /* Version bits 4:7 and 12:15 */
+#define MLX90635_EE_Gb 0x0040 /* Gb calibration register 16bit */
+
+/* Device status register - volatile */
+#define MLX90635_REG_STATUS 0x0000
+#define MLX90635_STAT_BUSY BIT(6) /* Device busy indicator */
+#define MLX90635_STAT_BRST BIT(5) /* Brown out reset indicator */
+#define MLX90635_STAT_CYCLE_POS GENMASK(4, 2) /* Data position */
+#define MLX90635_STAT_END_CONV BIT(1) /* End of conversion indicator */
+#define MLX90635_STAT_DATA_RDY BIT(0) /* Data ready indicator */
+
+/* EEPROM control register address - volatile */
+#define MLX90635_REG_EE 0x000C
+#define MLX90635_EE_ACTIVE BIT(4) /* Power-on EEPROM */
+#define MLX90635_EE_BUSY_MASK BIT(15)
+
+#define MLX90635_REG_CMD 0x0010 /* Command register address */
+
+/* Control register1 address - volatile */
+#define MLX90635_REG_CTRL1 0x0014
+#define MLX90635_CTRL1_REFRESH_RATE_MASK GENMASK(2, 0)
+#define MLX90635_CTRL1_RES_CTRL_MASK GENMASK(4, 3)
+#define MLX90635_CTRL1_TABLE_MASK BIT(15) /* Table select */
+
+/* Control register2 address - volatile */
+#define MLX90635_REG_CTRL2 0x0016
+#define MLX90635_CTRL2_BURST_CNT_MASK GENMASK(10, 6) /* Burst count */
+#define MLX90635_CTRL2_MODE_MASK GENMASK(12, 11) /* Power mode */
+#define MLX90635_CTRL2_SOB_MASK BIT(15)
+
+/* PowerModes statuses */
+#define MLX90635_PWR_STATUS_HALT 0
+#define MLX90635_PWR_STATUS_SLEEP_STEP 1
+#define MLX90635_PWR_STATUS_STEP 2
+#define MLX90635_PWR_STATUS_CONTINUOUS 3
+
+/* Measurement data addresses */
+#define MLX90635_RESULT_1 0x0002
+#define MLX90635_RESULT_2 0x0004
+#define MLX90635_RESULT_3 0x0006
+#define MLX90635_RESULT_4 0x0008
+#define MLX90635_RESULT_5 0x000A
+
+/* Timings (ms) */
+#define MLX90635_TIMING_RST_MIN 200 /* Minimum time after addressed reset command */
+#define MLX90635_TIMING_RST_MAX 250 /* Maximum time after addressed reset command */
+#define MLX90635_TIMING_POLLING 10000 /* Time between bit polling*/
+#define MLX90635_TIMING_EE_ACTIVE_MIN 100 /* Minimum time after activating the EEPROM for read */
+#define MLX90635_TIMING_EE_ACTIVE_MAX 150 /* Maximum time after activating the EEPROM for read */
+
+/* Magic constants */
+#define MLX90635_ID_DSPv1 0x01 /* EEPROM DSP version */
+#define MLX90635_RESET_CMD 0x0006 /* Reset sensor (address or global) */
+#define MLX90635_MAX_MEAS_NUM 31 /* Maximum number of measurements in list */
+#define MLX90635_PTAT_DIV 12 /* Used to divide the PTAT value in pre-processing */
+#define MLX90635_IR_DIV 24 /* Used to divide the IR value in pre-processing */
+#define MLX90635_SLEEP_DELAY_MS 6000 /* Autosleep delay */
+#define MLX90635_MEAS_MAX_TIME 2000 /* Max measurement time in ms for the lowest refresh rate */
+#define MLX90635_READ_RETRIES 100 /* Number of read retries before quitting with timeout error */
+#define MLX90635_VERSION_MASK (GENMASK(15, 12) | GENMASK(7, 4))
+#define MLX90635_DSP_VERSION(reg) (((reg & GENMASK(14, 12)) >> 9) | ((reg & GENMASK(6, 4)) >> 4))
+#define MLX90635_DSP_FIXED BIT(15)
+
+
+/**
+ * struct mlx90635_data - private data for the MLX90635 device
+ * @client: I2C client of the device
+ * @lock: Internal mutex because multiple reads are needed for single triggered
+ * measurement to ensure data consistency
+ * @regmap: Regmap of the device registers
+ * @regmap_ee: Regmap of the device EEPROM which can be cached
+ * @emissivity: Object emissivity from 0 to 1000 where 1000 = 1
+ * @regulator: Regulator of the device
+ * @powerstatus: Current POWER status of the device
+ * @interaction_ts: Timestamp of the last temperature read that is used
+ * for power management in jiffies
+ */
+struct mlx90635_data {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct regmap *regmap;
+ struct regmap *regmap_ee;
+ u16 emissivity;
+ struct regulator *regulator;
+ int powerstatus;
+ unsigned long interaction_ts;
+};
+
+static const struct regmap_range mlx90635_volatile_reg_range[] = {
+ regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS),
+ regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5),
+ regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE),
+ regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD),
+ regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2),
+};
+
+static const struct regmap_access_table mlx90635_volatile_regs_tbl = {
+ .yes_ranges = mlx90635_volatile_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90635_volatile_reg_range),
+};
+
+static const struct regmap_range mlx90635_read_reg_range[] = {
+ regmap_reg_range(MLX90635_REG_STATUS, MLX90635_REG_STATUS),
+ regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5),
+ regmap_reg_range(MLX90635_REG_EE, MLX90635_REG_EE),
+ regmap_reg_range(MLX90635_REG_CMD, MLX90635_REG_CMD),
+ regmap_reg_range(MLX90635_REG_CTRL1, MLX90635_REG_CTRL2),
+};
+
+static const struct regmap_access_table mlx90635_readable_regs_tbl = {
+ .yes_ranges = mlx90635_read_reg_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90635_read_reg_range),
+};
+
+static const struct regmap_range mlx90635_no_write_reg_range[] = {
+ regmap_reg_range(MLX90635_RESULT_1, MLX90635_RESULT_5),
+};
+
+static const struct regmap_access_table mlx90635_writeable_regs_tbl = {
+ .no_ranges = mlx90635_no_write_reg_range,
+ .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_reg_range),
+};
+
+static const struct regmap_config mlx90635_regmap = {
+ .name = "mlx90635-registers",
+ .reg_stride = 1,
+ .reg_bits = 16,
+ .val_bits = 16,
+
+ .volatile_table = &mlx90635_volatile_regs_tbl,
+ .rd_table = &mlx90635_readable_regs_tbl,
+ .wr_table = &mlx90635_writeable_regs_tbl,
+
+ .use_single_read = true,
+ .use_single_write = true,
+ .can_multi_write = false,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct regmap_range mlx90635_read_ee_range[] = {
+ regmap_reg_range(MLX90635_EE_I2C_CFG, MLX90635_EE_CTRL2),
+ regmap_reg_range(MLX90635_EE_Ha, MLX90635_EE_Gb),
+};
+
+static const struct regmap_access_table mlx90635_readable_ees_tbl = {
+ .yes_ranges = mlx90635_read_ee_range,
+ .n_yes_ranges = ARRAY_SIZE(mlx90635_read_ee_range),
+};
+
+static const struct regmap_range mlx90635_no_write_ee_range[] = {
+ regmap_reg_range(MLX90635_ADDR_EEPROM, MLX90635_EE_Gb),
+};
+
+static const struct regmap_access_table mlx90635_writeable_ees_tbl = {
+ .no_ranges = mlx90635_no_write_ee_range,
+ .n_no_ranges = ARRAY_SIZE(mlx90635_no_write_ee_range),
+};
+
+static const struct regmap_config mlx90635_regmap_ee = {
+ .name = "mlx90635-eeprom",
+ .reg_stride = 1,
+ .reg_bits = 16,
+ .val_bits = 16,
+
+ .volatile_table = NULL,
+ .rd_table = &mlx90635_readable_ees_tbl,
+ .wr_table = &mlx90635_writeable_ees_tbl,
+
+ .use_single_read = true,
+ .use_single_write = true,
+ .can_multi_write = false,
+ .reg_format_endian = REGMAP_ENDIAN_BIG,
+ .val_format_endian = REGMAP_ENDIAN_BIG,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+/**
+ * mlx90635_reset_delay() - Give the mlx90635 some time to reset properly
+ * If this is not done, the following I2C command(s) will not be accepted.
+ */
+static void mlx90635_reset_delay(void)
+{
+ usleep_range(MLX90635_TIMING_RST_MIN, MLX90635_TIMING_RST_MAX);
+}
+
+static int mlx90635_pwr_sleep_step(struct mlx90635_data *data)
+{
+ int ret;
+
+ if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP)
+ return 0;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK,
+ FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_SLEEP_STEP));
+ if (ret < 0)
+ return ret;
+
+ data->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP;
+ return 0;
+}
+
+static int mlx90635_pwr_continuous(struct mlx90635_data *data)
+{
+ int ret;
+
+ if (data->powerstatus == MLX90635_PWR_STATUS_CONTINUOUS)
+ return 0;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2, MLX90635_CTRL2_MODE_MASK,
+ FIELD_PREP(MLX90635_CTRL2_MODE_MASK, MLX90635_PWR_STATUS_CONTINUOUS));
+ if (ret < 0)
+ return ret;
+
+ data->powerstatus = MLX90635_PWR_STATUS_CONTINUOUS;
+ return 0;
+}
+
+static int mlx90635_read_ee_register(struct regmap *regmap, u16 reg_lsb,
+ s32 *reg_value)
+{
+ unsigned int read;
+ u32 value;
+ int ret;
+
+ ret = regmap_read(regmap, reg_lsb + 2, &read);
+ if (ret < 0)
+ return ret;
+
+ value = read;
+
+ ret = regmap_read(regmap, reg_lsb, &read);
+ if (ret < 0)
+ return ret;
+
+ *reg_value = (read << 16) | (value & 0xffff);
+
+ return 0;
+}
+
+static int mlx90635_read_ee_ambient(struct regmap *regmap, s16 *PG, s16 *PO, s16 *Gb)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90635_EE_P_O, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *PO = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_P_G, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *PG = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Gb = (u16)read_tmp;
+
+ return 0;
+}
+
+static int mlx90635_read_ee_object(struct regmap *regmap, u32 *Ea, u32 *Eb, u32 *Fa, s16 *Fb,
+ s16 *Ga, s16 *Gb, s16 *Ha, s16 *Hb, u16 *Fa_scale)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Ea, Ea);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Eb, Eb);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_read_ee_register(regmap, MLX90635_EE_Fa, Fa);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, MLX90635_EE_Ha, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Ha = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Hb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Hb = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Ga, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Ga = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Gb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Gb = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_Fb, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Fb = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_EE_FASCALE, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *Fa_scale = (u16)read_tmp;
+
+ return 0;
+}
+
+static int mlx90635_calculate_dataset_ready_time(struct mlx90635_data *data, int *refresh_time)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, &reg);
+ if (ret < 0)
+ return ret;
+
+ *refresh_time = 2 * (MLX90635_MEAS_MAX_TIME >> FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg)) + 80;
+
+ return 0;
+}
+
+static int mlx90635_perform_measurement_burst(struct mlx90635_data *data)
+{
+ unsigned int reg_status;
+ int refresh_time;
+ int ret;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_STATUS,
+ MLX90635_STAT_END_CONV, MLX90635_STAT_END_CONV);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_calculate_dataset_ready_time(data, &refresh_time);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL2,
+ FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1),
+ FIELD_PREP(MLX90635_CTRL2_SOB_MASK, 1));
+ if (ret < 0)
+ return ret;
+
+ msleep(refresh_time); /* Wait minimum time for dataset to be ready */
+
+ ret = regmap_read_poll_timeout(data->regmap, MLX90635_REG_STATUS, reg_status,
+ (!(reg_status & MLX90635_STAT_END_CONV)) == 0,
+ MLX90635_TIMING_POLLING, MLX90635_READ_RETRIES * 10000);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "data not ready");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int mlx90635_read_ambient_raw(struct regmap *regmap,
+ s16 *ambient_new_raw, s16 *ambient_old_raw)
+{
+ unsigned int read_tmp;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_2, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_new_raw = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_3, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *ambient_old_raw = (s16)read_tmp;
+
+ return 0;
+}
+
+static int mlx90635_read_object_raw(struct regmap *regmap, s16 *object_raw)
+{
+ unsigned int read_tmp;
+ s16 read;
+ int ret;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_1, &read_tmp);
+ if (ret < 0)
+ return ret;
+
+ read = (s16)read_tmp;
+
+ ret = regmap_read(regmap, MLX90635_RESULT_4, &read_tmp);
+ if (ret < 0)
+ return ret;
+ *object_raw = (read - (s16)read_tmp) / 2;
+
+ return 0;
+}
+
+static int mlx90635_read_all_channel(struct mlx90635_data *data,
+ s16 *ambient_new_raw, s16 *ambient_old_raw,
+ s16 *object_raw)
+{
+ int ret;
+
+ mutex_lock(&data->lock);
+ if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) {
+ /* Trigger measurement in Sleep Step mode */
+ ret = mlx90635_perform_measurement_burst(data);
+ if (ret < 0)
+ goto read_unlock;
+ }
+
+ ret = mlx90635_read_ambient_raw(data->regmap, ambient_new_raw,
+ ambient_old_raw);
+ if (ret < 0)
+ goto read_unlock;
+
+ ret = mlx90635_read_object_raw(data->regmap, object_raw);
+read_unlock:
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static s64 mlx90635_preprocess_temp_amb(s16 ambient_new_raw,
+ s16 ambient_old_raw, s16 Gb)
+{
+ s64 VR_Ta, kGb, tmp;
+
+ kGb = ((s64)Gb * 1000LL) >> 10ULL;
+ VR_Ta = (s64)ambient_old_raw * 1000000LL +
+ kGb * div64_s64(((s64)ambient_new_raw * 1000LL),
+ (MLX90635_PTAT_DIV));
+ tmp = div64_s64(
+ div64_s64(((s64)ambient_new_raw * 1000000000000LL),
+ (MLX90635_PTAT_DIV)), VR_Ta);
+ return div64_s64(tmp << 19ULL, 1000LL);
+}
+
+static s64 mlx90635_preprocess_temp_obj(s16 object_raw,
+ s16 ambient_new_raw,
+ s16 ambient_old_raw, s16 Gb)
+{
+ s64 VR_IR, kGb, tmp;
+
+ kGb = ((s64)Gb * 1000LL) >> 10ULL;
+ VR_IR = (s64)ambient_old_raw * 1000000LL +
+ kGb * (div64_s64((s64)ambient_new_raw * 1000LL,
+ MLX90635_PTAT_DIV));
+ tmp = div64_s64(
+ div64_s64((s64)(object_raw * 1000000LL),
+ MLX90635_IR_DIV) * 1000000LL,
+ VR_IR);
+ return div64_s64((tmp << 19ULL), 1000LL);
+}
+
+static s32 mlx90635_calc_temp_ambient(s16 ambient_new_raw, s16 ambient_old_raw,
+ u16 P_G, u16 P_O, s16 Gb)
+{
+ s64 kPG, kPO, AMB;
+
+ AMB = mlx90635_preprocess_temp_amb(ambient_new_raw, ambient_old_raw,
+ Gb);
+ kPG = ((s64)P_G * 1000000LL) >> 9ULL;
+ kPO = AMB - (((s64)P_O * 1000LL) >> 1ULL);
+
+ return 30 * 1000LL + div64_s64(kPO * 1000000LL, kPG);
+}
+
+static s32 mlx90635_calc_temp_object_iteration(s32 prev_object_temp, s64 object,
+ s64 TAdut, s64 TAdut4, s16 Ga,
+ u32 Fa, u16 Fa_scale, s16 Fb,
+ s16 Ha, s16 Hb, u16 emissivity)
+{
+ s64 calcedGa, calcedGb, calcedFa, Alpha_corr;
+ s64 Ha_customer, Hb_customer;
+
+ Ha_customer = ((s64)Ha * 1000000LL) >> 14ULL;
+ Hb_customer = ((s64)Hb * 100) >> 10ULL;
+
+ calcedGa = ((s64)((s64)Ga * (prev_object_temp - 35 * 1000LL)
+ * 1000LL)) >> 24LL;
+ calcedGb = ((s64)(Fb * (TAdut - 30 * 1000000LL))) >> 24LL;
+
+ Alpha_corr = ((s64)((s64)Fa * Ha_customer * 10000LL) >> Fa_scale);
+ Alpha_corr *= ((s64)(1 * 1000000LL + calcedGa + calcedGb));
+
+ Alpha_corr = div64_s64(Alpha_corr, 1000LL);
+ Alpha_corr *= emissivity;
+ Alpha_corr = div64_s64(Alpha_corr, 100LL);
+ calcedFa = div64_s64((s64)object * 100000000000LL, Alpha_corr);
+
+ return (int_sqrt64(int_sqrt64(calcedFa * 100000000LL + TAdut4))
+ - 27315 - Hb_customer) * 10;
+}
+
+static s64 mlx90635_calc_ta4(s64 TAdut, s64 scale)
+{
+ return (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315) *
+ (div64_s64(TAdut, scale) + 27315);
+}
+
+static s32 mlx90635_calc_temp_object(s64 object, s64 ambient, u32 Ea, u32 Eb,
+ s16 Ga, u32 Fa, u16 Fa_scale, s16 Fb, s16 Ha, s16 Hb,
+ u16 tmp_emi)
+{
+ s64 kTA, kTA0, TAdut, TAdut4;
+ s64 temp = 35000;
+ s8 i;
+
+ kTA = (Ea * 1000LL) >> 16LL;
+ kTA0 = (Eb * 1000LL) >> 8LL;
+ TAdut = div64_s64(((ambient - kTA0) * 1000000LL), kTA) + 30 * 1000000LL;
+ TAdut4 = mlx90635_calc_ta4(TAdut, 10000LL);
+
+ /* Iterations of calculation as described in datasheet */
+ for (i = 0; i < 5; ++i) {
+ temp = mlx90635_calc_temp_object_iteration(temp, object, TAdut, TAdut4,
+ Ga, Fa, Fa_scale, Fb, Ha, Hb,
+ tmp_emi);
+ }
+ return temp;
+}
+
+static int mlx90635_calc_object(struct mlx90635_data *data, int *val)
+{
+ s16 ambient_new_raw, ambient_old_raw, object_raw;
+ s16 Fb, Ga, Gb, Ha, Hb;
+ s64 object, ambient;
+ u32 Ea, Eb, Fa;
+ u16 Fa_scale;
+ int ret;
+
+ ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale);
+ if (ret < 0)
+ return ret;
+
+ ret = mlx90635_read_all_channel(data,
+ &ambient_new_raw, &ambient_old_raw,
+ &object_raw);
+ if (ret < 0)
+ return ret;
+
+ ambient = mlx90635_preprocess_temp_amb(ambient_new_raw,
+ ambient_old_raw, Gb);
+ object = mlx90635_preprocess_temp_obj(object_raw,
+ ambient_new_raw,
+ ambient_old_raw, Gb);
+
+ *val = mlx90635_calc_temp_object(object, ambient, Ea, Eb, Ga, Fa, Fa_scale, Fb,
+ Ha, Hb, data->emissivity);
+ return 0;
+}
+
+static int mlx90635_calc_ambient(struct mlx90635_data *data, int *val)
+{
+ s16 ambient_new_raw, ambient_old_raw;
+ s16 PG, PO, Gb;
+ int ret;
+
+ ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&data->lock);
+ if (data->powerstatus == MLX90635_PWR_STATUS_SLEEP_STEP) {
+ ret = mlx90635_perform_measurement_burst(data);
+ if (ret < 0)
+ goto read_ambient_unlock;
+ }
+
+ ret = mlx90635_read_ambient_raw(data->regmap, &ambient_new_raw,
+ &ambient_old_raw);
+read_ambient_unlock:
+ mutex_unlock(&data->lock);
+ if (ret < 0)
+ return ret;
+
+ *val = mlx90635_calc_temp_ambient(ambient_new_raw, ambient_old_raw,
+ PG, PO, Gb);
+ return ret;
+}
+
+static int mlx90635_get_refresh_rate(struct mlx90635_data *data,
+ unsigned int *refresh_rate)
+{
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(data->regmap, MLX90635_REG_CTRL1, &reg);
+ if (ret < 0)
+ return ret;
+
+ *refresh_rate = FIELD_GET(MLX90635_CTRL1_REFRESH_RATE_MASK, reg);
+
+ return 0;
+}
+
+static const struct {
+ int val;
+ int val2;
+} mlx90635_freqs[] = {
+ { 0, 200000 },
+ { 0, 500000 },
+ { 0, 900000 },
+ { 1, 700000 },
+ { 3, 0 },
+ { 4, 800000 },
+ { 6, 900000 },
+ { 8, 900000 }
+};
+
+/**
+ * mlx90635_pm_interaction_wakeup() - Measure time between user interactions to change powermode
+ * @data: pointer to mlx90635_data object containing interaction_ts information
+ *
+ * Switch to continuous mode when interaction is faster than MLX90635_MEAS_MAX_TIME. Update the
+ * interaction_ts for each function call with the jiffies to enable measurement between function
+ * calls. Initial value of the interaction_ts needs to be set before this function call.
+ */
+static int mlx90635_pm_interaction_wakeup(struct mlx90635_data *data)
+{
+ unsigned long now;
+ int ret;
+
+ now = jiffies;
+ if (time_in_range(now, data->interaction_ts,
+ data->interaction_ts +
+ msecs_to_jiffies(MLX90635_MEAS_MAX_TIME + 100))) {
+ ret = mlx90635_pwr_continuous(data);
+ if (ret < 0)
+ return ret;
+ }
+
+ data->interaction_ts = now;
+
+ return 0;
+}
+
+static int mlx90635_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int *val,
+ int *val2, long mask)
+{
+ struct mlx90635_data *data = iio_priv(indio_dev);
+ int ret;
+ int cr;
+
+ pm_runtime_get_sync(&data->client->dev);
+ ret = mlx90635_pm_interaction_wakeup(data);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_PROCESSED:
+ switch (channel->channel2) {
+ case IIO_MOD_TEMP_AMBIENT:
+ ret = mlx90635_calc_ambient(data, val);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ ret = IIO_VAL_INT;
+ break;
+ case IIO_MOD_TEMP_OBJECT:
+ ret = mlx90635_calc_object(data, val);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ ret = IIO_VAL_INT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ break;
+ case IIO_CHAN_INFO_CALIBEMISSIVITY:
+ if (data->emissivity == 1000) {
+ *val = 1;
+ *val2 = 0;
+ } else {
+ *val = 0;
+ *val2 = data->emissivity * 1000;
+ }
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = mlx90635_get_refresh_rate(data, &cr);
+ if (ret < 0)
+ goto mlx90635_read_raw_pm;
+
+ *val = mlx90635_freqs[cr].val;
+ *val2 = mlx90635_freqs[cr].val2;
+ ret = IIO_VAL_INT_PLUS_MICRO;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+mlx90635_read_raw_pm:
+ pm_runtime_mark_last_busy(&data->client->dev);
+ pm_runtime_put_autosuspend(&data->client->dev);
+ return ret;
+}
+
+static int mlx90635_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *channel, int val,
+ int val2, long mask)
+{
+ struct mlx90635_data *data = iio_priv(indio_dev);
+ int ret;
+ int i;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_CALIBEMISSIVITY:
+ /* Confirm we are within 0 and 1.0 */
+ if (val < 0 || val2 < 0 || val > 1 ||
+ (val == 1 && val2 != 0))
+ return -EINVAL;
+ data->emissivity = val * 1000 + val2 / 1000;
+ return 0;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ for (i = 0; i < ARRAY_SIZE(mlx90635_freqs); i++) {
+ if (val == mlx90635_freqs[i].val &&
+ val2 == mlx90635_freqs[i].val2)
+ break;
+ }
+ if (i == ARRAY_SIZE(mlx90635_freqs))
+ return -EINVAL;
+
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_CTRL1,
+ MLX90635_CTRL1_REFRESH_RATE_MASK, i);
+
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int mlx90635_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_SAMP_FREQ:
+ *vals = (int *)mlx90635_freqs;
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *length = 2 * ARRAY_SIZE(mlx90635_freqs);
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_chan_spec mlx90635_channels[] = {
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_AMBIENT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ },
+ {
+ .type = IIO_TEMP,
+ .modified = 1,
+ .channel2 = IIO_MOD_TEMP_OBJECT,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) |
+ BIT(IIO_CHAN_INFO_CALIBEMISSIVITY),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ },
+};
+
+static const struct iio_info mlx90635_info = {
+ .read_raw = mlx90635_read_raw,
+ .write_raw = mlx90635_write_raw,
+ .read_avail = mlx90635_read_avail,
+};
+
+static void mlx90635_sleep(void *_data)
+{
+ struct mlx90635_data *data = _data;
+
+ mlx90635_pwr_sleep_step(data);
+}
+
+static int mlx90635_suspend(struct mlx90635_data *data)
+{
+ return mlx90635_pwr_sleep_step(data);
+}
+
+static int mlx90635_wakeup(struct mlx90635_data *data)
+{
+ s16 Fb, Ga, Gb, Ha, Hb, PG, PO;
+ unsigned int dsp_version;
+ u32 Ea, Eb, Fa;
+ u16 Fa_scale;
+ int ret;
+
+ regcache_cache_bypass(data->regmap_ee, false);
+ regcache_cache_only(data->regmap_ee, false);
+ regcache_cache_only(data->regmap, false);
+
+ ret = mlx90635_pwr_continuous(data);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Switch to continuous mode failed\n");
+ return ret;
+ }
+ ret = regmap_write_bits(data->regmap, MLX90635_REG_EE,
+ MLX90635_EE_ACTIVE, MLX90635_EE_ACTIVE);
+ if (ret < 0) {
+ dev_err(&data->client->dev, "Powering EEPROM failed\n");
+ return ret;
+ }
+ usleep_range(MLX90635_TIMING_EE_ACTIVE_MIN, MLX90635_TIMING_EE_ACTIVE_MAX);
+
+ regcache_mark_dirty(data->regmap_ee);
+
+ ret = regcache_sync(data->regmap_ee);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to sync cache: %d\n", ret);
+ return ret;
+ }
+
+ ret = mlx90635_read_ee_ambient(data->regmap_ee, &PG, &PO, &Gb);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to read to cache Ambient coefficients EEPROM region: %d\n", ret);
+ return ret;
+ }
+
+ ret = mlx90635_read_ee_object(data->regmap_ee, &Ea, &Eb, &Fa, &Fb, &Ga, &Gb, &Ha, &Hb, &Fa_scale);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to read to cache Object coefficients EEPROM region: %d\n", ret);
+ return ret;
+ }
+
+ ret = regmap_read(data->regmap_ee, MLX90635_EE_VERSION, &dsp_version);
+ if (ret < 0) {
+ dev_err(&data->client->dev,
+ "Failed to read to cache of EEPROM version: %d\n", ret);
+ return ret;
+ }
+
+ regcache_cache_only(data->regmap_ee, true);
+
+ return ret;
+}
+
+static void mlx90635_disable_regulator(void *_data)
+{
+ struct mlx90635_data *data = _data;
+ int ret;
+
+ ret = regulator_disable(data->regulator);
+ if (ret < 0)
+ dev_err(regmap_get_device(data->regmap),
+ "Failed to disable power regulator: %d\n", ret);
+}
+
+static int mlx90635_enable_regulator(struct mlx90635_data *data)
+{
+ int ret;
+
+ ret = regulator_enable(data->regulator);
+ if (ret < 0) {
+ dev_err(regmap_get_device(data->regmap), "Failed to enable power regulator!\n");
+ return ret;
+ }
+
+ mlx90635_reset_delay();
+
+ return ret;
+}
+
+static int mlx90635_probe(struct i2c_client *client)
+{
+ struct mlx90635_data *mlx90635;
+ struct iio_dev *indio_dev;
+ unsigned int dsp_version;
+ struct regmap *regmap;
+ struct regmap *regmap_ee;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*mlx90635));
+ if (!indio_dev)
+ return dev_err_probe(&client->dev, -ENOMEM, "failed to allocate device\n");
+
+ regmap = devm_regmap_init_i2c(client, &mlx90635_regmap);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap),
+ "failed to allocate regmap\n");
+
+ regmap_ee = devm_regmap_init_i2c(client, &mlx90635_regmap_ee);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap),
+ "failed to allocate regmap\n");
+
+ mlx90635 = iio_priv(indio_dev);
+ i2c_set_clientdata(client, indio_dev);
+ mlx90635->client = client;
+ mlx90635->regmap = regmap;
+ mlx90635->regmap_ee = regmap_ee;
+ mlx90635->powerstatus = MLX90635_PWR_STATUS_SLEEP_STEP;
+
+ mutex_init(&mlx90635->lock);
+ indio_dev->name = "mlx90635";
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &mlx90635_info;
+ indio_dev->channels = mlx90635_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mlx90635_channels);
+
+ mlx90635->regulator = devm_regulator_get(&client->dev, "vdd");
+ if (IS_ERR(mlx90635->regulator))
+ return dev_err_probe(&client->dev, PTR_ERR(mlx90635->regulator),
+ "failed to get vdd regulator");
+
+ ret = mlx90635_enable_regulator(mlx90635);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_add_action_or_reset(&client->dev, mlx90635_disable_regulator,
+ mlx90635);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "failed to setup regulator cleanup action\n");
+
+ ret = mlx90635_wakeup(mlx90635);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "wakeup failed\n");
+
+ ret = devm_add_action_or_reset(&client->dev, mlx90635_sleep, mlx90635);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "failed to setup low power cleanup\n");
+
+ ret = regmap_read(mlx90635->regmap_ee, MLX90635_EE_VERSION, &dsp_version);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret, "read of version failed\n");
+
+ dsp_version = dsp_version & MLX90635_VERSION_MASK;
+
+ if (FIELD_GET(MLX90635_DSP_FIXED, dsp_version)) {
+ if (MLX90635_DSP_VERSION(dsp_version) == MLX90635_ID_DSPv1) {
+ dev_dbg(&client->dev,
+ "Detected DSP v1 calibration %x\n", dsp_version);
+ } else {
+ dev_dbg(&client->dev,
+ "Detected Unknown EEPROM calibration %lx\n",
+ MLX90635_DSP_VERSION(dsp_version));
+ }
+ } else {
+ return dev_err_probe(&client->dev, -EPROTONOSUPPORT,
+ "Wrong fixed top bit %x (expected 0x8X0X)\n",
+ dsp_version);
+ }
+
+ mlx90635->emissivity = 1000;
+ mlx90635->interaction_ts = jiffies; /* Set initial value */
+
+ pm_runtime_get_noresume(&client->dev);
+ pm_runtime_set_active(&client->dev);
+
+ ret = devm_pm_runtime_enable(&client->dev);
+ if (ret)
+ return dev_err_probe(&client->dev, ret,
+ "failed to enable powermanagement\n");
+
+ pm_runtime_set_autosuspend_delay(&client->dev, MLX90635_SLEEP_DELAY_MS);
+ pm_runtime_use_autosuspend(&client->dev);
+ pm_runtime_put_autosuspend(&client->dev);
+
+ return devm_iio_device_register(&client->dev, indio_dev);
+}
+
+static const struct i2c_device_id mlx90635_id[] = {
+ { "mlx90635" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, mlx90635_id);
+
+static const struct of_device_id mlx90635_of_match[] = {
+ { .compatible = "melexis,mlx90635" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mlx90635_of_match);
+
+static int mlx90635_pm_suspend(struct device *dev)
+{
+ struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev));
+ int ret;
+
+ ret = mlx90635_suspend(data);
+ if (ret < 0)
+ return ret;
+
+ ret = regulator_disable(data->regulator);
+ if (ret < 0)
+ dev_err(regmap_get_device(data->regmap),
+ "Failed to disable power regulator: %d\n", ret);
+
+ return ret;
+}
+
+static int mlx90635_pm_resume(struct device *dev)
+{
+ struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev));
+ int ret;
+
+ ret = mlx90635_enable_regulator(data);
+ if (ret < 0)
+ return ret;
+
+ return mlx90635_wakeup(data);
+}
+
+static int mlx90635_pm_runtime_suspend(struct device *dev)
+{
+ struct mlx90635_data *data = iio_priv(dev_get_drvdata(dev));
+
+ return mlx90635_pwr_sleep_step(data);
+}
+
+static const struct dev_pm_ops mlx90635_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(mlx90635_pm_suspend, mlx90635_pm_resume)
+ RUNTIME_PM_OPS(mlx90635_pm_runtime_suspend, NULL, NULL)
+};
+
+static struct i2c_driver mlx90635_driver = {
+ .driver = {
+ .name = "mlx90635",
+ .of_match_table = mlx90635_of_match,
+ .pm = pm_ptr(&mlx90635_pm_ops),
+ },
+ .probe = mlx90635_probe,
+ .id_table = mlx90635_id,
+};
+module_i2c_driver(mlx90635_driver);
+
+MODULE_AUTHOR("Crt Mori <cmo@melexis.com>");
+MODULE_DESCRIPTION("Melexis MLX90635 contactless Infra Red temperature sensor driver");
+MODULE_LICENSE("GPL");