summaryrefslogtreecommitdiff
path: root/drivers/iio/chemical
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/iio/chemical')
-rw-r--r--drivers/iio/chemical/Kconfig20
-rw-r--r--drivers/iio/chemical/Makefile2
-rw-r--r--drivers/iio/chemical/ags02ma.c4
-rw-r--r--drivers/iio/chemical/atlas-ezo-sensor.c4
-rw-r--r--drivers/iio/chemical/atlas-sensor.c14
-rw-r--r--drivers/iio/chemical/bme680_core.c4
-rw-r--r--drivers/iio/chemical/bme680_i2c.c4
-rw-r--r--drivers/iio/chemical/bme680_spi.c12
-rw-r--r--drivers/iio/chemical/ccs811.c83
-rw-r--r--drivers/iio/chemical/ens160_core.c4
-rw-r--r--drivers/iio/chemical/mhz19b.c316
-rw-r--r--drivers/iio/chemical/pms7003.c4
-rw-r--r--drivers/iio/chemical/scd30_core.c3
-rw-r--r--drivers/iio/chemical/scd4x.c10
-rw-r--r--drivers/iio/chemical/sen0322.c161
-rw-r--r--drivers/iio/chemical/sps30.c4
-rw-r--r--drivers/iio/chemical/sunrise_co2.c4
17 files changed, 573 insertions, 80 deletions
diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index 330fe0af946f..b22afa1f6d59 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -108,6 +108,16 @@ config IAQCORE
iAQ-Core Continuous/Pulsed VOC (Volatile Organic Compounds)
sensors
+config MHZ19B
+ tristate "Winsen MHZ19B CO2 sensor"
+ depends on SERIAL_DEV_BUS
+ help
+ Say Y here to build Serdev interface support for the Winsen
+ MHZ19B CO2 sensor.
+
+ To compile this driver as a module, choose M here: the module will
+ be called mhz19b.
+
config PMS7003
tristate "Plantower PMS7003 particulate matter sensor"
depends on SERIAL_DEV_BUS
@@ -166,6 +176,16 @@ config SCD4X
To compile this driver as a module, choose M here: the module will
be called scd4x.
+config SEN0322
+ tristate "SEN0322 oxygen sensor"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here to build support for the DFRobot SEN0322 oxygen sensor.
+
+ To compile this driver as a module, choose M here: the module will
+ be called sen0322.
+
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..2287a00a6b75 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -15,11 +15,13 @@ 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_MHZ19B) += mhz19b.o
obj-$(CONFIG_PMS7003) += pms7003.o
obj-$(CONFIG_SCD30_CORE) += scd30_core.o
obj-$(CONFIG_SCD30_I2C) += scd30_i2c.o
obj-$(CONFIG_SCD30_SERIAL) += scd30_serial.o
obj-$(CONFIG_SCD4X) += scd4x.o
+obj-$(CONFIG_SEN0322) += sen0322.o
obj-$(CONFIG_SENSEAIR_SUNRISE_CO2) += sunrise_co2.o
obj-$(CONFIG_SENSIRION_SGP30) += sgp30.o
obj-$(CONFIG_SENSIRION_SGP40) += sgp40.o
diff --git a/drivers/iio/chemical/ags02ma.c b/drivers/iio/chemical/ags02ma.c
index 8fcd80946543..151178d4e8f4 100644
--- a/drivers/iio/chemical/ags02ma.c
+++ b/drivers/iio/chemical/ags02ma.c
@@ -140,13 +140,13 @@ static int ags02ma_probe(struct i2c_client *client)
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);
diff --git a/drivers/iio/chemical/atlas-ezo-sensor.c b/drivers/iio/chemical/atlas-ezo-sensor.c
index 761a853a4d17..de0b87edd188 100644
--- a/drivers/iio/chemical/atlas-ezo-sensor.c
+++ b/drivers/iio/chemical/atlas-ezo-sensor.c
@@ -189,7 +189,7 @@ static const struct i2c_device_id atlas_ezo_id[] = {
{ "atlas-co2-ezo", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_CO2_EZO] },
{ "atlas-o2-ezo", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_O2_EZO] },
{ "atlas-hum-ezo", (kernel_ulong_t)&atlas_ezo_devices[ATLAS_HUM_EZO] },
- {}
+ { }
};
MODULE_DEVICE_TABLE(i2c, atlas_ezo_id);
@@ -197,7 +197,7 @@ static const struct of_device_id atlas_ezo_dt_ids[] = {
{ .compatible = "atlas,co2-ezo", .data = &atlas_ezo_devices[ATLAS_CO2_EZO], },
{ .compatible = "atlas,o2-ezo", .data = &atlas_ezo_devices[ATLAS_O2_EZO], },
{ .compatible = "atlas,hum-ezo", .data = &atlas_ezo_devices[ATLAS_HUM_EZO], },
- {}
+ { }
};
MODULE_DEVICE_TABLE(of, atlas_ezo_dt_ids);
diff --git a/drivers/iio/chemical/atlas-sensor.c b/drivers/iio/chemical/atlas-sensor.c
index baf93e5e3ca7..cb6662b92137 100644
--- a/drivers/iio/chemical/atlas-sensor.c
+++ b/drivers/iio/chemical/atlas-sensor.c
@@ -458,8 +458,9 @@ static irqreturn_t atlas_trigger_handler(int irq, void *private)
&data->buffer, sizeof(__be32) * channels);
if (!ret)
- iio_push_to_buffers_with_timestamp(indio_dev, data->buffer,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, data->buffer,
+ sizeof(data->buffer),
+ iio_get_time_ns(indio_dev));
iio_trigger_notify_done(indio_dev->trig);
@@ -518,13 +519,12 @@ static int atlas_read_raw(struct iio_dev *indio_dev,
case IIO_CONCENTRATION:
case IIO_ELECTRICALCONDUCTIVITY:
case IIO_VOLTAGE:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
ret = atlas_read_measurement(data, chan->address, &reg);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
break;
default:
ret = -EINVAL;
@@ -594,7 +594,7 @@ static const struct i2c_device_id atlas_id[] = {
{ "atlas-orp-sm", (kernel_ulong_t)&atlas_devices[ATLAS_ORP_SM] },
{ "atlas-do-sm", (kernel_ulong_t)&atlas_devices[ATLAS_DO_SM] },
{ "atlas-rtd-sm", (kernel_ulong_t)&atlas_devices[ATLAS_RTD_SM] },
- {}
+ { }
};
MODULE_DEVICE_TABLE(i2c, atlas_id);
diff --git a/drivers/iio/chemical/bme680_core.c b/drivers/iio/chemical/bme680_core.c
index 9d73fd2cf52c..3e850562ab00 100644
--- a/drivers/iio/chemical/bme680_core.c
+++ b/drivers/iio/chemical/bme680_core.c
@@ -1120,8 +1120,8 @@ static irqreturn_t bme680_trigger_handler(int irq, void *p)
gas_range = FIELD_GET(BME680_GAS_RANGE_MASK, gas_regs_val);
data->scan.chan[3] = bme680_compensate_gas(data, adc_gas_res, gas_range);
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ iio_get_time_ns(indio_dev));
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
diff --git a/drivers/iio/chemical/bme680_i2c.c b/drivers/iio/chemical/bme680_i2c.c
index ac7763f98a6a..5560ea708b36 100644
--- a/drivers/iio/chemical/bme680_i2c.c
+++ b/drivers/iio/chemical/bme680_i2c.c
@@ -37,13 +37,13 @@ static int bme680_i2c_probe(struct i2c_client *client)
static const struct i2c_device_id bme680_i2c_id[] = {
{ "bme680" },
- {}
+ { }
};
MODULE_DEVICE_TABLE(i2c, bme680_i2c_id);
static const struct of_device_id bme680_of_i2c_match[] = {
{ .compatible = "bosch,bme680", },
- {},
+ { }
};
MODULE_DEVICE_TABLE(of, bme680_of_i2c_match);
diff --git a/drivers/iio/chemical/bme680_spi.c b/drivers/iio/chemical/bme680_spi.c
index ecb24ba0ebc9..aa97645ba539 100644
--- a/drivers/iio/chemical/bme680_spi.c
+++ b/drivers/iio/chemical/bme680_spi.c
@@ -112,14 +112,6 @@ static int bme680_spi_probe(struct spi_device *spi)
const struct spi_device_id *id = spi_get_device_id(spi);
struct bme680_spi_bus_context *bus_context;
struct regmap *regmap;
- int ret;
-
- spi->bits_per_word = 8;
- ret = spi_setup(spi);
- if (ret < 0) {
- dev_err(&spi->dev, "spi_setup failed!\n");
- return ret;
- }
bus_context = devm_kzalloc(&spi->dev, sizeof(*bus_context), GFP_KERNEL);
if (!bus_context)
@@ -140,13 +132,13 @@ static int bme680_spi_probe(struct spi_device *spi)
static const struct spi_device_id bme680_spi_id[] = {
{"bme680", 0},
- {},
+ { }
};
MODULE_DEVICE_TABLE(spi, bme680_spi_id);
static const struct of_device_id bme680_of_spi_match[] = {
{ .compatible = "bosch,bme680", },
- {},
+ { }
};
MODULE_DEVICE_TABLE(of, bme680_of_spi_match);
diff --git a/drivers/iio/chemical/ccs811.c b/drivers/iio/chemical/ccs811.c
index 451fb65dbe60..998c9239c4c7 100644
--- a/drivers/iio/chemical/ccs811.c
+++ b/drivers/iio/chemical/ccs811.c
@@ -15,6 +15,7 @@
* 4. Read error register and put the information in logs
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
@@ -214,6 +215,40 @@ static int ccs811_get_measurement(struct ccs811_data *data)
return ret;
}
+static int ccs811_read_info_raw(struct ccs811_data *data,
+ struct iio_chan_spec const *chan,
+ int *val, int mask)
+{
+ int ret;
+
+ guard(mutex)(&data->lock);
+ ret = ccs811_get_measurement(data);
+ if (ret < 0)
+ return ret;
+
+ switch (chan->type) {
+ case IIO_VOLTAGE:
+ *val = be16_to_cpu(data->buffer.raw_data) & CCS811_VOLTAGE_MASK;
+ return IIO_VAL_INT;
+ case IIO_CURRENT:
+ *val = be16_to_cpu(data->buffer.raw_data) >> 10;
+ return IIO_VAL_INT;
+ case IIO_CONCENTRATION:
+ switch (chan->channel2) {
+ case IIO_MOD_CO2:
+ *val = be16_to_cpu(data->buffer.co2);
+ return IIO_VAL_INT;
+ case IIO_MOD_VOC:
+ *val = be16_to_cpu(data->buffer.voc);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
static int ccs811_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
@@ -223,46 +258,12 @@ static int ccs811_read_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_RAW:
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
- mutex_lock(&data->lock);
- ret = ccs811_get_measurement(data);
- if (ret < 0) {
- mutex_unlock(&data->lock);
- iio_device_release_direct_mode(indio_dev);
- return ret;
- }
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
- switch (chan->type) {
- case IIO_VOLTAGE:
- *val = be16_to_cpu(data->buffer.raw_data) &
- CCS811_VOLTAGE_MASK;
- ret = IIO_VAL_INT;
- break;
- case IIO_CURRENT:
- *val = be16_to_cpu(data->buffer.raw_data) >> 10;
- ret = IIO_VAL_INT;
- break;
- case IIO_CONCENTRATION:
- switch (chan->channel2) {
- case IIO_MOD_CO2:
- *val = be16_to_cpu(data->buffer.co2);
- ret = IIO_VAL_INT;
- break;
- case IIO_MOD_VOC:
- *val = be16_to_cpu(data->buffer.voc);
- ret = IIO_VAL_INT;
- break;
- default:
- ret = -EINVAL;
- }
- break;
- default:
- ret = -EINVAL;
- }
- mutex_unlock(&data->lock);
- iio_device_release_direct_mode(indio_dev);
+ ret = ccs811_read_info_raw(data, chan, val, mask);
+
+ iio_device_release_direct(indio_dev);
return ret;
@@ -342,8 +343,8 @@ static irqreturn_t ccs811_trigger_handler(int irq, void *p)
goto err;
}
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ iio_get_time_ns(indio_dev));
err:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/chemical/ens160_core.c b/drivers/iio/chemical/ens160_core.c
index 152f81ff57e3..6cec60074827 100644
--- a/drivers/iio/chemical/ens160_core.c
+++ b/drivers/iio/chemical/ens160_core.c
@@ -267,8 +267,8 @@ static irqreturn_t ens160_trigger_handler(int irq, void *p)
if (ret)
goto err;
- iio_push_to_buffers_with_timestamp(indio_dev, &data->scan,
- pf->timestamp);
+ iio_push_to_buffers_with_ts(indio_dev, &data->scan, sizeof(data->scan),
+ pf->timestamp);
err:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/chemical/mhz19b.c b/drivers/iio/chemical/mhz19b.c
new file mode 100644
index 000000000000..3c64154918b1
--- /dev/null
+++ b/drivers/iio/chemical/mhz19b.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * mh-z19b CO₂ sensor driver
+ *
+ * Copyright (c) 2025 Gyeyoung Baek <gye976@gmail.com>
+ *
+ * Datasheet:
+ * https://www.winsen-sensor.com/d/files/infrared-gas-sensor/mh-z19b-co2-ver1_0.pdf
+ */
+
+#include <linux/array_size.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/jiffies.h>
+#include <linux/kstrtox.h>
+#include <linux/minmax.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/serdev.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+/*
+ * Commands have following format:
+ *
+ * +------+------+-----+------+------+------+------+------+-------+
+ * | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum |
+ * +------+------+-----+------+------+------+------+------+-------+
+ */
+#define MHZ19B_CMD_SIZE 9
+
+/* ABC logic in MHZ19B means auto calibration. */
+#define MHZ19B_ABC_LOGIC_CMD 0x79
+#define MHZ19B_READ_CO2_CMD 0x86
+#define MHZ19B_SPAN_POINT_CMD 0x88
+#define MHZ19B_ZERO_POINT_CMD 0x87
+
+#define MHZ19B_SPAN_POINT_PPM_MIN 1000
+#define MHZ19B_SPAN_POINT_PPM_MAX 5000
+
+#define MHZ19B_SERDEV_TIMEOUT msecs_to_jiffies(100)
+
+struct mhz19b_state {
+ struct serdev_device *serdev;
+
+ /* Must wait until the 'buf' is filled with 9 bytes.*/
+ struct completion buf_ready;
+
+ u8 buf_idx;
+ /*
+ * Serdev receive buffer.
+ * When data is received from the MH-Z19B,
+ * the 'mhz19b_receive_buf' callback function is called and fills this buffer.
+ */
+ u8 buf[MHZ19B_CMD_SIZE] __aligned(IIO_DMA_MINALIGN);
+};
+
+static u8 mhz19b_get_checksum(u8 *cmd_buf)
+{
+ u8 i, checksum = 0;
+
+/*
+ * +------+------+-----+------+------+------+------+------+-------+
+ * | 0xFF | 0x01 | cmd | arg0 | arg1 | 0x00 | 0x00 | 0x00 | cksum |
+ * +------+------+-----+------+------+------+------+------+-------+
+ * i:1 2 3 4 5 6 7
+ *
+ * Sum all cmd_buf elements from index 1 to 7.
+ */
+ for (i = 1; i < 8; i++)
+ checksum += cmd_buf[i];
+
+ return -checksum;
+}
+
+static int mhz19b_serdev_cmd(struct iio_dev *indio_dev, int cmd, u16 arg)
+{
+ struct mhz19b_state *st = iio_priv(indio_dev);
+ struct serdev_device *serdev = st->serdev;
+ struct device *dev = &indio_dev->dev;
+ int ret;
+
+ /*
+ * cmd_buf[3,4] : arg0,1
+ * cmd_buf[8] : checksum
+ */
+ u8 cmd_buf[MHZ19B_CMD_SIZE] = {
+ 0xFF, 0x01, cmd,
+ };
+
+ switch (cmd) {
+ case MHZ19B_ABC_LOGIC_CMD:
+ cmd_buf[3] = arg ? 0xA0 : 0;
+ break;
+ case MHZ19B_SPAN_POINT_CMD:
+ put_unaligned_be16(arg, &cmd_buf[3]);
+ break;
+ default:
+ break;
+ }
+ cmd_buf[8] = mhz19b_get_checksum(cmd_buf);
+
+ /* Write buf to uart ctrl synchronously */
+ ret = serdev_device_write(serdev, cmd_buf, MHZ19B_CMD_SIZE, 0);
+ if (ret < 0)
+ return ret;
+ if (ret != MHZ19B_CMD_SIZE)
+ return -EIO;
+
+ switch (cmd) {
+ case MHZ19B_READ_CO2_CMD:
+ ret = wait_for_completion_interruptible_timeout(&st->buf_ready,
+ MHZ19B_SERDEV_TIMEOUT);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -ETIMEDOUT;
+
+ if (st->buf[8] != mhz19b_get_checksum(st->buf)) {
+ dev_err(dev, "checksum err");
+ return -EINVAL;
+ }
+
+ return get_unaligned_be16(&st->buf[2]);
+ default:
+ /* No response commands. */
+ return 0;
+ }
+}
+
+static int mhz19b_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long mask)
+{
+ int ret;
+
+ ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_READ_CO2_CMD, 0);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return IIO_VAL_INT;
+}
+
+/*
+ * echo 0 > calibration_auto_enable : ABC logic off
+ * echo 1 > calibration_auto_enable : ABC logic on
+ */
+static ssize_t calibration_auto_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ bool enable;
+ int ret;
+
+ ret = kstrtobool(buf, &enable);
+ if (ret)
+ return ret;
+
+ ret = mhz19b_serdev_cmd(indio_dev, MHZ19B_ABC_LOGIC_CMD, enable);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+static IIO_DEVICE_ATTR_WO(calibration_auto_enable, 0);
+
+/*
+ * echo 0 > calibration_forced_value : zero point calibration
+ * (make sure the sensor has been working under 400ppm for over 20 minutes.)
+ * echo [1000 1 5000] > calibration_forced_value : span point calibration
+ * (make sure the sensor has been working under a certain level CO₂ for over 20 minutes.)
+ */
+static ssize_t calibration_forced_value_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ u16 ppm;
+ int cmd, ret;
+
+ ret = kstrtou16(buf, 0, &ppm);
+ if (ret)
+ return ret;
+
+ if (ppm) {
+ if (!in_range(ppm, MHZ19B_SPAN_POINT_PPM_MIN,
+ MHZ19B_SPAN_POINT_PPM_MAX - MHZ19B_SPAN_POINT_PPM_MIN + 1)) {
+ dev_dbg(&indio_dev->dev,
+ "span point ppm should be in a range [%d-%d]\n",
+ MHZ19B_SPAN_POINT_PPM_MIN, MHZ19B_SPAN_POINT_PPM_MAX);
+ return -EINVAL;
+ }
+
+ cmd = MHZ19B_SPAN_POINT_CMD;
+ } else {
+ cmd = MHZ19B_ZERO_POINT_CMD;
+ }
+
+ ret = mhz19b_serdev_cmd(indio_dev, cmd, ppm);
+ if (ret < 0)
+ return ret;
+
+ return len;
+}
+static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0);
+
+static struct attribute *mhz19b_attrs[] = {
+ &iio_dev_attr_calibration_auto_enable.dev_attr.attr,
+ &iio_dev_attr_calibration_forced_value.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group mhz19b_attr_group = {
+ .attrs = mhz19b_attrs,
+};
+
+static const struct iio_info mhz19b_info = {
+ .attrs = &mhz19b_attr_group,
+ .read_raw = mhz19b_read_raw,
+};
+
+static const struct iio_chan_spec mhz19b_channels[] = {
+ {
+ .type = IIO_CONCENTRATION,
+ .channel2 = IIO_MOD_CO2,
+ .modified = 1,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+};
+
+static size_t mhz19b_receive_buf(struct serdev_device *serdev,
+ const u8 *data, size_t len)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
+ struct mhz19b_state *st = iio_priv(indio_dev);
+
+ memcpy(st->buf + st->buf_idx, data, len);
+ st->buf_idx += len;
+
+ if (st->buf_idx == MHZ19B_CMD_SIZE) {
+ st->buf_idx = 0;
+ complete(&st->buf_ready);
+ }
+
+ return len;
+}
+
+static const struct serdev_device_ops mhz19b_ops = {
+ .receive_buf = mhz19b_receive_buf,
+ .write_wakeup = serdev_device_write_wakeup,
+};
+
+static int mhz19b_probe(struct serdev_device *serdev)
+{
+ int ret;
+ struct device *dev = &serdev->dev;
+ struct iio_dev *indio_dev;
+ struct mhz19b_state *st;
+
+ serdev_device_set_client_ops(serdev, &mhz19b_ops);
+ ret = devm_serdev_device_open(dev, serdev);
+ if (ret)
+ return ret;
+ serdev_device_set_baudrate(serdev, 9600);
+ serdev_device_set_flow_control(serdev, false);
+ ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+ if (ret)
+ return ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ serdev_device_set_drvdata(serdev, indio_dev);
+
+ st = iio_priv(indio_dev);
+ st->serdev = serdev;
+
+ init_completion(&st->buf_ready);
+
+ ret = devm_regulator_get_enable(dev, "vin");
+ if (ret)
+ return ret;
+
+ indio_dev->name = "mh-z19b";
+ indio_dev->channels = mhz19b_channels;
+ indio_dev->num_channels = ARRAY_SIZE(mhz19b_channels);
+ indio_dev->info = &mhz19b_info;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id mhz19b_of_match[] = {
+ { .compatible = "winsen,mhz19b", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mhz19b_of_match);
+
+static struct serdev_device_driver mhz19b_driver = {
+ .driver = {
+ .name = "mhz19b",
+ .of_match_table = mhz19b_of_match,
+ },
+ .probe = mhz19b_probe,
+};
+module_serdev_device_driver(mhz19b_driver);
+
+MODULE_AUTHOR("Gyeyoung Baek");
+MODULE_DESCRIPTION("MH-Z19B CO2 sensor driver using serdev interface");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/chemical/pms7003.c b/drivers/iio/chemical/pms7003.c
index e05ce1f12065..656d4a12c58f 100644
--- a/drivers/iio/chemical/pms7003.c
+++ b/drivers/iio/chemical/pms7003.c
@@ -127,8 +127,8 @@ static irqreturn_t pms7003_trigger_handler(int irq, void *p)
pms7003_get_pm(frame->data + PMS7003_PM10_OFFSET);
mutex_unlock(&state->lock);
- iio_push_to_buffers_with_timestamp(indio_dev, &state->scan,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &state->scan, sizeof(state->scan),
+ iio_get_time_ns(indio_dev));
err:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/chemical/scd30_core.c b/drivers/iio/chemical/scd30_core.c
index 3fed6b63710f..8316720b1fa3 100644
--- a/drivers/iio/chemical/scd30_core.c
+++ b/drivers/iio/chemical/scd30_core.c
@@ -601,7 +601,8 @@ static irqreturn_t scd30_trigger_handler(int irq, void *p)
if (ret)
goto out;
- iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
diff --git a/drivers/iio/chemical/scd4x.c b/drivers/iio/chemical/scd4x.c
index 50e3ac44422b..2463149519b6 100644
--- a/drivers/iio/chemical/scd4x.c
+++ b/drivers/iio/chemical/scd4x.c
@@ -358,15 +358,14 @@ static int scd4x_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
}
- ret = iio_device_claim_direct_mode(indio_dev);
- if (ret)
- return ret;
+ if (!iio_device_claim_direct(indio_dev))
+ return -EBUSY;
mutex_lock(&state->lock);
ret = scd4x_read_channel(state, chan->address);
mutex_unlock(&state->lock);
- iio_device_release_direct_mode(indio_dev);
+ iio_device_release_direct(indio_dev);
if (ret < 0)
return ret;
@@ -676,7 +675,8 @@ static irqreturn_t scd4x_trigger_handler(int irq, void *p)
if (ret)
goto out;
- iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
out:
iio_trigger_notify_done(indio_dev->trig);
return IRQ_HANDLED;
diff --git a/drivers/iio/chemical/sen0322.c b/drivers/iio/chemical/sen0322.c
new file mode 100644
index 000000000000..96c6fc1203ad
--- /dev/null
+++ b/drivers/iio/chemical/sen0322.c
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the DFRobot SEN0322 oxygen sensor.
+ *
+ * Datasheet:
+ * https://wiki.dfrobot.com/Gravity_I2C_Oxygen_Sensor_SKU_SEN0322
+ *
+ * Possible I2C slave addresses:
+ * 0x70
+ * 0x71
+ * 0x72
+ * 0x73
+ *
+ * Copyright (C) 2025 Tóth János <gomba007@gmail.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#include <linux/iio/iio.h>
+
+#define SEN0322_REG_DATA 0x03
+#define SEN0322_REG_COEFF 0x0A
+
+struct sen0322 {
+ struct regmap *regmap;
+};
+
+static int sen0322_read_data(struct sen0322 *sen0322)
+{
+ u8 data[3] = { };
+ int ret;
+
+ ret = regmap_bulk_read(sen0322->regmap, SEN0322_REG_DATA, data,
+ sizeof(data));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The actual value in the registers is:
+ * val = data[0] + data[1] / 10 + data[2] / 100
+ * but it is multiplied by 100 here to avoid floating-point math
+ * and the scale is divided by 100 to compensate this.
+ */
+ return data[0] * 100 + data[1] * 10 + data[2];
+}
+
+static int sen0322_read_scale(struct sen0322 *sen0322, int *num, int *den)
+{
+ u32 val;
+ int ret;
+
+ ret = regmap_read(sen0322->regmap, SEN0322_REG_COEFF, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val) {
+ *num = val;
+ *den = 100000; /* Coeff is scaled by 1000 at calibration. */
+ } else { /* The device is not calibrated, using the factory-defaults. */
+ *num = 209; /* Oxygen content in the atmosphere is 20.9%. */
+ *den = 120000; /* Output of the sensor at 20.9% is 120 uA. */
+ }
+
+ dev_dbg(regmap_get_device(sen0322->regmap), "scale: %d/%d\n",
+ *num, *den);
+
+ return 0;
+}
+
+static int sen0322_read_raw(struct iio_dev *iio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct sen0322 *sen0322 = iio_priv(iio_dev);
+ int ret;
+
+ if (chan->type != IIO_CONCENTRATION)
+ return -EINVAL;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = sen0322_read_data(sen0322);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return IIO_VAL_INT;
+
+ case IIO_CHAN_INFO_SCALE:
+ ret = sen0322_read_scale(sen0322, val, val2);
+ if (ret < 0)
+ return ret;
+
+ return IIO_VAL_FRACTIONAL;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info sen0322_info = {
+ .read_raw = sen0322_read_raw,
+};
+
+static const struct regmap_config sen0322_regmap_conf = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static const struct iio_chan_spec sen0322_channel = {
+ .type = IIO_CONCENTRATION,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SCALE),
+};
+
+static int sen0322_probe(struct i2c_client *client)
+{
+ struct sen0322 *sen0322;
+ struct iio_dev *iio_dev;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -ENODEV;
+
+ iio_dev = devm_iio_device_alloc(&client->dev, sizeof(*sen0322));
+ if (!iio_dev)
+ return -ENOMEM;
+
+ sen0322 = iio_priv(iio_dev);
+
+ sen0322->regmap = devm_regmap_init_i2c(client, &sen0322_regmap_conf);
+ if (IS_ERR(sen0322->regmap))
+ return PTR_ERR(sen0322->regmap);
+
+ iio_dev->info = &sen0322_info;
+ iio_dev->name = "sen0322";
+ iio_dev->channels = &sen0322_channel;
+ iio_dev->num_channels = 1;
+ iio_dev->modes = INDIO_DIRECT_MODE;
+
+ return devm_iio_device_register(&client->dev, iio_dev);
+}
+
+static const struct of_device_id sen0322_of_match[] = {
+ { .compatible = "dfrobot,sen0322" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sen0322_of_match);
+
+static struct i2c_driver sen0322_driver = {
+ .driver = {
+ .name = "sen0322",
+ .of_match_table = sen0322_of_match,
+ },
+ .probe = sen0322_probe,
+};
+module_i2c_driver(sen0322_driver);
+
+MODULE_AUTHOR("Tóth János <gomba007@gmail.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SEN0322 oxygen sensor driver");
diff --git a/drivers/iio/chemical/sps30.c b/drivers/iio/chemical/sps30.c
index a7888146188d..a934bf0298dd 100644
--- a/drivers/iio/chemical/sps30.c
+++ b/drivers/iio/chemical/sps30.c
@@ -117,8 +117,8 @@ static irqreturn_t sps30_trigger_handler(int irq, void *p)
if (ret)
goto err;
- iio_push_to_buffers_with_timestamp(indio_dev, &scan,
- iio_get_time_ns(indio_dev));
+ iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+ iio_get_time_ns(indio_dev));
err:
iio_trigger_notify_done(indio_dev->trig);
diff --git a/drivers/iio/chemical/sunrise_co2.c b/drivers/iio/chemical/sunrise_co2.c
index cdb8696a4e81..af79efde37e8 100644
--- a/drivers/iio/chemical/sunrise_co2.c
+++ b/drivers/iio/chemical/sunrise_co2.c
@@ -373,7 +373,7 @@ static const struct iio_chan_spec_ext_info sunrise_concentration_ext_info[] = {
.read = iio_enum_available_read,
.private = (uintptr_t)&sunrise_error_statuses_enum,
},
- {}
+ { }
};
static const struct iio_chan_spec sunrise_channels[] = {
@@ -519,7 +519,7 @@ static int sunrise_probe(struct i2c_client *client)
static const struct of_device_id sunrise_of_match[] = {
{ .compatible = "senseair,sunrise-006-0-0007" },
- {}
+ { }
};
MODULE_DEVICE_TABLE(of, sunrise_of_match);