diff mbox series

[3/3] drivers: iio: chemical: Add support for Sensirion SCD4x CO2 sensor

Message ID 20210831074832.16310-4-roan@protonic.nl (mailing list archive)
State Superseded
Headers show
Series iio: chemical: Add support for Sensirion SCD4x CO2 sensor | expand

Commit Message

Roan van Dijk Aug. 31, 2021, 7:48 a.m. UTC
This is a driver for the SCD4x CO2 sensor from Sensirion. The sensor is
able to measure CO2 concentration, temperature and relative humdity.
The sensor uses a photoacoustic principle for measuring CO2 concentration.
An I2C interface is supported by this driver in order to communicate with
the sensor.

Signed-off-by: Roan van Dijk <roan@protonic.nl>
---
 drivers/iio/chemical/Kconfig  |  13 +
 drivers/iio/chemical/Makefile |   1 +
 drivers/iio/chemical/scd4x.c  | 707 ++++++++++++++++++++++++++++++++++
 3 files changed, 721 insertions(+)
 create mode 100644 drivers/iio/chemical/scd4x.c

Comments

kernel test robot Aug. 31, 2021, 11:35 p.m. UTC | #1
Hi Roan,

I love your patch! Perhaps something to improve:

[auto build test WARNING on robh/for-next]
[also build test WARNING on linux/master linus/master v5.14]
[cannot apply to iio/togreg next-20210831]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Roan-van-Dijk/iio-chemical-Add-support-for-Sensirion-SCD4x-CO2-sensor/20210831-202025
base:   https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next
config: hexagon-randconfig-r036-20210901 (attached as .config)
compiler: clang version 14.0.0 (https://github.com/llvm/llvm-project 4b1fde8a2b681dad2ce0c082a5d6422caa06b0bc)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/605896bbee687d465d4ae58d910878e9b85f0035
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Roan-van-Dijk/iio-chemical-Add-support-for-Sensirion-SCD4x-CO2-sensor/20210831-202025
        git checkout 605896bbee687d465d4ae58d910878e9b85f0035
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=hexagon 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/iio/chemical/scd4x.c:503:20: warning: no previous prototype for function 'scd4x_suspend' [-Wmissing-prototypes]
   int __maybe_unused scd4x_suspend(struct device *dev)
                      ^
   drivers/iio/chemical/scd4x.c:503:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   int __maybe_unused scd4x_suspend(struct device *dev)
   ^
   static 
>> drivers/iio/chemical/scd4x.c:517:20: warning: no previous prototype for function 'scd4x_resume' [-Wmissing-prototypes]
   int __maybe_unused scd4x_resume(struct device *dev)
                      ^
   drivers/iio/chemical/scd4x.c:517:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   int __maybe_unused scd4x_resume(struct device *dev)
   ^
   static 
>> drivers/iio/chemical/scd4x.c:613:5: warning: no previous prototype for function 'scd4x_probe' [-Wmissing-prototypes]
   int scd4x_probe(struct i2c_client *client, const struct i2c_device_id *id)
       ^
   drivers/iio/chemical/scd4x.c:613:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   int scd4x_probe(struct i2c_client *client, const struct i2c_device_id *id)
   ^
   static 
   3 warnings generated.


vim +/scd4x_suspend +503 drivers/iio/chemical/scd4x.c

   502	
 > 503	int __maybe_unused scd4x_suspend(struct device *dev)
   504	{
   505		struct iio_dev *indio_dev = dev_get_drvdata(dev);
   506		struct scd4x_state *state  = iio_priv(indio_dev);
   507		int ret;
   508	
   509		ret = scd4x_send_command(state, CMD_STOP_MEAS);
   510		if (ret)
   511			return ret;
   512	
   513		return regulator_disable(state->vdd);
   514	}
   515	EXPORT_SYMBOL(scd4x_suspend);
   516	
 > 517	int __maybe_unused scd4x_resume(struct device *dev)
   518	{
   519		struct iio_dev *indio_dev = dev_get_drvdata(dev);
   520		struct scd4x_state *state = iio_priv(indio_dev);
   521		int ret;
   522	
   523		ret = regulator_enable(state->vdd);
   524		if (ret)
   525			return ret;
   526	
   527		return scd4x_send_command(state, CMD_START_MEAS);
   528	}
   529	EXPORT_SYMBOL(scd4x_resume);
   530	
   531	static void scd4x_stop_meas(void *data)
   532	{
   533		struct scd4x_state *state = data;
   534	
   535		scd4x_send_command(state, CMD_STOP_MEAS);
   536	}
   537	
   538	static void scd4x_disable_regulator(void *data)
   539	{
   540		struct scd4x_state *state = data;
   541	
   542		regulator_disable(state->vdd);
   543	}
   544	
   545	static irqreturn_t scd4x_trigger_handler(int irq, void *p)
   546	{
   547		struct iio_poll_func *pf = p;
   548		struct iio_dev *indio_dev = pf->indio_dev;
   549		struct scd4x_state *state = iio_priv(indio_dev);
   550		struct {
   551			int data[3];
   552			int64_t ts __aligned(8);
   553		} scan;
   554		int ret;
   555	
   556		mutex_lock(&state->lock);
   557		ret = scd4x_read_poll(state);
   558		memset(&scan, 0, sizeof(scan));
   559		memcpy(scan.data, state->meas, sizeof(state->meas));
   560		mutex_unlock(&state->lock);
   561		if (ret)
   562			goto out;
   563	
   564		iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev));
   565	out:
   566		iio_trigger_notify_done(indio_dev->trig);
   567		return IRQ_HANDLED;
   568	}
   569	
   570	static int scd4x_set_trigger_state(struct iio_trigger *trig, bool state)
   571	{
   572		struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
   573		struct scd4x_state *st = iio_priv(indio_dev);
   574	
   575		if (state)
   576			enable_irq(st->irq);
   577		else
   578			disable_irq(st->irq);
   579	
   580		return 0;
   581	}
   582	
   583	static const struct iio_trigger_ops scd4x_trigger_ops = {
   584		.set_trigger_state = scd4x_set_trigger_state,
   585		.validate_device = iio_trigger_validate_own_device,
   586	};
   587	
   588	static int scd4x_setup_trigger(struct iio_dev *indio_dev)
   589	{
   590		struct device *dev = indio_dev->dev.parent;
   591		struct iio_trigger *trig;
   592		int ret;
   593	
   594		trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
   595					      iio_device_id(indio_dev));
   596		if (!trig) {
   597			dev_err(dev, "failed to allocate trigger\n");
   598			return -ENOMEM;
   599		}
   600	
   601		trig->ops = &scd4x_trigger_ops;
   602		iio_trigger_set_drvdata(trig, indio_dev);
   603	
   604		ret = devm_iio_trigger_register(dev, trig);
   605		if (ret)
   606			return ret;
   607	
   608		indio_dev->trig = iio_trigger_get(trig);
   609	
   610		return ret;
   611	}
   612	
 > 613	int scd4x_probe(struct i2c_client *client, const struct i2c_device_id *id)
   614	{
   615		static const unsigned long scd4x_scan_masks[] = { 0x07, 0x00 };
   616		struct device *dev = &client->dev;
   617		struct iio_dev *indio_dev;
   618		struct scd4x_state *state;
   619		int ret;
   620	
   621		indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
   622		if (!indio_dev)
   623			return -ENOMEM;
   624	
   625		state = iio_priv(indio_dev);
   626		mutex_init(&state->lock);
   627		state->dev = dev;
   628		crc8_populate_msb(scd4x_crc8_table, SCD4X_CRC8_POLYNOMIAL);
   629	
   630		indio_dev->info = &scd4x_info;
   631		indio_dev->name = client->name;
   632		indio_dev->channels = scd4x_channels;
   633		indio_dev->num_channels = ARRAY_SIZE(scd4x_channels);
   634		indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED;
   635		indio_dev->available_scan_masks = scd4x_scan_masks;
   636	
   637		state->vdd = devm_regulator_get(dev, "vdd");
   638		if (IS_ERR(state->vdd))
   639			return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n");
   640	
   641		ret = regulator_enable(state->vdd);
   642		if (ret)
   643			return ret;
   644	
   645		ret = devm_add_action_or_reset(dev, scd4x_disable_regulator, state);
   646		if (ret)
   647			return ret;
   648	
   649		ret = scd4x_send_command(state, CMD_STOP_MEAS);
   650		if (ret) {
   651			dev_err(dev, "failed to stop measurement: %d\n", ret);
   652			return ret;
   653		}
   654	
   655		/* execution time */
   656		msleep_interruptible(500);
   657	
   658		if (state->irq > 0) {
   659			ret = scd4x_setup_trigger(indio_dev);
   660			if (ret) {
   661				dev_err(dev, "failed to setup trigger: %d\n", ret);
   662				return ret;
   663			}
   664		}
   665	
   666		ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd4x_trigger_handler, NULL);
   667		if (ret)
   668			return ret;
   669	
   670		ret = devm_iio_device_register(dev, indio_dev);
   671		if (ret) {
   672			dev_err(dev, "failed to register iio device\n");
   673			return ret;
   674		}
   675	
   676		ret = scd4x_send_command(state, CMD_START_MEAS);
   677		if (ret) {
   678			dev_err(dev, "failed to start measurement: %d\n", ret);
   679			return ret;
   680		}
   681	
   682		ret = devm_add_action_or_reset(dev, scd4x_stop_meas, state);
   683		if (ret)
   684			return ret;
   685	
   686		return 0;
   687	}
   688	EXPORT_SYMBOL(scd4x_probe);
   689	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index a4920646e9be..ce9ec81d4993 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -118,6 +118,19 @@  config SCD30_SERIAL
 	  To compile this driver as a module, choose M here: the module will
 	  be called scd30_serial.
 
+config SCD4X
+	tristate "SCD4X carbon dioxide sensor driver"
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	depends on I2C
+	select CRC8
+	help
+	  Say Y here to build support for the Sensirion SCD4X sensor with cabon
+	  dioxide, relative humidity and temperature sensing capabilities
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called scd4x.
+
 config SENSIRION_SGP30
 	tristate "Sensirion SGPxx gas sensors"
 	depends on I2C
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
index 4898690cc155..3a766dd23020 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -15,6 +15,7 @@  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_SENSIRION_SGP30)	+= sgp30.o
 obj-$(CONFIG_SPS30) += sps30.o
 obj-$(CONFIG_SPS30_I2C) += sps30_i2c.o
diff --git a/drivers/iio/chemical/scd4x.c b/drivers/iio/chemical/scd4x.c
new file mode 100644
index 000000000000..3e3ff62922f6
--- /dev/null
+++ b/drivers/iio/chemical/scd4x.c
@@ -0,0 +1,707 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sensirion SCD4X carbon dioxide sensor i2c driver
+ *
+ * Copyright (C) 2021 Protonic Holland
+ * Author: Roan van Dijk <roan@protonic.nl>
+ *
+ * I2C slave address: 0x62
+ *
+ * Datasheets:
+ * https://www.sensirion.com/file/datasheet_scd4x
+ */
+
+#include <asm/unaligned.h>
+#include <linux/crc8.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/types.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#define SCD4X_CRC8_POLYNOMIAL 0x31
+#define SCD4X_TIMEOUT_ERR 1000
+#define SCD4X_READ_BUF_SIZE 9
+#define SCD4X_COMMAND_BUF_SIZE 2
+#define SCD4X_WRITE_BUF_SIZE 5
+#define SCD4X_FRC_MIN_PPM 400
+#define SCD4X_FRC_MAX_PPM 2000
+
+/*Commands SCD4X*/
+enum scd4x_cmd {
+	CMD_START_MEAS          = 0x21b1,
+	CMD_READ_MEAS           = 0xec05,
+	CMD_STOP_MEAS           = 0x3f86,
+	CMD_SET_TEMP_OFFSET     = 0x241d,
+	CMD_GET_TEMP_OFFSET     = 0x2318,
+	CMD_FRC                 = 0x362f,
+	CMD_SET_ASC             = 0x2416,
+	CMD_GET_ASC             = 0x2313,
+	CMD_GET_DATA_READY      = 0xe4b8,
+};
+
+enum scd4x_channel_idx {
+	SCD4X_CO2,
+	SCD4X_TEMP,
+	SCD4X_HR,
+};
+
+struct scd4x_state {
+	struct i2c_client *client;
+	struct mutex lock;
+	struct device *dev;
+	struct regulator *vdd;
+
+	int irq;
+	uint16_t meas[3];
+};
+
+DECLARE_CRC8_TABLE(scd4x_crc8_table);
+
+static int scd4x_i2c_xfer(struct scd4x_state *state, char *txbuf, int txsize,
+				char *rxbuf, int rxsize)
+{
+	struct i2c_client *client = to_i2c_client(state->dev);
+	int ret;
+
+	ret = i2c_master_send(client, txbuf, txsize);
+
+	if (ret < 0)
+		return ret;
+	if (ret != txsize)
+		return -EIO;
+
+	if (rxsize == 0)
+		return 0;
+
+	ret = i2c_master_recv(client, rxbuf, rxsize);
+	if (ret < 0)
+		return ret;
+	if (ret != rxsize)
+		return -EIO;
+
+	return 0;
+}
+
+static int scd4x_send_command(struct scd4x_state *state, enum scd4x_cmd cmd)
+{
+	char buf[SCD4X_COMMAND_BUF_SIZE];
+	int ret;
+
+	/*
+	 * Measurement needs to be stopped before sending commands.
+	 * Except stop and start command.
+	 */
+	if ((cmd != CMD_STOP_MEAS) & (cmd != CMD_START_MEAS)) {
+
+		ret = scd4x_send_command(state, CMD_STOP_MEAS);
+		if (ret)
+			return ret;
+
+		/* execution time for stopping measurement */
+		msleep_interruptible(500);
+	}
+
+	put_unaligned_be16(cmd, buf);
+	ret = scd4x_i2c_xfer(state, buf, 2, buf, 0);
+	if (ret)
+		return ret;
+
+	if ((cmd != CMD_STOP_MEAS) & (cmd != CMD_START_MEAS)) {
+		ret = scd4x_send_command(state, CMD_START_MEAS);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int scd4x_read(struct scd4x_state *state, enum scd4x_cmd cmd,
+			void *response, int byte_cnt)
+{
+	char buf[SCD4X_READ_BUF_SIZE];
+	char *rsp = response;
+	int i, ret;
+	char crc;
+
+	/*
+	 * Measurement needs to be stopped before sending commands.
+	 * Except for reading measurement and data ready command.
+	 */
+	if ((cmd != CMD_GET_DATA_READY) & (cmd != CMD_READ_MEAS)) {
+		ret = scd4x_send_command(state, CMD_STOP_MEAS);
+		if (ret)
+			return ret;
+
+		/* execution time for stopping measurement */
+		msleep_interruptible(500);
+	}
+
+	put_unaligned_be16(cmd, buf);
+	ret = scd4x_i2c_xfer(state, buf, 2, buf, byte_cnt);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < byte_cnt; i += 3) {
+		crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
+		if (crc != buf[i + 2]) {
+			dev_err(state->dev, "CRC error\n");
+			return -EIO;
+		}
+
+		*rsp++ = buf[i];
+		*rsp++ = buf[i + 1];
+	}
+
+	/* start measurement */
+	if ((cmd != CMD_GET_DATA_READY) & (cmd != CMD_READ_MEAS)) {
+		ret = scd4x_send_command(state, CMD_START_MEAS);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int scd4x_write(struct scd4x_state *state, enum scd4x_cmd cmd, uint16_t arg)
+{
+	char buf[SCD4X_WRITE_BUF_SIZE];
+	int ret;
+	char crc;
+
+	put_unaligned_be16(cmd, buf);
+	put_unaligned_be16(arg, buf + 2);
+
+	crc = crc8(scd4x_crc8_table, buf + 2, 2, CRC8_INIT_VALUE);
+	buf[4] = crc;
+
+	/* measurement needs to be stopped before sending commands */
+	ret = scd4x_send_command(state, CMD_STOP_MEAS);
+	if (ret)
+		return ret;
+
+	/* execution time */
+	msleep_interruptible(500);
+
+	ret = scd4x_i2c_xfer(state, buf, SCD4X_WRITE_BUF_SIZE, buf, 0);
+	if (ret)
+		return ret;
+
+	/* start measurement, except for forced calibration command */
+	if (cmd != CMD_FRC) {
+		ret = scd4x_send_command(state, CMD_START_MEAS);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int scd4x_write_and_fetch(struct scd4x_state *state, enum scd4x_cmd cmd,
+				uint16_t arg, void *response, int byte_cnt)
+{
+	struct i2c_client *client = to_i2c_client(state->dev);
+	char buf[SCD4X_READ_BUF_SIZE];
+	char *rsp = response;
+	int i, ret;
+	char crc;
+
+	ret = scd4x_write(state, CMD_FRC, arg);
+	if (ret) {
+		scd4x_send_command(state, CMD_START_MEAS);
+		return ret;
+	}
+
+	/* Execution time */
+	msleep_interruptible(400);
+
+	ret = i2c_master_recv(client, buf, byte_cnt);
+	if (ret < 0)
+		return ret;
+	if (ret != byte_cnt)
+		return -EIO;
+
+	for (i = 0; i < byte_cnt; i += 3) {
+		crc = crc8(scd4x_crc8_table, buf + i, 2, CRC8_INIT_VALUE);
+		if (crc != buf[i + 2]) {
+			dev_err(state->dev, "CRC error\n");
+			scd4x_send_command(state, CMD_START_MEAS);
+			return -EIO;
+		}
+
+		*rsp++ = buf[i];
+		*rsp++ = buf[i + 1];
+	}
+
+	ret = scd4x_send_command(state, CMD_START_MEAS);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int scd4x_read_meas(struct scd4x_state *state)
+{
+	int i, ret;
+
+	ret = scd4x_read(state, CMD_READ_MEAS, state->meas, 9);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(state->meas); i++)
+		state->meas[i] = be16_to_cpu(state->meas[i]);
+	/*
+	 * Temperature and humidity values are convertered and scaled to
+	 * milli deg C and milli percent.
+	 */
+	state->meas[SCD4X_TEMP] = ((175 * state->meas[SCD4X_TEMP]/65536) - 45)*1000;
+	state->meas[SCD4X_HR] = ((100 * state->meas[SCD4X_HR]/65536))*1000;
+
+	return 0;
+}
+
+static int scd4x_wait_meas_poll(struct scd4x_state *state)
+{
+	int tries = 6;
+	int ret;
+
+	do {
+		uint16_t val;
+
+		ret = scd4x_read(state, CMD_GET_DATA_READY, &val, 3);
+		if (ret)
+			return -EIO;
+		val = be16_to_cpu(val);
+
+		/* new measurement available */
+		if (val & 0x7FF)
+			break;
+
+		msleep_interruptible(1000);
+	} while (--tries);
+
+	if (tries == 0) {
+		/* try to start sensor on timeout */
+		ret = scd4x_send_command(state, CMD_START_MEAS);
+		if (ret)
+			dev_err(state->dev, "failed to start measurement: %d\n", ret);
+	}
+
+	return tries ? 0 : -ETIMEDOUT;
+}
+
+static int scd4x_read_poll(struct scd4x_state *state)
+{
+	int ret;
+
+	ret = scd4x_wait_meas_poll(state);
+	if (ret)
+		return ret;
+
+	return scd4x_read_meas(state);
+}
+
+static int scd4x_read_raw(struct iio_dev *indio_dev,
+			struct iio_chan_spec const *chan, int *val,
+			int *val2, long mask)
+{
+	struct scd4x_state *state = iio_priv(indio_dev);
+	int ret;
+	uint16_t tmp;
+
+	mutex_lock(&state->lock);
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+	case IIO_CHAN_INFO_PROCESSED:
+		ret = iio_device_claim_direct_mode(indio_dev);
+		if (ret)
+			break;
+
+		ret = scd4x_read_poll(state);
+		if (ret) {
+			iio_device_release_direct_mode(indio_dev);
+			break;
+		}
+		*val = state->meas[chan->address];
+		iio_device_release_direct_mode(indio_dev);
+		ret = IIO_VAL_INT;
+		break;
+	case IIO_CHAN_INFO_CALIBBIAS:
+		ret = scd4x_read(state, CMD_GET_TEMP_OFFSET, &tmp, 3);
+		if (ret)
+			break;
+		*val = 175000 * be16_to_cpu(tmp) / 65536;
+		ret = IIO_VAL_INT;
+		break;
+	}
+	mutex_unlock(&state->lock);
+
+	return ret;
+}
+
+static int scd4x_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan,
+				int val, int val2, long mask)
+{
+	struct scd4x_state *state = iio_priv(indio_dev);
+	int ret = 0;
+
+	mutex_lock(&state->lock);
+	switch (mask) {
+	case IIO_CHAN_INFO_CALIBBIAS:
+		val = val * 65536 / 175000;
+		ret = scd4x_write(state, CMD_SET_TEMP_OFFSET, val);
+		if (ret)
+			break;
+	}
+	mutex_unlock(&state->lock);
+
+	return ret;
+}
+
+static ssize_t calibration_auto_enable_show(struct device *dev,
+			struct device_attribute *attr, char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct scd4x_state *state = iio_priv(indio_dev);
+	int ret;
+	uint16_t val;
+
+	mutex_lock(&state->lock);
+	ret = scd4x_read(state, CMD_GET_ASC, &val, 3);
+	mutex_unlock(&state->lock);
+	if (ret)
+		dev_err(dev, "failed to read automatic calibration");
+
+	val = be16_to_cpu(val);
+
+	return ret ?: sprintf(buf, "%d\n", val);
+}
+
+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);
+	struct scd4x_state *state = iio_priv(indio_dev);
+	bool val;
+	int ret;
+	uint16_t value;
+
+	ret = kstrtobool(buf, &val);
+	if (ret)
+		return ret;
+
+	value = val;
+
+	mutex_lock(&state->lock);
+	ret = scd4x_write(state, CMD_SET_ASC, value);
+	mutex_unlock(&state->lock);
+	if (ret)
+		dev_err(dev, "failed to set automatic calibration");
+
+	return ret ?: len;
+}
+
+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);
+	struct scd4x_state *state = iio_priv(indio_dev);
+	uint16_t val, arg;
+	int ret;
+
+	ret = kstrtou16(buf, 0, &arg);
+	if (ret)
+		return ret;
+
+	if (arg < SCD4X_FRC_MIN_PPM || arg > SCD4X_FRC_MAX_PPM)
+		return -EINVAL;
+
+	mutex_lock(&state->lock);
+	ret = scd4x_write_and_fetch(state, CMD_FRC, arg, &val, 3);
+	mutex_unlock(&state->lock);
+
+	if (val == 0xff) {
+		dev_err(state->dev, "forced calibration has failed");
+		return -EINVAL;
+	}
+
+	return ret ?: len;
+}
+
+static IIO_DEVICE_ATTR_RW(calibration_auto_enable, 0);
+static IIO_DEVICE_ATTR_WO(calibration_forced_value, 0);
+
+static struct attribute *scd4x_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 scd4x_attr_group = {
+	.attrs = scd4x_attrs,
+};
+static const struct iio_info scd4x_info = {
+	.attrs = &scd4x_attr_group,
+	.read_raw = scd4x_read_raw,
+	.write_raw = scd4x_write_raw,
+};
+
+static const struct iio_chan_spec scd4x_channels[] = {
+	{
+		.type = IIO_CONCENTRATION,
+		.channel2 = IIO_MOD_CO2,
+		.modified = 1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.address = SCD4X_CO2,
+		.scan_index = SCD4X_CO2,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_BE,
+		},
+	},
+	{
+		.type = IIO_TEMP,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED)|
+				      BIT(IIO_CHAN_INFO_CALIBBIAS),
+		.address = SCD4X_TEMP,
+		.scan_index = SCD4X_TEMP,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_BE,
+		},
+	},
+	{
+		.type = IIO_HUMIDITYRELATIVE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),
+		.address = SCD4X_HR,
+		.scan_index = SCD4X_HR,
+		.scan_type = {
+			.sign = 'u',
+			.realbits = 16,
+			.storagebits = 16,
+			.endianness = IIO_BE,
+		},
+	},
+};
+
+int __maybe_unused scd4x_suspend(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct scd4x_state *state  = iio_priv(indio_dev);
+	int ret;
+
+	ret = scd4x_send_command(state, CMD_STOP_MEAS);
+	if (ret)
+		return ret;
+
+	return regulator_disable(state->vdd);
+}
+EXPORT_SYMBOL(scd4x_suspend);
+
+int __maybe_unused scd4x_resume(struct device *dev)
+{
+	struct iio_dev *indio_dev = dev_get_drvdata(dev);
+	struct scd4x_state *state = iio_priv(indio_dev);
+	int ret;
+
+	ret = regulator_enable(state->vdd);
+	if (ret)
+		return ret;
+
+	return scd4x_send_command(state, CMD_START_MEAS);
+}
+EXPORT_SYMBOL(scd4x_resume);
+
+static void scd4x_stop_meas(void *data)
+{
+	struct scd4x_state *state = data;
+
+	scd4x_send_command(state, CMD_STOP_MEAS);
+}
+
+static void scd4x_disable_regulator(void *data)
+{
+	struct scd4x_state *state = data;
+
+	regulator_disable(state->vdd);
+}
+
+static irqreturn_t scd4x_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct scd4x_state *state = iio_priv(indio_dev);
+	struct {
+		int data[3];
+		int64_t ts __aligned(8);
+	} scan;
+	int ret;
+
+	mutex_lock(&state->lock);
+	ret = scd4x_read_poll(state);
+	memset(&scan, 0, sizeof(scan));
+	memcpy(scan.data, state->meas, sizeof(state->meas));
+	mutex_unlock(&state->lock);
+	if (ret)
+		goto out;
+
+	iio_push_to_buffers_with_timestamp(indio_dev, &scan, iio_get_time_ns(indio_dev));
+out:
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
+static int scd4x_set_trigger_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct scd4x_state *st = iio_priv(indio_dev);
+
+	if (state)
+		enable_irq(st->irq);
+	else
+		disable_irq(st->irq);
+
+	return 0;
+}
+
+static const struct iio_trigger_ops scd4x_trigger_ops = {
+	.set_trigger_state = scd4x_set_trigger_state,
+	.validate_device = iio_trigger_validate_own_device,
+};
+
+static int scd4x_setup_trigger(struct iio_dev *indio_dev)
+{
+	struct device *dev = indio_dev->dev.parent;
+	struct iio_trigger *trig;
+	int ret;
+
+	trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
+				      iio_device_id(indio_dev));
+	if (!trig) {
+		dev_err(dev, "failed to allocate trigger\n");
+		return -ENOMEM;
+	}
+
+	trig->ops = &scd4x_trigger_ops;
+	iio_trigger_set_drvdata(trig, indio_dev);
+
+	ret = devm_iio_trigger_register(dev, trig);
+	if (ret)
+		return ret;
+
+	indio_dev->trig = iio_trigger_get(trig);
+
+	return ret;
+}
+
+int scd4x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	static const unsigned long scd4x_scan_masks[] = { 0x07, 0x00 };
+	struct device *dev = &client->dev;
+	struct iio_dev *indio_dev;
+	struct scd4x_state *state;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	state = iio_priv(indio_dev);
+	mutex_init(&state->lock);
+	state->dev = dev;
+	crc8_populate_msb(scd4x_crc8_table, SCD4X_CRC8_POLYNOMIAL);
+
+	indio_dev->info = &scd4x_info;
+	indio_dev->name = client->name;
+	indio_dev->channels = scd4x_channels;
+	indio_dev->num_channels = ARRAY_SIZE(scd4x_channels);
+	indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_TRIGGERED;
+	indio_dev->available_scan_masks = scd4x_scan_masks;
+
+	state->vdd = devm_regulator_get(dev, "vdd");
+	if (IS_ERR(state->vdd))
+		return dev_err_probe(dev, PTR_ERR(state->vdd), "failed to get regulator\n");
+
+	ret = regulator_enable(state->vdd);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(dev, scd4x_disable_regulator, state);
+	if (ret)
+		return ret;
+
+	ret = scd4x_send_command(state, CMD_STOP_MEAS);
+	if (ret) {
+		dev_err(dev, "failed to stop measurement: %d\n", ret);
+		return ret;
+	}
+
+	/* execution time */
+	msleep_interruptible(500);
+
+	if (state->irq > 0) {
+		ret = scd4x_setup_trigger(indio_dev);
+		if (ret) {
+			dev_err(dev, "failed to setup trigger: %d\n", ret);
+			return ret;
+		}
+	}
+
+	ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, scd4x_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;
+	}
+
+	ret = scd4x_send_command(state, CMD_START_MEAS);
+	if (ret) {
+		dev_err(dev, "failed to start measurement: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(dev, scd4x_stop_meas, state);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL(scd4x_probe);
+
+static const struct of_device_id scd4x_dt_ids[] = {
+	{ .compatible = "sensirion,scd4x" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, scd4x_dt_ids);
+
+static struct i2c_driver scd4x_i2c_driver = {
+	.driver = {
+		.name = KBUILD_MODNAME,
+		.of_match_table = scd4x_dt_ids,
+	},
+	.probe = scd4x_probe,
+};
+module_i2c_driver(scd4x_i2c_driver);
+
+MODULE_AUTHOR("Roan van Dijk <roan@protonic.nl>");
+MODULE_DESCRIPTION("Sensirion SCD4X carbon dioxide sensor core driver");
+MODULE_LICENSE("GPL v2");