diff options
Diffstat (limited to 'drivers/iio/chemical')
-rw-r--r-- | drivers/iio/chemical/Kconfig | 20 | ||||
-rw-r--r-- | drivers/iio/chemical/Makefile | 2 | ||||
-rw-r--r-- | drivers/iio/chemical/ags02ma.c | 4 | ||||
-rw-r--r-- | drivers/iio/chemical/atlas-ezo-sensor.c | 4 | ||||
-rw-r--r-- | drivers/iio/chemical/atlas-sensor.c | 14 | ||||
-rw-r--r-- | drivers/iio/chemical/bme680_core.c | 4 | ||||
-rw-r--r-- | drivers/iio/chemical/bme680_i2c.c | 4 | ||||
-rw-r--r-- | drivers/iio/chemical/bme680_spi.c | 12 | ||||
-rw-r--r-- | drivers/iio/chemical/ccs811.c | 83 | ||||
-rw-r--r-- | drivers/iio/chemical/ens160_core.c | 4 | ||||
-rw-r--r-- | drivers/iio/chemical/mhz19b.c | 316 | ||||
-rw-r--r-- | drivers/iio/chemical/pms7003.c | 4 | ||||
-rw-r--r-- | drivers/iio/chemical/scd30_core.c | 3 | ||||
-rw-r--r-- | drivers/iio/chemical/scd4x.c | 10 | ||||
-rw-r--r-- | drivers/iio/chemical/sen0322.c | 161 | ||||
-rw-r--r-- | drivers/iio/chemical/sps30.c | 4 | ||||
-rw-r--r-- | drivers/iio/chemical/sunrise_co2.c | 4 |
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, ®); - 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); |