This series of patches adds a driver for ScioSense ENS160 multi-gas
sensor, designed for indoor air quality monitoring.
---
Changes in v3:
- Add missing `$ref: /schemas/spi/spi-peripheral-props.yaml#` in
devicetree binding
- Move the devm_add_action_or_reset() call to right after setting the
operation mode to standard
- Remove "_spi" and "_i2c" suffixes from device name
- Add a comment explaining what data the mutex is protecting
- Format register address macro
Link to v2: https://lore.kernel.org/linux-iio/[email protected]/
---
Gustavo Silva (6):
dt-bindings: vendor-prefixes: add ScioSense
dt-bindings: iio: chemical: add ENS160 sensor
iio: chemical: add driver for ENS160 sensor
iio: chemical: ens160: add triggered buffer support
iio: chemical: ens160: add power management support
MAINTAINERS: Add ScioSense ENS160
.../iio/chemical/sciosense,ens160.yaml | 70 ++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 8 +
drivers/iio/chemical/Kconfig | 22 ++
drivers/iio/chemical/Makefile | 3 +
drivers/iio/chemical/ens160.h | 10 +
drivers/iio/chemical/ens160_core.c | 367 ++++++++++++++++++
drivers/iio/chemical/ens160_i2c.c | 62 +++
drivers/iio/chemical/ens160_spi.c | 61 +++
9 files changed, 605 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/chemical/sciosense,ens160.yaml
create mode 100644 drivers/iio/chemical/ens160.h
create mode 100644 drivers/iio/chemical/ens160_core.c
create mode 100644 drivers/iio/chemical/ens160_i2c.c
create mode 100644 drivers/iio/chemical/ens160_spi.c
base-commit: 084eeee1d8da6b4712719264b01cb27b41307f54
--
2.45.1
Add bindings for ScioSense ENS160 multi-gas sensor.
Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
Reviewed-by: Conor Dooley <[email protected]>
Signed-off-by: Gustavo Silva <[email protected]>
---
.../iio/chemical/sciosense,ens160.yaml | 70 +++++++++++++++++++
1 file changed, 70 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/chemical/sciosense,ens160.yaml
diff --git a/Documentation/devicetree/bindings/iio/chemical/sciosense,ens160.yaml b/Documentation/devicetree/bindings/iio/chemical/sciosense,ens160.yaml
new file mode 100644
index 000000000..267033a68
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/chemical/sciosense,ens160.yaml
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/chemical/sciosense,ens160.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ScioSense ENS160 multi-gas sensor
+
+maintainers:
+ - Gustavo Silva <[email protected]>
+
+description: |
+ Digital Multi-Gas Sensor for Monitoring Indoor Air Quality.
+
+ Datasheet:
+ https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
+
+properties:
+ compatible:
+ enum:
+ - sciosense,ens160
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ vdd-supply: true
+ vddio-supply: true
+
+required:
+ - compatible
+ - reg
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ gas-sensor@52 {
+ compatible = "sciosense,ens160";
+ reg = <0x52>;
+ interrupt-parent = <&gpio0>;
+ interrupts = <19 IRQ_TYPE_EDGE_FALLING>;
+ };
+ };
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ gas-sensor@0 {
+ compatible = "sciosense,ens160";
+ reg = <0>;
+ spi-max-frequency = <10000000>;
+ interrupt-parent = <&gpio>;
+ interrupts = <19 IRQ_TYPE_EDGE_FALLING>;
+ };
+ };
+
+...
--
2.45.1
ScioSense ENS160 is a digital metal oxide multi-gas sensor, designed
for indoor air quality monitoring. The driver supports readings of
CO2 and VOC, and can be accessed via both SPI and I2C.
Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
Signed-off-by: Gustavo Silva <[email protected]>
---
drivers/iio/chemical/Kconfig | 22 +++
drivers/iio/chemical/Makefile | 3 +
drivers/iio/chemical/ens160.h | 7 +
drivers/iio/chemical/ens160_core.c | 221 +++++++++++++++++++++++++++++
drivers/iio/chemical/ens160_i2c.c | 60 ++++++++
drivers/iio/chemical/ens160_spi.c | 60 ++++++++
6 files changed, 373 insertions(+)
create mode 100644 drivers/iio/chemical/ens160.h
create mode 100644 drivers/iio/chemical/ens160_core.c
create mode 100644 drivers/iio/chemical/ens160_i2c.c
create mode 100644 drivers/iio/chemical/ens160_spi.c
diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index 02649ab81..e407afab8 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -76,6 +76,28 @@ config CCS811
Say Y here to build I2C interface support for the AMS
CCS811 VOC (Volatile Organic Compounds) sensor
+config ENS160
+ tristate "ScioSense ENS160 sensor driver"
+ depends on (I2C || SPI)
+ select REGMAP
+ select ENS160_I2C if I2C
+ select ENS160_SPI if SPI
+ help
+ Say yes here to build support for ScioSense ENS160 multi-gas sensor.
+
+ This driver can also be built as a module. If so, the module for I2C
+ would be called ens160_i2c and ens160_spi for SPI support.
+
+config ENS160_I2C
+ tristate
+ depends on I2C && ENS160
+ select REGMAP_I2C
+
+config ENS160_SPI
+ tristate
+ depends on SPI && ENS160
+ select REGMAP_SPI
+
config IAQCORE
tristate "AMS iAQ-Core VOC sensors"
depends on I2C
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
index 2f3dee8bb..4866db06b 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -11,6 +11,9 @@ obj-$(CONFIG_BME680) += bme680_core.o
obj-$(CONFIG_BME680_I2C) += bme680_i2c.o
obj-$(CONFIG_BME680_SPI) += bme680_spi.o
obj-$(CONFIG_CCS811) += ccs811.o
+obj-$(CONFIG_ENS160) += ens160_core.o
+obj-$(CONFIG_ENS160_I2C) += ens160_i2c.o
+obj-$(CONFIG_ENS160_SPI) += ens160_spi.o
obj-$(CONFIG_IAQCORE) += ams-iaq-core.o
obj-$(CONFIG_PMS7003) += pms7003.o
obj-$(CONFIG_SCD30_CORE) += scd30_core.o
diff --git a/drivers/iio/chemical/ens160.h b/drivers/iio/chemical/ens160.h
new file mode 100644
index 000000000..d0df15f08
--- /dev/null
+++ b/drivers/iio/chemical/ens160.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef ENS160_H_
+#define ENS160_H_
+
+int devm_ens160_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name);
+#endif
diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c
new file mode 100644
index 000000000..ae99a95c6
--- /dev/null
+++ b/drivers/iio/chemical/ens160_core.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ScioSense ENS160 multi-gas sensor driver
+ *
+ * Copyright (c) 2024 Gustavo Silva <[email protected]>
+ *
+ * Datasheet:
+ * https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
+ */
+
+#include <linux/bitfield.h>
+#include <linux/iio/iio.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "ens160.h"
+
+#define ENS160_PART_ID 0x160
+
+#define ENS160_BOOTING_TIME_MS 10U
+
+#define ENS160_REG_PART_ID 0x00
+
+#define ENS160_REG_OPMODE 0x10
+
+#define ENS160_REG_MODE_DEEP_SLEEP 0x00
+#define ENS160_REG_MODE_IDLE 0x01
+#define ENS160_REG_MODE_STANDARD 0x02
+#define ENS160_REG_MODE_RESET 0xF0
+
+#define ENS160_REG_COMMAND 0x12
+#define ENS160_REG_COMMAND_GET_APPVER 0x0E
+#define ENS160_REG_COMMAND_CLRGPR 0xCC
+
+#define ENS160_REG_TEMP_IN 0x13
+#define ENS160_REG_RH_IN 0x15
+#define ENS160_REG_DEVICE_STATUS 0x20
+#define ENS160_REG_DATA_AQI 0x21
+#define ENS160_REG_DATA_TVOC 0x22
+#define ENS160_REG_DATA_ECO2 0x24
+#define ENS160_REG_DATA_T 0x30
+#define ENS160_REG_DATA_RH 0x32
+#define ENS160_REG_GPR_READ4 0x4C
+
+#define ENS160_STATUS_VALIDITY_FLAG GENMASK(3, 2)
+
+#define ENS160_STATUS_NORMAL 0x00
+
+struct ens160_data {
+ struct regmap *regmap;
+ u8 fw_version[3] __aligned(IIO_DMA_MINALIGN);
+ __le16 buf;
+};
+
+static const struct iio_chan_spec ens160_channels[] = {
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_VOC,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = ENS160_REG_DATA_TVOC,
+ },
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_CO2,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .address = ENS160_REG_DATA_ECO2,
+ },
+};
+
+static int ens160_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ struct ens160_data *data = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = regmap_bulk_read(data->regmap, chan->address,
+ &data->buf, sizeof(data->buf));
+ if (ret)
+ return ret;
+ *val = le16_to_cpu(data->buf);
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->channel2) {
+ case IIO_MOD_CO2:
+ /* The sensor reads CO2 data as ppm */
+ *val = 0;
+ *val2 = 100;
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_MOD_VOC:
+ /* The sensor reads VOC data as ppb */
+ *val = 0;
+ *val2 = 100;
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ens160_set_mode(struct ens160_data *data, u8 mode)
+{
+ int ret;
+
+ ret = regmap_write(data->regmap, ENS160_REG_OPMODE, mode);
+ if (ret)
+ return ret;
+
+ msleep(ENS160_BOOTING_TIME_MS);
+
+ return 0;
+}
+
+static void ens160_set_idle(void *data)
+{
+ ens160_set_mode(data, ENS160_REG_MODE_IDLE);
+}
+
+static int ens160_chip_init(struct ens160_data *data)
+{
+ struct device *dev = regmap_get_device(data->regmap);
+ unsigned int status;
+ int ret;
+
+ ret = ens160_set_mode(data, ENS160_REG_MODE_RESET);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(data->regmap, ENS160_REG_PART_ID, &data->buf,
+ sizeof(data->buf));
+ if (ret)
+ return ret;
+
+ if (le16_to_cpu(data->buf) != ENS160_PART_ID)
+ return -ENODEV;
+
+ ret = ens160_set_mode(data, ENS160_REG_MODE_IDLE);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ENS160_REG_COMMAND,
+ ENS160_REG_COMMAND_CLRGPR);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(data->regmap, ENS160_REG_COMMAND,
+ ENS160_REG_COMMAND_GET_APPVER);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_read(data->regmap, ENS160_REG_GPR_READ4,
+ data->fw_version, sizeof(data->fw_version));
+ if (ret)
+ return ret;
+
+ dev_info(dev, "firmware version: %u.%u.%u\n", data->fw_version[2],
+ data->fw_version[1], data->fw_version[0]);
+
+ ret = ens160_set_mode(data, ENS160_REG_MODE_STANDARD);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, ens160_set_idle, data);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(data->regmap, ENS160_REG_DEVICE_STATUS, &status);
+ if (ret)
+ return ret;
+
+ if (FIELD_GET(ENS160_STATUS_VALIDITY_FLAG, status)
+ != ENS160_STATUS_NORMAL)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct iio_info ens160_info = {
+ .read_raw = ens160_read_raw,
+};
+
+int devm_ens160_core_probe(struct device *dev, struct regmap *regmap,
+ const char *name)
+{
+ struct ens160_data *data;
+ struct iio_dev *indio_dev;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*data));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ data = iio_priv(indio_dev);
+ data->regmap = regmap;
+
+ indio_dev->name = name;
+ indio_dev->info = &ens160_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = ens160_channels;
+ indio_dev->num_channels = ARRAY_SIZE(ens160_channels);
+
+ ret = ens160_chip_init(data);
+ if (ret)
+ return dev_err_probe(dev, ret, "chip initialization failed\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+EXPORT_SYMBOL_NS(devm_ens160_core_probe, IIO_ENS160);
+
+MODULE_AUTHOR("Gustavo Silva <[email protected]>");
+MODULE_DESCRIPTION("ScioSense ENS160 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/iio/chemical/ens160_i2c.c b/drivers/iio/chemical/ens160_i2c.c
new file mode 100644
index 000000000..f885903fe
--- /dev/null
+++ b/drivers/iio/chemical/ens160_i2c.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ScioSense ENS160 multi-gas sensor I2C driver
+ *
+ * Copyright (c) 2024 Gustavo Silva <[email protected]>
+ *
+ * 7-Bit I2C slave address is:
+ * - 0x52 if ADDR pin LOW
+ * - 0x53 if ADDR pin HIGH
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#include "ens160.h"
+
+static const struct regmap_config ens160_regmap_i2c_conf = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static int ens160_i2c_probe(struct i2c_client *client)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_i2c(client, &ens160_regmap_i2c_conf);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(regmap),
+ "Failed to register i2c regmap\n");
+
+ return devm_ens160_core_probe(&client->dev, regmap, "ens160");
+}
+
+static const struct i2c_device_id ens160_i2c_id[] = {
+ { "ens160" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ens160_i2c_id);
+
+static const struct of_device_id ens160_of_i2c_match[] = {
+ { .compatible = "sciosense,ens160" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ens160_of_i2c_match);
+
+static struct i2c_driver ens160_i2c_driver = {
+ .driver = {
+ .name = "ens160",
+ .of_match_table = ens160_of_i2c_match,
+ },
+ .probe = ens160_i2c_probe,
+ .id_table = ens160_i2c_id,
+};
+module_i2c_driver(ens160_i2c_driver);
+
+MODULE_AUTHOR("Gustavo Silva <[email protected]>");
+MODULE_DESCRIPTION("ScioSense ENS160 I2C driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_ENS160);
diff --git a/drivers/iio/chemical/ens160_spi.c b/drivers/iio/chemical/ens160_spi.c
new file mode 100644
index 000000000..40c68c5c4
--- /dev/null
+++ b/drivers/iio/chemical/ens160_spi.c
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ScioSense ENS160 multi-gas sensor SPI driver
+ *
+ * Copyright (c) 2024 Gustavo Silva <[email protected]>
+ */
+
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spi/spi.h>
+
+#include "ens160.h"
+
+#define ENS160_SPI_READ BIT(0)
+
+static const struct regmap_config ens160_regmap_spi_conf = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .reg_shift = -1,
+ .read_flag_mask = ENS160_SPI_READ,
+};
+
+static int ens160_spi_probe(struct spi_device *spi)
+{
+ struct regmap *regmap;
+
+ regmap = devm_regmap_init_spi(spi, &ens160_regmap_spi_conf);
+ if (IS_ERR(regmap))
+ return dev_err_probe(&spi->dev, PTR_ERR(regmap),
+ "Failed to register spi regmap\n");
+
+ return devm_ens160_core_probe(&spi->dev, regmap, "ens160");
+}
+
+static const struct of_device_id ens160_spi_of_match[] = {
+ { .compatible = "sciosense,ens160" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ens160_spi_of_match);
+
+static const struct spi_device_id ens160_spi_id[] = {
+ { "ens160" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ens160_spi_id);
+
+static struct spi_driver ens160_spi_driver = {
+ .driver = {
+ .name = "ens160",
+ .of_match_table = ens160_spi_of_match,
+ },
+ .probe = ens160_spi_probe,
+ .id_table = ens160_spi_id,
+};
+module_spi_driver(ens160_spi_driver);
+
+MODULE_AUTHOR("Gustavo Silva <[email protected]>");
+MODULE_DESCRIPTION("ScioSense ENS160 SPI driver");
+MODULE_LICENSE("GPL v2");
+MODULE_IMPORT_NS(IIO_ENS160);
--
2.45.1
Add myself as maintainer for ScioSense ENS160 multi-gas sensor driver.
Signed-off-by: Gustavo Silva <[email protected]>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 304429f9b..92a130c8c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19660,6 +19660,14 @@ F: include/linux/wait.h
F: include/uapi/linux/sched.h
F: kernel/sched/
+SCIOSENSE ENS160 MULTI-GAS SENSOR DRIVER
+M: Gustavo Silva <[email protected]>
+S: Maintained
+F: drivers/iio/chemical/ens160_core.c
+F: drivers/iio/chemical/ens160_i2c.c
+F: drivers/iio/chemical/ens160_spi.c
+F: drivers/iio/chemical/ens160.h
+
SCSI LIBSAS SUBSYSTEM
R: John Garry <[email protected]>
R: Jason Yan <[email protected]>
--
2.45.1
Add vendor prefix for ScioSense B.V.
https://www.sciosense.com/
Acked-by: Conor Dooley <[email protected]>
Signed-off-by: Gustavo Silva <[email protected]>
---
Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index b97d298b3..298f13a0d 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1246,6 +1246,8 @@ patternProperties:
description: Smart Battery System
"^schindler,.*":
description: Schindler
+ "^sciosense,.*":
+ description: ScioSense B.V.
"^seagate,.*":
description: Seagate Technology PLC
"^seeed,.*":
--
2.45.1
ENS160 supports a data ready interrupt. Use it in combination with
triggered buffer for continuous data readings.
Signed-off-by: Gustavo Silva <[email protected]>
---
drivers/iio/chemical/ens160.h | 2 +-
drivers/iio/chemical/ens160_core.c | 141 +++++++++++++++++++++++++++--
drivers/iio/chemical/ens160_i2c.c | 3 +-
drivers/iio/chemical/ens160_spi.c | 2 +-
4 files changed, 136 insertions(+), 12 deletions(-)
diff --git a/drivers/iio/chemical/ens160.h b/drivers/iio/chemical/ens160.h
index d0df15f08..e6cc0987a 100644
--- a/drivers/iio/chemical/ens160.h
+++ b/drivers/iio/chemical/ens160.h
@@ -2,6 +2,6 @@
#ifndef ENS160_H_
#define ENS160_H_
-int devm_ens160_core_probe(struct device *dev, struct regmap *regmap,
+int devm_ens160_core_probe(struct device *dev, struct regmap *regmap, int irq,
const char *name);
#endif
diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c
index ae99a95c6..3da3b1095 100644
--- a/drivers/iio/chemical/ens160_core.c
+++ b/drivers/iio/chemical/ens160_core.c
@@ -10,6 +10,9 @@
#include <linux/bitfield.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
#include <linux/module.h>
#include <linux/regmap.h>
@@ -23,6 +26,11 @@
#define ENS160_REG_OPMODE 0x10
+#define ENS160_REG_CONFIG 0x11
+#define ENS160_REG_CONFIG_INTEN BIT(0)
+#define ENS160_REG_CONFIG_INTDAT BIT(1)
+#define ENS160_REG_CONFIG_INT_CFG BIT(5)
+
#define ENS160_REG_MODE_DEEP_SLEEP 0x00
#define ENS160_REG_MODE_IDLE 0x01
#define ENS160_REG_MODE_STANDARD 0x02
@@ -48,7 +56,13 @@
struct ens160_data {
struct regmap *regmap;
- u8 fw_version[3] __aligned(IIO_DMA_MINALIGN);
+ /* Protect reads from the sensor */
+ struct mutex mutex;
+ struct {
+ __le16 chans[2];
+ s64 timestamp __aligned(8);
+ } scan __aligned(IIO_DMA_MINALIGN);
+ u8 fw_version[3];
__le16 buf;
};
@@ -60,6 +74,13 @@ static const struct iio_chan_spec ens160_channels[] = {
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.address = ENS160_REG_DATA_TVOC,
+ .scan_index = 0,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
},
{
.type = IIO_CONCENTRATION,
@@ -68,7 +89,15 @@ static const struct iio_chan_spec ens160_channels[] = {
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
BIT(IIO_CHAN_INFO_SCALE),
.address = ENS160_REG_DATA_ECO2,
+ .scan_index = 1,
+ .scan_type = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ .endianness = IIO_LE,
+ },
},
+ IIO_CHAN_SOFT_TIMESTAMP(2),
};
static int ens160_read_raw(struct iio_dev *indio_dev,
@@ -80,13 +109,16 @@ static int ens160_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
- ret = regmap_bulk_read(data->regmap, chan->address,
- &data->buf, sizeof(data->buf));
- if (ret)
- return ret;
- *val = le16_to_cpu(data->buf);
- return IIO_VAL_INT;
-
+ iio_device_claim_direct_scoped(return -EBUSY, indio_dev) {
+ guard(mutex)(&data->mutex);
+ ret = regmap_bulk_read(data->regmap, chan->address,
+ &data->buf, sizeof(data->buf));
+ if (ret)
+ return ret;
+ *val = le16_to_cpu(data->buf);
+ return IIO_VAL_INT;
+ }
+ unreachable();
case IIO_CHAN_INFO_SCALE:
switch (chan->channel2) {
case IIO_MOD_CO2:
@@ -188,7 +220,83 @@ static const struct iio_info ens160_info = {
.read_raw = ens160_read_raw,
};
-int devm_ens160_core_probe(struct device *dev, struct regmap *regmap,
+static irqreturn_t ens160_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ens160_data *data = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&data->mutex);
+
+ ret = regmap_bulk_read(data->regmap, ENS160_REG_DATA_TVOC,
+ data->scan.chans, sizeof(data->scan.chans));
+ if (ret)
+ goto err;
+
+ iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
+ pf->timestamp);
+err:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static int ens160_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+ struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+ struct ens160_data *data = iio_priv(indio_dev);
+ unsigned int int_bits = ENS160_REG_CONFIG_INTEN |
+ ENS160_REG_CONFIG_INTDAT |
+ ENS160_REG_CONFIG_INT_CFG;
+
+ if (state)
+ return regmap_set_bits(data->regmap, ENS160_REG_CONFIG,
+ int_bits);
+ else
+ return regmap_clear_bits(data->regmap, ENS160_REG_CONFIG,
+ int_bits);
+}
+
+static const struct iio_trigger_ops ens160_trigger_ops = {
+ .set_trigger_state = ens160_set_trigger_state,
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static int ens160_setup_trigger(struct iio_dev *indio_dev, int irq)
+{
+ struct device *dev = indio_dev->dev.parent;
+ struct iio_trigger *trig;
+ int ret;
+
+ trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!trig)
+ return dev_err_probe(dev, -ENOMEM,
+ "failed to allocate trigger\n");
+
+ trig->ops = &ens160_trigger_ops;
+ iio_trigger_set_drvdata(trig, indio_dev);
+
+ ret = devm_iio_trigger_register(dev, trig);
+ if (ret)
+ return ret;
+
+ indio_dev->trig = iio_trigger_get(trig);
+
+ ret = devm_request_threaded_irq(dev, irq,
+ iio_trigger_generic_data_rdy_poll,
+ NULL,
+ IRQF_ONESHOT,
+ indio_dev->name,
+ indio_dev->trig);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request irq\n");
+
+ return 0;
+}
+
+int devm_ens160_core_probe(struct device *dev, struct regmap *regmap, int irq,
const char *name)
{
struct ens160_data *data;
@@ -208,10 +316,25 @@ int devm_ens160_core_probe(struct device *dev, struct regmap *regmap,
indio_dev->channels = ens160_channels;
indio_dev->num_channels = ARRAY_SIZE(ens160_channels);
+ if (irq > 0) {
+ ret = ens160_setup_trigger(indio_dev, irq);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to setup trigger\n");
+ }
+
ret = ens160_chip_init(data);
if (ret)
return dev_err_probe(dev, ret, "chip initialization failed\n");
+ mutex_init(&data->mutex);
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ens160_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
return devm_iio_device_register(dev, indio_dev);
}
EXPORT_SYMBOL_NS(devm_ens160_core_probe, IIO_ENS160);
diff --git a/drivers/iio/chemical/ens160_i2c.c b/drivers/iio/chemical/ens160_i2c.c
index f885903fe..f3fb45b75 100644
--- a/drivers/iio/chemical/ens160_i2c.c
+++ b/drivers/iio/chemical/ens160_i2c.c
@@ -29,7 +29,8 @@ static int ens160_i2c_probe(struct i2c_client *client)
return dev_err_probe(&client->dev, PTR_ERR(regmap),
"Failed to register i2c regmap\n");
- return devm_ens160_core_probe(&client->dev, regmap, "ens160");
+ return devm_ens160_core_probe(&client->dev, regmap, client->irq,
+ "ens160");
}
static const struct i2c_device_id ens160_i2c_id[] = {
diff --git a/drivers/iio/chemical/ens160_spi.c b/drivers/iio/chemical/ens160_spi.c
index 40c68c5c4..90e1e8386 100644
--- a/drivers/iio/chemical/ens160_spi.c
+++ b/drivers/iio/chemical/ens160_spi.c
@@ -29,7 +29,7 @@ static int ens160_spi_probe(struct spi_device *spi)
return dev_err_probe(&spi->dev, PTR_ERR(regmap),
"Failed to register spi regmap\n");
- return devm_ens160_core_probe(&spi->dev, regmap, "ens160");
+ return devm_ens160_core_probe(&spi->dev, regmap, spi->irq, "ens160");
}
static const struct of_device_id ens160_spi_of_match[] = {
--
2.45.1
ENS160 supports a deep sleep mode for minimal power consumption.
Use it to add PM sleep capability to the driver.
Signed-off-by: Gustavo Silva <[email protected]>
---
drivers/iio/chemical/ens160.h | 3 +++
drivers/iio/chemical/ens160_core.c | 23 +++++++++++++++++++++++
drivers/iio/chemical/ens160_i2c.c | 1 +
drivers/iio/chemical/ens160_spi.c | 1 +
4 files changed, 28 insertions(+)
diff --git a/drivers/iio/chemical/ens160.h b/drivers/iio/chemical/ens160.h
index e6cc0987a..f9f0575ce 100644
--- a/drivers/iio/chemical/ens160.h
+++ b/drivers/iio/chemical/ens160.h
@@ -4,4 +4,7 @@
int devm_ens160_core_probe(struct device *dev, struct regmap *regmap, int irq,
const char *name);
+
+extern const struct dev_pm_ops ens160_pm_ops;
+
#endif
diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c
index 3da3b1095..c1aa3b498 100644
--- a/drivers/iio/chemical/ens160_core.c
+++ b/drivers/iio/chemical/ens160_core.c
@@ -220,6 +220,29 @@ static const struct iio_info ens160_info = {
.read_raw = ens160_read_raw,
};
+static int ens160_suspend(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ens160_data *data = iio_priv(indio_dev);
+
+ return ens160_set_mode(data, ENS160_REG_MODE_DEEP_SLEEP);
+}
+
+static int ens160_resume(struct device *dev)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(dev);
+ struct ens160_data *data = iio_priv(indio_dev);
+ int ret;
+
+ ret = ens160_set_mode(data, ENS160_REG_MODE_IDLE);
+ if (ret)
+ return ret;
+
+ return ens160_set_mode(data, ENS160_REG_MODE_STANDARD);
+}
+EXPORT_NS_SIMPLE_DEV_PM_OPS(ens160_pm_ops, ens160_suspend, ens160_resume,
+ IIO_ENS160);
+
static irqreturn_t ens160_trigger_handler(int irq, void *p)
{
struct iio_poll_func *pf = p;
diff --git a/drivers/iio/chemical/ens160_i2c.c b/drivers/iio/chemical/ens160_i2c.c
index f3fb45b75..57a189a4c 100644
--- a/drivers/iio/chemical/ens160_i2c.c
+++ b/drivers/iio/chemical/ens160_i2c.c
@@ -49,6 +49,7 @@ static struct i2c_driver ens160_i2c_driver = {
.driver = {
.name = "ens160",
.of_match_table = ens160_of_i2c_match,
+ .pm = pm_sleep_ptr(&ens160_pm_ops),
},
.probe = ens160_i2c_probe,
.id_table = ens160_i2c_id,
diff --git a/drivers/iio/chemical/ens160_spi.c b/drivers/iio/chemical/ens160_spi.c
index 90e1e8386..10e4f5fd0 100644
--- a/drivers/iio/chemical/ens160_spi.c
+++ b/drivers/iio/chemical/ens160_spi.c
@@ -48,6 +48,7 @@ static struct spi_driver ens160_spi_driver = {
.driver = {
.name = "ens160",
.of_match_table = ens160_spi_of_match,
+ .pm = pm_sleep_ptr(&ens160_pm_ops),
},
.probe = ens160_spi_probe,
.id_table = ens160_spi_id,
--
2.45.1
On Tue, 4 Jun 2024 19:57:27 -0300
Gustavo Silva <[email protected]> wrote:
> ScioSense ENS160 is a digital metal oxide multi-gas sensor, designed
> for indoor air quality monitoring. The driver supports readings of
> CO2 and VOC, and can be accessed via both SPI and I2C.
>
> Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
>
No blank lines in tags block. Datahsheet is a semi official tag.
I'll tidy up in this and previous patch if everything else fine.
> Signed-off-by: Gustavo Silva <[email protected]>
> ---
> drivers/iio/chemical/Kconfig | 22 +++
> drivers/iio/chemical/Makefile | 3 +
> drivers/iio/chemical/ens160.h | 7 +
> drivers/iio/chemical/ens160_core.c | 221 +++++++++++++++++++++++++++++
> drivers/iio/chemical/ens160_i2c.c | 60 ++++++++
> drivers/iio/chemical/ens160_spi.c | 60 ++++++++
> 6 files changed, 373 insertions(+)
> create mode 100644 drivers/iio/chemical/ens160.h
> create mode 100644 drivers/iio/chemical/ens160_core.c
> create mode 100644 drivers/iio/chemical/ens160_i2c.c
> create mode 100644 drivers/iio/chemical/ens160_spi.c
>
> diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
> index 02649ab81..e407afab8 100644
> --- a/drivers/iio/chemical/Kconfig
> +++ b/drivers/iio/chemical/Kconfig
> @@ -76,6 +76,28 @@ config CCS811
> Say Y here to build I2C interface support for the AMS
> CCS811 VOC (Volatile Organic Compounds) sensor
>
> +config ENS160
> + tristate "ScioSense ENS160 sensor driver"
> + depends on (I2C || SPI)
> + select REGMAP
> + select ENS160_I2C if I2C
> + select ENS160_SPI if SPI
> + help
> + Say yes here to build support for ScioSense ENS160 multi-gas sensor.
> +
> + This driver can also be built as a module. If so, the module for I2C
> + would be called ens160_i2c and ens160_spi for SPI support.
> +
> +config ENS160_I2C
> + tristate
> + depends on I2C && ENS160
> + select REGMAP_I2C
> +
> +config ENS160_SPI
> + tristate
> + depends on SPI && ENS160
> + select REGMAP_SPI
As these two config symbols aren't exposed I think you don't need the
depends lines.
See the BMA400 entry in drivers/iio/accel/Kconfig for example.
If everything else looks good I'll drop those two lines whilst
applying.
Jonathan
On Tue, 4 Jun 2024 19:57:24 -0300
Gustavo Silva <[email protected]> wrote:
> This series of patches adds a driver for ScioSense ENS160 multi-gas
> sensor, designed for indoor air quality monitoring.
Series applied with minor tweaks as called out for individual patches.
Applied to the togreg branch of iio.git and pushed out initially as
testing for 0-day to see if it can find anything we missed.
Thanks,
Jonathan
>
> ---
> Changes in v3:
> - Add missing `$ref: /schemas/spi/spi-peripheral-props.yaml#` in
> devicetree binding
> - Move the devm_add_action_or_reset() call to right after setting the
> operation mode to standard
> - Remove "_spi" and "_i2c" suffixes from device name
> - Add a comment explaining what data the mutex is protecting
> - Format register address macro
>
> Link to v2: https://lore.kernel.org/linux-iio/[email protected]/
>
> ---
> Gustavo Silva (6):
> dt-bindings: vendor-prefixes: add ScioSense
> dt-bindings: iio: chemical: add ENS160 sensor
> iio: chemical: add driver for ENS160 sensor
> iio: chemical: ens160: add triggered buffer support
> iio: chemical: ens160: add power management support
> MAINTAINERS: Add ScioSense ENS160
>
> .../iio/chemical/sciosense,ens160.yaml | 70 ++++
> .../devicetree/bindings/vendor-prefixes.yaml | 2 +
> MAINTAINERS | 8 +
> drivers/iio/chemical/Kconfig | 22 ++
> drivers/iio/chemical/Makefile | 3 +
> drivers/iio/chemical/ens160.h | 10 +
> drivers/iio/chemical/ens160_core.c | 367 ++++++++++++++++++
> drivers/iio/chemical/ens160_i2c.c | 62 +++
> drivers/iio/chemical/ens160_spi.c | 61 +++
> 9 files changed, 605 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/iio/chemical/sciosense,ens160.yaml
> create mode 100644 drivers/iio/chemical/ens160.h
> create mode 100644 drivers/iio/chemical/ens160_core.c
> create mode 100644 drivers/iio/chemical/ens160_i2c.c
> create mode 100644 drivers/iio/chemical/ens160_spi.c
>
>
> base-commit: 084eeee1d8da6b4712719264b01cb27b41307f54
On Sun, Jun 09, 2024 at 10:36:55AM GMT, Jonathan Cameron wrote:
> Series applied with minor tweaks as called out for individual patches.
>
> Applied to the togreg branch of iio.git and pushed out initially as
> testing for 0-day to see if it can find anything we missed.
>
> Thanks,
>
> Jonathan
>
Thank you!