lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <20250206061521.2546108-4-Hermes.Zhang@axis.com>
Date: Thu, 6 Feb 2025 14:15:17 +0800
From: Hermes Zhang <Hermes.Zhang@...s.com>
To: <jic23@...nel.org>, <robh@...nel.org>, <lars@...afoo.de>,
	<krzk+dt@...nel.org>
CC: <kernel@...s.com>, Hermes Zhang <Hermes.Zhang@...s.com>,
	<linux-kernel@...r.kernel.org>, <linux-iio@...r.kernel.org>
Subject: [PATCH 3/3] iio: chemical: add support for Sensirion SEN5x/SEN6x

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@...s.com>
---
 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


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ