[PATCH 3/3] iio: chemical: add support for Sensirion SEN5x/SEN6x

From: Hermes Zhang
Date: Thu Feb 06 2025 - 01:17:55 EST


Add support for the Senseair SEN5x/SEN6x environment sensor through the
IIO subsystem, include SEN50, SEN54, SEN55, SEN60, SEN65 and SEN66.
Different sensor channels will be supported in different models:

SEN50: PM1, PM2.5, PM4, PM10
SEN54: PM1, PM2.5, PM4, PM10, Humidity, Temperature, VOC
SEN55: PM1, PM2.5, PM4, PM10, Humidity, Temperature, VOC, NOx

SEN60: PM1, PM2.5, PM4, PM10
SEN65: PM1, PM2.5, PM4, PM10, Humidity, Temperature, VOC, NOx
SEN66: PM1, PM2.5, PM4, PM10, Humidity, Temperature, VOC, NOx, CO2

The driver support to read sensor data from raw data or iio buffer with
software trigger configured.

Signed-off-by: Hermes Zhang <Hermes.Zhang@xxxxxxxx>
---
drivers/iio/chemical/Kconfig | 27 ++
drivers/iio/chemical/Makefile | 3 +
drivers/iio/chemical/sen5x.c | 76 +++++
drivers/iio/chemical/sen6x.c | 76 +++++
drivers/iio/chemical/sen_common.c | 464 ++++++++++++++++++++++++++++++
drivers/iio/chemical/sen_common.h | 29 ++
6 files changed, 675 insertions(+)
create mode 100644 drivers/iio/chemical/sen5x.c
create mode 100644 drivers/iio/chemical/sen6x.c
create mode 100644 drivers/iio/chemical/sen_common.c
create mode 100644 drivers/iio/chemical/sen_common.h

diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index 330fe0af946f..8a21ac74d12b 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -166,6 +166,33 @@ config SCD4X
To compile this driver as a module, choose M here: the module will
be called scd4x.

+config SENSIRION_SEN_COMMON
+ tristate
+ depends on I2C
+ select CRC8
+ help
+ Common Sensirion environmental sensor code
+
+config SENSIRION_SEN5X
+ tristate "Sensirion SEN5x environmental sensor"
+ select SENSIRION_SEN_COMMON
+ help
+ Say Y here to build support for the Sensirion SEN5x environmental
+ sensor driver.
+
+ To compile this driver as module, choose M here: the module will
+ be called sen5x.
+
+config SENSIRION_SEN6X
+ tristate "Sensirion SEN6x environmental sensor"
+ select SENSIRION_SEN_COMMON
+ help
+ Say Y here to build support for the Sensirion SEN6x environmental
+ sensor driver.
+
+ To compile this driver as module, choose M here: the module will
+ be called sen6x.
+
config SENSIRION_SGP30
tristate "Sensirion SGPxx gas sensors"
depends on I2C
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
index 4866db06bdc9..988c929383d4 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -21,6 +21,9 @@ obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o
obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o
obj-$(CONFIG_SCD4X) += scd4x.o
obj-$(CONFIG_SENSEAIR_SUNRISE_CO2) += sunrise_co2.o
+obj-$(CONFIG_SENSIRION_SEN_COMMON) += sen_common.o
+obj-$(CONFIG_SENSIRION_SEN5X) += sen5x.o
+obj-$(CONFIG_SENSIRION_SEN6X) += sen6x.o
obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o
obj-$(CONFIG_SENSIRION_SGP40) += sgp40.o
obj-$(CONFIG_SPS30) += sps30.o
diff --git a/drivers/iio/chemical/sen5x.c b/drivers/iio/chemical/sen5x.c
new file mode 100644
index 000000000000..ddd1cbf18d0c
--- /dev/null
+++ b/drivers/iio/chemical/sen5x.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "sen_common.h"
+
+#define SEN50_NUM_CHANNELS 4 /* PM1,PM2.5,PM4,PM10 */
+#define SEN54_NUM_CHANNELS 7 /* PM1,PM2.5,PM4,PM10,RHT,TEMP,VOC */
+#define SEN55_NUM_CHANNELS 8 /* PM1,PM2.5,PM4,PM10,RHT,TEMP,VOC,NOx */
+
+#define SEN5X_RESET_DELAY 100
+
+#define SEN5X_READ_MEASURED 0x03C4
+
+enum {
+ SEN50,
+ SEN54,
+ SEN55,
+};
+
+static const struct sen_chip_info sen5x_chips[] = {
+ [SEN50] = {
+ .reset_delay = SEN5X_RESET_DELAY,
+ .num_iio_channels = SEN50_NUM_CHANNELS,
+ .read_measured_cmd = SEN5X_READ_MEASURED,
+ .read_measured_rx_size = 24,
+ .temperature_compensation_required = false,
+ },
+ [SEN54] = {
+ .reset_delay = SEN5X_RESET_DELAY,
+ .num_iio_channels = SEN54_NUM_CHANNELS,
+ .read_measured_cmd = SEN5X_READ_MEASURED,
+ .read_measured_rx_size = 24,
+ .temperature_compensation_required = true,
+ },
+ [SEN55] = {
+ .reset_delay = SEN5X_RESET_DELAY,
+ .num_iio_channels = SEN55_NUM_CHANNELS,
+ .read_measured_cmd = SEN5X_READ_MEASURED,
+ .read_measured_rx_size = 24,
+ .temperature_compensation_required = true,
+ },
+};
+
+static int sen5x_probe(struct i2c_client *client)
+{
+ return sen_common_probe(client);
+}
+
+static const struct i2c_device_id sen5x_id[] = {
+ { "sen50", },
+ { "sen54", },
+ { "sen55", },
+ {} };
+
+MODULE_DEVICE_TABLE(i2c, sen5x_id);
+
+static const struct of_device_id sen5x_dt_ids[] = {
+ { .compatible = "sensirion,sen50", .data = &sen5x_chips[SEN50] },
+ { .compatible = "sensirion,sen54", .data = &sen5x_chips[SEN54] },
+ { .compatible = "sensirion,sen55", .data = &sen5x_chips[SEN55] },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, sen5x_dt_ids);
+
+static struct i2c_driver sen5x_driver = {
+ .driver = {
+ .name = "sen5x",
+ .of_match_table = sen5x_dt_ids,
+ },
+ .probe = sen5x_probe,
+ .id_table = sen5x_id,
+};
+module_i2c_driver(sen5x_driver);
+
+MODULE_DESCRIPTION("Sensirion SEN5x environmental sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/chemical/sen6x.c b/drivers/iio/chemical/sen6x.c
new file mode 100644
index 000000000000..ba99242f30ef
--- /dev/null
+++ b/drivers/iio/chemical/sen6x.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include "sen_common.h"
+
+#define SEN60_NUM_CHANNELS 4 /* PM1,PM2.5,PM4,PM10 */
+#define SEN65_NUM_CHANNELS 8 /* PM1,PM2.5,PM4,PM10,RHT,TEMP,VOC,NOx */
+#define SEN66_NUM_CHANNELS 9 /* PM1,PM2.5,PM4,PM10,RHT,TEMP,VOC,NOx,CO2 */
+
+#define SEN6X_RESET_DELAY 1200
+
+#define SEN6X_READ_MEASURED 0x0300
+
+enum {
+ SEN60,
+ SEN65,
+ SEN66,
+};
+
+static const struct sen_chip_info sen6x_chips[] = {
+ [SEN60] = {
+ .reset_delay = SEN6X_RESET_DELAY,
+ .num_iio_channels = SEN60_NUM_CHANNELS,
+ .read_measured_cmd = SEN6X_READ_MEASURED,
+ .read_measured_rx_size = 27,
+ .temperature_compensation_required = false,
+ },
+ [SEN65] = {
+ .reset_delay = SEN6X_RESET_DELAY,
+ .num_iio_channels = SEN65_NUM_CHANNELS,
+ .read_measured_cmd = SEN6X_READ_MEASURED,
+ .read_measured_rx_size = 27,
+ .temperature_compensation_required = false,
+ },
+ [SEN66] = {
+ .reset_delay = SEN6X_RESET_DELAY,
+ .num_iio_channels = SEN66_NUM_CHANNELS,
+ .read_measured_cmd = SEN6X_READ_MEASURED,
+ .read_measured_rx_size = 27,
+ .temperature_compensation_required = false,
+ },
+};
+
+static int sen6x_probe(struct i2c_client *client)
+{
+ return sen_common_probe(client);
+}
+
+static const struct i2c_device_id sen6x_id[] = {
+ { "sen60", },
+ { "sen65", },
+ { "sen66", },
+ {} };
+
+MODULE_DEVICE_TABLE(i2c, sen6x_id);
+
+static const struct of_device_id sen6x_dt_ids[] = {
+ { .compatible = "sensirion,sen60", .data = &sen6x_chips[SEN60] },
+ { .compatible = "sensirion,sen65", .data = &sen6x_chips[SEN65] },
+ { .compatible = "sensirion,sen66", .data = &sen6x_chips[SEN66] },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, sen6x_dt_ids);
+
+static struct i2c_driver sen6x_driver = {
+ .driver = {
+ .name = "sen6x",
+ .of_match_table = sen6x_dt_ids,
+ },
+ .probe = sen6x_probe,
+ .id_table = sen6x_id,
+};
+module_i2c_driver(sen6x_driver);
+
+MODULE_DESCRIPTION("Sensirion SEN6x environmental sensor");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/chemical/sen_common.c b/drivers/iio/chemical/sen_common.c
new file mode 100644
index 000000000000..5fad439c87d9
--- /dev/null
+++ b/drivers/iio/chemical/sen_common.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/crc8.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "sen_common.h"
+
+DECLARE_CRC8_TABLE(sen_common_crc8_table);
+
+enum {
+ PM1,
+ PM2P5,
+ PM4,
+ PM10,
+ RHT,
+ TEMP,
+ VOC,
+ NOX,
+ CO2,
+};
+
+#define SENXX_CRC8_POLYNOMIAL 0x31
+#define SENXX_CRC8_INIT 0xff
+
+/* Command */
+#define SENXX_START_MEASUREMENT 0x0021
+#define SENXX_STOP_MEASUREMENT 0x0104
+#define SENXX_READ_DATA_READY 0x0202
+#define SENXX_PRODUCT_NAME 0xD014
+#define SENXX_DEVICE_STATUS 0xD206
+#define SENXX_RESET 0xD304
+#define SEN5X_TEMPERATURE 0x60B2
+
+#define SENXX_CMD_HEAD_LEN 2
+
+#define SENXX_PRODUCT_NAME_RSP_LEN 48
+#define SENXX_DEVICE_STATUS_RSP_LEN 6
+
+#define SEN5X_TEMPERATURE_REQ_LEN 9
+
+/* For sen5x(03C4) is 24 and sen6x(0300) is 27 */
+#define SENXX_MAX_MEASURED_RSP_LEN 27
+
+#define PACK_CMD_HEAD(value, buffer) \
+ do { \
+ (buffer)[0] = ((value) >> 8) & 0xFF; \
+ (buffer)[1] = (value) & 0xFF; \
+ } while (0)
+
+/* Convert a 16-bit value to big-endian, store in a 3-bytes buffer with calculated CRC */
+#define PACK_TO_BUFFER_WITH_CRC(value, buffer) \
+ do { \
+ u16 __be_value = cpu_to_be16(value); \
+ (buffer)[0] = (__be_value >> 8) & 0xFF; \
+ (buffer)[1] = __be_value & 0xFF; \
+ (buffer)[2] = crc8(sen_common_crc8_table, buffer, 2, \
+ CRC8_INIT_VALUE); \
+ } while (0)
+
+static int sen_common_i2c_recv_reply(struct i2c_client *client,
+ unsigned char *buf, unsigned int size)
+{
+ struct device *dev = &client->dev;
+ unsigned char *tmp = buf;
+ int ret;
+ int i;
+
+ ret = i2c_master_recv(client, buf, size);
+ if (ret < 0)
+ return ret;
+ if (ret != size) {
+ dev_warn(dev, "i2c_master_recv ret: %d size: %u\n", ret, size);
+ return -EIO;
+ }
+
+ /* All the Read and Write Commands transmit the data in 2-byte packets,
+ * followed by an 8-bit checksum
+ */
+ for (i = 0; i < size / 3; i++) {
+ unsigned char crc;
+
+ crc = crc8(sen_common_crc8_table, buf + i * 3, 2,
+ CRC8_INIT_VALUE);
+ if (crc != buf[i * 3 + 2]) {
+ dev_err(dev, "data integrity check failed\n");
+ return -EIO;
+ }
+
+ *tmp++ = buf[i * 3];
+ *tmp++ = buf[i * 3 + 1];
+ }
+
+ return tmp - buf;
+}
+
+static int sen_common_simple_command(struct i2c_client *client,
+ unsigned int cmd, unsigned int time)
+{
+ unsigned char txbuf[SENXX_CMD_HEAD_LEN];
+ int ret;
+
+ PACK_CMD_HEAD(cmd, &txbuf[0]);
+
+ ret = i2c_master_send(client, txbuf, 2);
+ if (ret < 0)
+ return ret;
+
+ msleep(time);
+
+ return 0;
+}
+
+static int sen_common_product_name(struct i2c_client *client, char *name,
+ size_t len)
+{
+ unsigned char txbuf[SENXX_CMD_HEAD_LEN];
+ unsigned char rxbuf[SENXX_PRODUCT_NAME_RSP_LEN];
+ int ret;
+
+ PACK_CMD_HEAD(SENXX_PRODUCT_NAME, &txbuf[0]);
+
+ ret = i2c_master_send(client, txbuf, 2);
+ if (ret < 0)
+ return ret;
+
+ msleep(20);
+
+ ret = sen_common_i2c_recv_reply(client, rxbuf, sizeof(rxbuf));
+ if (ret < 0)
+ return ret;
+
+ rxbuf[ret] = '\0';
+
+ strscpy(name, rxbuf, len);
+
+ return 0;
+}
+
+static int sen_common_status(struct i2c_client *client, int *status)
+{
+ unsigned char txbuf[SENXX_CMD_HEAD_LEN];
+ unsigned char rxbuf[SENXX_DEVICE_STATUS_RSP_LEN];
+ int ret;
+
+ PACK_CMD_HEAD(SENXX_DEVICE_STATUS, &txbuf[0]);
+
+ ret = i2c_master_send(client, txbuf, 2);
+ if (ret < 0)
+ return ret;
+
+ msleep(20);
+
+ ret = sen_common_i2c_recv_reply(client, rxbuf, sizeof(rxbuf));
+ if (ret < 0)
+ return ret;
+
+ *status = (rxbuf[0] << 24) + (rxbuf[1] << 16) + (rxbuf[2] << 8) +
+ rxbuf[3];
+
+ return 0;
+}
+
+static int sen5x_set_temperature_compensation(struct i2c_client *client,
+ s16 offset, s16 slope, u16 time)
+{
+ unsigned char txbuf[SENXX_CMD_HEAD_LEN + SEN5X_TEMPERATURE_REQ_LEN];
+ int ret;
+
+ PACK_CMD_HEAD(SEN5X_TEMPERATURE, &txbuf[0]);
+
+ PACK_TO_BUFFER_WITH_CRC(offset, &txbuf[2]);
+ PACK_TO_BUFFER_WITH_CRC(slope, &txbuf[5]);
+ PACK_TO_BUFFER_WITH_CRC(time, &txbuf[8]);
+
+ ret = i2c_master_send(client, txbuf, sizeof(txbuf));
+ if (ret < 0)
+ return ret;
+
+ msleep(20);
+
+ return 0;
+}
+
+static int sen_common_fetch_measured(struct sen_common_state *state)
+{
+ struct i2c_client *client = state->client;
+ unsigned char txbuf[SENXX_CMD_HEAD_LEN];
+ unsigned char rxbuf[SENXX_MAX_MEASURED_RSP_LEN];
+ int rx_size = state->chip->read_measured_rx_size;
+ int ret;
+
+ if (rx_size > SENXX_MAX_MEASURED_RSP_LEN)
+ return -EINVAL;
+
+ PACK_CMD_HEAD(state->chip->read_measured_cmd, &txbuf[0]);
+
+ /* sensor can only be polled once a second max per datasheet */
+ if (!time_after(jiffies, state->last_update + HZ))
+ return 0;
+
+ ret = i2c_master_send(client, txbuf, sizeof(txbuf));
+ if (ret < 0)
+ return ret;
+
+ msleep(20);
+
+ ret = sen_common_i2c_recv_reply(client, rxbuf, rx_size);
+ if (ret < 0)
+ return ret;
+
+ memcpy(&state->data, rxbuf, ret);
+
+ state->last_update = jiffies;
+
+ return 0;
+}
+
+static ssize_t status_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct sen_common_state *state = iio_priv(indio_dev);
+ int status;
+ int ret;
+
+ ret = sen_common_status(state->client, &status);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%x\n", status);
+}
+
+static IIO_DEVICE_ATTR_RO(status, 0);
+
+static struct attribute *sen_common_attrs[] = {
+ &iio_dev_attr_status.dev_attr.attr, NULL
+};
+
+static const struct attribute_group sen_common_attr_group = {
+ .attrs = sen_common_attrs,
+};
+
+#define SEN_CHAN_SCAN_TYPE(_sign) \
+ .scan_type = { \
+ .sign = _sign, \
+ .realbits = 16, \
+ .storagebits = 16, \
+ .endianness = IIO_BE, \
+ }
+
+#define SEN_MASSCONC_CHAN(_index, _mod, _sign) \
+ { \
+ .type = IIO_MASSCONCENTRATION, .modified = 1, \
+ .channel2 = IIO_MOD_##_mod, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .address = _mod, .scan_index = _index, \
+ SEN_CHAN_SCAN_TYPE(_sign), \
+ }
+
+#define SEN_CONC_CHAN(_index, _mod, _sign) \
+ { \
+ .type = IIO_CONCENTRATION, .modified = 1, \
+ .channel2 = IIO_MOD_##_mod, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .address = _mod, .scan_index = _index, \
+ SEN_CHAN_SCAN_TYPE(_sign), \
+ }
+
+static const struct iio_chan_spec sen_common_channels[] = {
+ SEN_MASSCONC_CHAN(0, PM1, 'u'),
+ SEN_MASSCONC_CHAN(1, PM2P5, 'u'),
+ SEN_MASSCONC_CHAN(2, PM4, 'u'),
+ SEN_MASSCONC_CHAN(3, PM10, 'u'),
+ {
+ .type = IIO_HUMIDITYRELATIVE,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .address = RHT,
+ .scan_index = 4,
+ SEN_CHAN_SCAN_TYPE('s'),
+ },
+ {
+ .type = IIO_TEMP,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .address = TEMP,
+ .scan_index = 5,
+ SEN_CHAN_SCAN_TYPE('s'),
+ },
+ SEN_CONC_CHAN(6, VOC, 's'),
+ SEN_CONC_CHAN(7, NOX, 's'),
+ SEN_CONC_CHAN(8, CO2, 'u'),
+};
+
+static int sen_common_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ struct sen_common_state *state = iio_priv(indio_dev);
+ int ret = IIO_VAL_INT;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ mutex_lock(&state->lock);
+ ret = sen_common_fetch_measured(state);
+ if (ret < 0) {
+ mutex_unlock(&state->lock);
+ return ret;
+ }
+
+ *val = be16_to_cpu(state->data[chan->address]);
+
+ mutex_unlock(&state->lock);
+
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ switch (chan->type) {
+ case IIO_MASSCONCENTRATION:
+ *val = 0;
+ *val2 = 100000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_HUMIDITYRELATIVE:
+ *val = 0;
+ *val2 = 10000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_TEMP:
+ *val = 0;
+ *val2 = 5000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_CONCENTRATION:
+ switch (chan->channel2) {
+ case IIO_MOD_NOX:
+ case IIO_MOD_VOC:
+ *val = 0;
+ *val2 = 100000;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ case IIO_MOD_CO2:
+ *val = 1;
+ *val2 = 0;
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = 1;
+ return IIO_VAL_INT;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info sen_common_info = {
+ .attrs = &sen_common_attr_group,
+ .read_raw = sen_common_read_raw,
+};
+
+static irqreturn_t sen_common_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct sen_common_state *state = iio_priv(indio_dev);
+
+ mutex_lock(&state->lock);
+
+ if (sen_common_fetch_measured(state) < 0)
+ goto out;
+
+ iio_push_to_buffers(indio_dev, &state->data);
+
+out:
+ mutex_unlock(&state->lock);
+
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+int sen_common_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ const struct sen_chip_info *chip;
+ const char *name = client->name;
+ struct sen_common_state *state;
+ struct iio_dev *indio_dev;
+ char senxx[8];
+ int ret = 0;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(struct sen_common_state));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ chip = i2c_get_match_data(client);
+
+ state = iio_priv(indio_dev);
+ state->client = client;
+ state->dev = dev;
+ mutex_init(&state->lock);
+ state->chip = chip;
+
+ crc8_populate_msb(sen_common_crc8_table, SENXX_CRC8_POLYNOMIAL);
+
+ ret = sen_common_simple_command(client, SENXX_RESET, chip->reset_delay);
+ if (ret < 0)
+ return ret;
+
+ ret = sen_common_product_name(client, senxx, sizeof(senxx));
+ if (ret < 0)
+ return ret;
+
+ if (strncasecmp(senxx, name, sizeof(senxx)) != 0)
+ dev_warn(dev, "chip mismatch: %s != %s\n", senxx, name);
+
+ /* Set default temperature compensation parameters */
+ if (chip->temperature_compensation_required) {
+ ret = sen5x_set_temperature_compensation(client, 0, 0, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = sen_common_simple_command(client, SENXX_START_MEASUREMENT, 50);
+ if (ret < 0)
+ return ret;
+
+ indio_dev->name = name;
+ indio_dev->info = &sen_common_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = sen_common_channels;
+ indio_dev->num_channels = chip->num_iio_channels;
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
+ sen_common_trigger_handler, NULL);
+ if (ret)
+ return ret;
+
+ ret = devm_iio_device_register(dev, indio_dev);
+ if (ret)
+ dev_err(dev, "failed to register iio device\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sen_common_probe);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Sensirion SEN5x/SEN6x common functionality");
diff --git a/drivers/iio/chemical/sen_common.h b/drivers/iio/chemical/sen_common.h
new file mode 100644
index 000000000000..aa40053c53ae
--- /dev/null
+++ b/drivers/iio/chemical/sen_common.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#ifndef IIO_SEN_COMMON_H
+#define IIO_SEN_COMMON_H
+
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+
+struct sen_chip_info {
+ int reset_delay;
+ int num_iio_channels;
+ unsigned int read_measured_cmd;
+ unsigned int read_measured_rx_size;
+ bool temperature_compensation_required;
+};
+
+struct sen_common_state {
+ struct device *dev;
+ struct i2c_client *client;
+ struct mutex lock;
+ /* PM1, PM2.5, PM4, PM10, RHT, TEMP, VOC, NOX, CO2 */
+ __be16 data[9];
+ const struct sen_chip_info *chip;
+ unsigned long last_update;
+};
+
+int sen_common_probe(struct i2c_client *client);
+
+#endif
--
2.45.2