2020-06-03 08:50:04

by Tomasz Duszynski

[permalink] [raw]
Subject: [PATCH v4 0/4] Add support for SCD30 sensor

Following series adds support for Sensirion SCD30 sensor module capable of
measuring carbon dioxide, temperature and relative humidity. CO2 measurements
base on NDIR principle while temperature and relative humidity are measured by
the on board SHT31. As for sensor communication, both I2C and serial interfaces
are supported.

v4:
* improve formatting
* improve error handling readability
* fix message validity check on serial write

v3:
* simplify code by scaling temperature & humidity in _read_meas()
* update realbits in scan types
* s/adjecent/adjacent
* drop IIO_CHAN_INFO_RAW from _write_raw_get_fmt because there's no raw
output channel
* rework locking in _read_raw
* fix endianess problem on BE machine
* align timestamp properly before pushing to buffers
* explain why interrupt gets disabled after registration
* add trigger validation
* drop SCALE for temperature and humidity channel as they are processed
* register action which stops measuring after starting measurements
* spit generic calibration attr into two doing specific things
* add comment explaining why priv in struct scd30_state is for
* rename node in binding example to co2-sensor

v2:
* move asm/byteorder.h towards the bottom of include list
* make channel address names in enum more specific
* add postfixes to defines and extra comments
* drop unneeded i2c include from scd30 header
* break generic command sending function into specialized options
* expose automatic calibration and forced calibration via the same attr
* use SAMP_FREQ to set frequency instead of meas_interval attr
* use CALISCALE to set pressure compensation instead of pressure_comp attr
* use CALIBBIAS to set temperature offset instead of temp_offset attr
* fix order in MAINTAINERS
* drop attribute allowing one to reset sensor
* as we have dt probing drop board file based probing (i2c_device_id)
* merge patches touching related files
* use fwnode API to retrieve interrupt from dt
* fix interrupt-parent spelling
* change binding license
* drop supply from required property

Tomasz Duszynski (4):
iio: chemical: scd30: add core driver
iio: chemical: scd30: add I2C interface driver
iio: chemical: scd30: add serial interface driver
dt-bindings: iio: scd30: add device binding file

Documentation/ABI/testing/sysfs-bus-iio-scd30 | 34 +
.../iio/chemical/sensirion,scd30.yaml | 68 ++
MAINTAINERS | 9 +
drivers/iio/chemical/Kconfig | 33 +
drivers/iio/chemical/Makefile | 3 +
drivers/iio/chemical/scd30.h | 78 ++
drivers/iio/chemical/scd30_core.c | 761 ++++++++++++++++++
drivers/iio/chemical/scd30_i2c.c | 139 ++++
drivers/iio/chemical/scd30_serial.c | 263 ++++++
9 files changed, 1388 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-iio-scd30
create mode 100644 Documentation/devicetree/bindings/iio/chemical/sensirion,scd30.yaml
create mode 100644 drivers/iio/chemical/scd30.h
create mode 100644 drivers/iio/chemical/scd30_core.c
create mode 100644 drivers/iio/chemical/scd30_i2c.c
create mode 100644 drivers/iio/chemical/scd30_serial.c

--
2.27.0


2020-06-03 08:51:54

by Tomasz Duszynski

[permalink] [raw]
Subject: [PATCH v4 3/4] iio: chemical: scd30: add serial interface driver

Add serial interface driver for the SCD30 sensor.

Signed-off-by: Tomasz Duszynski <[email protected]>
---
MAINTAINERS | 1 +
drivers/iio/chemical/Kconfig | 11 ++
drivers/iio/chemical/Makefile | 1 +
drivers/iio/chemical/scd30_serial.c | 263 ++++++++++++++++++++++++++++
4 files changed, 276 insertions(+)
create mode 100644 drivers/iio/chemical/scd30_serial.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 13aed3473b7e..5db4b446c8ba 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15143,6 +15143,7 @@ S: Maintained
F: drivers/iio/chemical/scd30.h
F: drivers/iio/chemical/scd30_core.c
F: drivers/iio/chemical/scd30_i2c.c
+F: drivers/iio/chemical/scd30_serial.c

SENSIRION SPS30 AIR POLLUTION SENSOR DRIVER
M: Tomasz Duszynski <[email protected]>
diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
index 970d34888c2e..10bb431bc3ce 100644
--- a/drivers/iio/chemical/Kconfig
+++ b/drivers/iio/chemical/Kconfig
@@ -107,6 +107,17 @@ config SCD30_I2C
To compile this driver as a module, choose M here: the module will
be called scd30_i2c.

+config SCD30_SERIAL
+ tristate "SCD30 carbon dioxide sensor serial driver"
+ depends on SCD30_CORE && SERIAL_DEV_BUS
+ select CRC16
+ help
+ Say Y here to build support for the Sensirion SCD30 serial interface
+ driver.
+
+ To compile this driver as a module, choose M here: the module will
+ be called scd30_serial.
+
config SENSIRION_SGP30
tristate "Sensirion SGPxx gas sensors"
depends on I2C
diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
index 0966ca34e34b..fef63dd5bf92 100644
--- a/drivers/iio/chemical/Makefile
+++ b/drivers/iio/chemical/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_IAQCORE) += ams-iaq-core.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_SENSIRION_SGP30) += sgp30.o
obj-$(CONFIG_SPS30) += sps30.o
obj-$(CONFIG_VZ89X) += vz89x.o
diff --git a/drivers/iio/chemical/scd30_serial.c b/drivers/iio/chemical/scd30_serial.c
new file mode 100644
index 000000000000..06f85eb1a4dd
--- /dev/null
+++ b/drivers/iio/chemical/scd30_serial.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Sensirion SCD30 carbon dioxide sensor serial driver
+ *
+ * Copyright (c) 2020 Tomasz Duszynski <[email protected]>
+ */
+#include <linux/crc16.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/iio/iio.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/serdev.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <asm/unaligned.h>
+
+#include "scd30.h"
+
+#define SCD30_SERDEV_ADDR 0x61
+#define SCD30_SERDEV_WRITE 0x06
+#define SCD30_SERDEV_READ 0x03
+#define SCD30_SERDEV_MAX_BUF_SIZE 17
+#define SCD30_SERDEV_RX_HEADER_SIZE 3
+#define SCD30_SERDEV_CRC_SIZE 2
+#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)
+
+struct scd30_serdev_priv {
+ struct completion meas_ready;
+ char *buf;
+ int num_expected;
+ int num;
+};
+
+static u16 scd30_serdev_cmd_lookup_tbl[] = {
+ [CMD_START_MEAS] = 0x0036,
+ [CMD_STOP_MEAS] = 0x0037,
+ [CMD_MEAS_INTERVAL] = 0x0025,
+ [CMD_MEAS_READY] = 0x0027,
+ [CMD_READ_MEAS] = 0x0028,
+ [CMD_ASC] = 0x003a,
+ [CMD_FRC] = 0x0039,
+ [CMD_TEMP_OFFSET] = 0x003b,
+ [CMD_FW_VERSION] = 0x0020,
+ [CMD_RESET] = 0x0034,
+};
+
+static u16 scd30_serdev_calc_crc(const char *buf, int size)
+{
+ return crc16(0xffff, buf, size);
+}
+
+static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,
+ char *rxbuf, int rxsize)
+{
+ struct serdev_device *serdev = to_serdev_device(state->dev);
+ struct scd30_serdev_priv *priv = state->priv;
+ int ret;
+
+ priv->buf = rxbuf;
+ priv->num_expected = rxsize;
+ priv->num = 0;
+
+ ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);
+ if (ret < 0)
+ return ret;
+ if (ret != txsize)
+ return -EIO;
+
+ ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT);
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
+ void *response, int size)
+{
+ /*
+ * Communication over serial line is based on modbus protocol (or rather
+ * its variation called modbus over serial to be precise). Upon
+ * receiving a request device should reply with response.
+ *
+ * Frame below represents a request message. Each field takes
+ * exactly one byte.
+ *
+ * +------+------+-----+-----+-------+-------+-----+-----+
+ * | dev | op | reg | reg | byte1 | byte0 | crc | crc |
+ * | addr | code | msb | lsb | | | lsb | msb |
+ * +------+------+-----+-----+-------+-------+-----+-----+
+ *
+ * The message device replies with depends on the 'op code' field from
+ * the request. In case it was set to SCD30_SERDEV_WRITE sensor should
+ * reply with unchanged request. Otherwise 'op code' was set to
+ * SCD30_SERDEV_READ and response looks like the one below. As with
+ * request, each field takes one byte.
+ *
+ * +------+------+--------+-------+-----+-------+-----+-----+
+ * | dev | op | num of | byte0 | ... | byteN | crc | crc |
+ * | addr | code | bytes | | | | lsb | msb |
+ * +------+------+--------+-------+-----+-------+-----+-----+
+ */
+ char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },
+ rxbuf[SCD30_SERDEV_MAX_BUF_SIZE];
+ int ret, rxsize, txsize = 2;
+ char *rsp = response;
+ u16 crc;
+
+ put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);
+ txsize += 2;
+
+ if (rsp) {
+ txbuf[1] = SCD30_SERDEV_READ;
+ if (cmd == CMD_READ_MEAS)
+ /* number of u16 words to read */
+ put_unaligned_be16(size / 2, txbuf + txsize);
+ else
+ put_unaligned_be16(0x0001, txbuf + txsize);
+ txsize += 2;
+ crc = scd30_serdev_calc_crc(txbuf, txsize);
+ put_unaligned_le16(crc, txbuf + txsize);
+ txsize += 2;
+ rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE;
+ } else {
+ if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
+ arg = 0x0001;
+
+ txbuf[1] = SCD30_SERDEV_WRITE;
+ put_unaligned_be16(arg, txbuf + txsize);
+ txsize += 2;
+ crc = scd30_serdev_calc_crc(txbuf, txsize);
+ put_unaligned_le16(crc, txbuf + txsize);
+ txsize += 2;
+ rxsize = txsize;
+ }
+
+ ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);
+ if (ret)
+ return ret;
+
+ switch (txbuf[1]) {
+ case SCD30_SERDEV_WRITE:
+ if (memcmp(txbuf, rxbuf, txsize)) {
+ dev_err(state->dev, "wrong message received\n");
+ return -EIO;
+ }
+ break;
+ case SCD30_SERDEV_READ:
+ if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) {
+ dev_err(state->dev, "received data size does not match header\n");
+ return -EIO;
+ }
+
+ rxsize -= SCD30_SERDEV_CRC_SIZE;
+ crc = get_unaligned_le16(rxbuf + rxsize);
+ if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {
+ dev_err(state->dev, "data integrity check failed\n");
+ return -EIO;
+ }
+
+ rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;
+ memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);
+ break;
+ default:
+ dev_err(state->dev, "received unknown op code\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int scd30_serdev_receive_buf(struct serdev_device *serdev,
+ const unsigned char *buf, size_t size)
+{
+ struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
+ struct scd30_serdev_priv *priv;
+ struct scd30_state *state;
+ int num;
+
+ if (!indio_dev)
+ return 0;
+
+ state = iio_priv(indio_dev);
+ priv = state->priv;
+
+ /* just in case sensor puts some unexpected bytes on the bus */
+ if (!priv->buf)
+ return 0;
+
+ if (priv->num + size >= priv->num_expected)
+ num = priv->num_expected - priv->num;
+ else
+ num = size;
+
+ memcpy(priv->buf + priv->num, buf, num);
+ priv->num += num;
+
+ if (priv->num == priv->num_expected) {
+ priv->buf = NULL;
+ complete(&priv->meas_ready);
+ }
+
+ return num;
+}
+
+static const struct serdev_device_ops scd30_serdev_ops = {
+ .receive_buf = scd30_serdev_receive_buf,
+ .write_wakeup = serdev_device_write_wakeup,
+};
+
+static int scd30_serdev_probe(struct serdev_device *serdev)
+{
+ struct device *dev = &serdev->dev;
+ struct scd30_serdev_priv *priv;
+ int irq, ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ init_completion(&priv->meas_ready);
+ serdev_device_set_client_ops(serdev, &scd30_serdev_ops);
+
+ ret = devm_serdev_device_open(dev, serdev);
+ if (ret)
+ return ret;
+
+ serdev_device_set_baudrate(serdev, 19200);
+ serdev_device_set_flow_control(serdev, false);
+
+ ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+ if (ret)
+ return ret;
+
+ irq = fwnode_irq_get(dev_fwnode(dev), 0);
+
+ return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command);
+}
+
+static const struct of_device_id scd30_serdev_of_match[] = {
+ { .compatible = "sensirion,scd30" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);
+
+static struct serdev_device_driver scd30_serdev_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = scd30_serdev_of_match,
+ .pm = &scd30_pm_ops,
+ },
+ .probe = scd30_serdev_probe,
+};
+module_serdev_device_driver(scd30_serdev_driver);
+
+MODULE_AUTHOR("Tomasz Duszynski <[email protected]>");
+MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
+MODULE_LICENSE("GPL v2");
--
2.27.0

2020-06-03 11:34:17

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v4 3/4] iio: chemical: scd30: add serial interface driver

On Wed, Jun 3, 2020 at 11:47 AM Tomasz Duszynski
<[email protected]> wrote:
>
> Add serial interface driver for the SCD30 sensor.

FWIW,
Reviewed-by: Andy Shevchenko <[email protected]>

> Signed-off-by: Tomasz Duszynski <[email protected]>
> ---
> MAINTAINERS | 1 +
> drivers/iio/chemical/Kconfig | 11 ++
> drivers/iio/chemical/Makefile | 1 +
> drivers/iio/chemical/scd30_serial.c | 263 ++++++++++++++++++++++++++++
> 4 files changed, 276 insertions(+)
> create mode 100644 drivers/iio/chemical/scd30_serial.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 13aed3473b7e..5db4b446c8ba 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15143,6 +15143,7 @@ S: Maintained
> F: drivers/iio/chemical/scd30.h
> F: drivers/iio/chemical/scd30_core.c
> F: drivers/iio/chemical/scd30_i2c.c
> +F: drivers/iio/chemical/scd30_serial.c
>
> SENSIRION SPS30 AIR POLLUTION SENSOR DRIVER
> M: Tomasz Duszynski <[email protected]>
> diff --git a/drivers/iio/chemical/Kconfig b/drivers/iio/chemical/Kconfig
> index 970d34888c2e..10bb431bc3ce 100644
> --- a/drivers/iio/chemical/Kconfig
> +++ b/drivers/iio/chemical/Kconfig
> @@ -107,6 +107,17 @@ config SCD30_I2C
> To compile this driver as a module, choose M here: the module will
> be called scd30_i2c.
>
> +config SCD30_SERIAL
> + tristate "SCD30 carbon dioxide sensor serial driver"
> + depends on SCD30_CORE && SERIAL_DEV_BUS
> + select CRC16
> + help
> + Say Y here to build support for the Sensirion SCD30 serial interface
> + driver.
> +
> + To compile this driver as a module, choose M here: the module will
> + be called scd30_serial.
> +
> config SENSIRION_SGP30
> tristate "Sensirion SGPxx gas sensors"
> depends on I2C
> diff --git a/drivers/iio/chemical/Makefile b/drivers/iio/chemical/Makefile
> index 0966ca34e34b..fef63dd5bf92 100644
> --- a/drivers/iio/chemical/Makefile
> +++ b/drivers/iio/chemical/Makefile
> @@ -14,6 +14,7 @@ obj-$(CONFIG_IAQCORE) += ams-iaq-core.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_SENSIRION_SGP30) += sgp30.o
> obj-$(CONFIG_SPS30) += sps30.o
> obj-$(CONFIG_VZ89X) += vz89x.o
> diff --git a/drivers/iio/chemical/scd30_serial.c b/drivers/iio/chemical/scd30_serial.c
> new file mode 100644
> index 000000000000..06f85eb1a4dd
> --- /dev/null
> +++ b/drivers/iio/chemical/scd30_serial.c
> @@ -0,0 +1,263 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Sensirion SCD30 carbon dioxide sensor serial driver
> + *
> + * Copyright (c) 2020 Tomasz Duszynski <[email protected]>
> + */
> +#include <linux/crc16.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/iio/iio.h>
> +#include <linux/jiffies.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/property.h>
> +#include <linux/serdev.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +#include <asm/unaligned.h>
> +
> +#include "scd30.h"
> +
> +#define SCD30_SERDEV_ADDR 0x61
> +#define SCD30_SERDEV_WRITE 0x06
> +#define SCD30_SERDEV_READ 0x03
> +#define SCD30_SERDEV_MAX_BUF_SIZE 17
> +#define SCD30_SERDEV_RX_HEADER_SIZE 3
> +#define SCD30_SERDEV_CRC_SIZE 2
> +#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)
> +
> +struct scd30_serdev_priv {
> + struct completion meas_ready;
> + char *buf;
> + int num_expected;
> + int num;
> +};
> +
> +static u16 scd30_serdev_cmd_lookup_tbl[] = {
> + [CMD_START_MEAS] = 0x0036,
> + [CMD_STOP_MEAS] = 0x0037,
> + [CMD_MEAS_INTERVAL] = 0x0025,
> + [CMD_MEAS_READY] = 0x0027,
> + [CMD_READ_MEAS] = 0x0028,
> + [CMD_ASC] = 0x003a,
> + [CMD_FRC] = 0x0039,
> + [CMD_TEMP_OFFSET] = 0x003b,
> + [CMD_FW_VERSION] = 0x0020,
> + [CMD_RESET] = 0x0034,
> +};
> +
> +static u16 scd30_serdev_calc_crc(const char *buf, int size)
> +{
> + return crc16(0xffff, buf, size);
> +}
> +
> +static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,
> + char *rxbuf, int rxsize)
> +{
> + struct serdev_device *serdev = to_serdev_device(state->dev);
> + struct scd30_serdev_priv *priv = state->priv;
> + int ret;
> +
> + priv->buf = rxbuf;
> + priv->num_expected = rxsize;
> + priv->num = 0;
> +
> + ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);
> + if (ret < 0)
> + return ret;
> + if (ret != txsize)
> + return -EIO;
> +
> + ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT);
> + if (ret < 0)
> + return ret;
> + if (!ret)
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
> +
> +static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
> + void *response, int size)
> +{
> + /*
> + * Communication over serial line is based on modbus protocol (or rather
> + * its variation called modbus over serial to be precise). Upon
> + * receiving a request device should reply with response.
> + *
> + * Frame below represents a request message. Each field takes
> + * exactly one byte.
> + *
> + * +------+------+-----+-----+-------+-------+-----+-----+
> + * | dev | op | reg | reg | byte1 | byte0 | crc | crc |
> + * | addr | code | msb | lsb | | | lsb | msb |
> + * +------+------+-----+-----+-------+-------+-----+-----+
> + *
> + * The message device replies with depends on the 'op code' field from
> + * the request. In case it was set to SCD30_SERDEV_WRITE sensor should
> + * reply with unchanged request. Otherwise 'op code' was set to
> + * SCD30_SERDEV_READ and response looks like the one below. As with
> + * request, each field takes one byte.
> + *
> + * +------+------+--------+-------+-----+-------+-----+-----+
> + * | dev | op | num of | byte0 | ... | byteN | crc | crc |
> + * | addr | code | bytes | | | | lsb | msb |
> + * +------+------+--------+-------+-----+-------+-----+-----+
> + */
> + char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },
> + rxbuf[SCD30_SERDEV_MAX_BUF_SIZE];
> + int ret, rxsize, txsize = 2;
> + char *rsp = response;
> + u16 crc;
> +
> + put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);
> + txsize += 2;
> +
> + if (rsp) {
> + txbuf[1] = SCD30_SERDEV_READ;
> + if (cmd == CMD_READ_MEAS)
> + /* number of u16 words to read */
> + put_unaligned_be16(size / 2, txbuf + txsize);
> + else
> + put_unaligned_be16(0x0001, txbuf + txsize);
> + txsize += 2;
> + crc = scd30_serdev_calc_crc(txbuf, txsize);
> + put_unaligned_le16(crc, txbuf + txsize);
> + txsize += 2;
> + rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE;
> + } else {
> + if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
> + arg = 0x0001;
> +
> + txbuf[1] = SCD30_SERDEV_WRITE;
> + put_unaligned_be16(arg, txbuf + txsize);
> + txsize += 2;
> + crc = scd30_serdev_calc_crc(txbuf, txsize);
> + put_unaligned_le16(crc, txbuf + txsize);
> + txsize += 2;
> + rxsize = txsize;
> + }
> +
> + ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);
> + if (ret)
> + return ret;
> +
> + switch (txbuf[1]) {
> + case SCD30_SERDEV_WRITE:
> + if (memcmp(txbuf, rxbuf, txsize)) {
> + dev_err(state->dev, "wrong message received\n");
> + return -EIO;
> + }
> + break;
> + case SCD30_SERDEV_READ:
> + if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) {
> + dev_err(state->dev, "received data size does not match header\n");
> + return -EIO;
> + }
> +
> + rxsize -= SCD30_SERDEV_CRC_SIZE;
> + crc = get_unaligned_le16(rxbuf + rxsize);
> + if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {
> + dev_err(state->dev, "data integrity check failed\n");
> + return -EIO;
> + }
> +
> + rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;
> + memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);
> + break;
> + default:
> + dev_err(state->dev, "received unknown op code\n");
> + return -EIO;
> + }
> +
> + return 0;
> +}
> +
> +static int scd30_serdev_receive_buf(struct serdev_device *serdev,
> + const unsigned char *buf, size_t size)
> +{
> + struct iio_dev *indio_dev = dev_get_drvdata(&serdev->dev);
> + struct scd30_serdev_priv *priv;
> + struct scd30_state *state;
> + int num;
> +
> + if (!indio_dev)
> + return 0;
> +
> + state = iio_priv(indio_dev);
> + priv = state->priv;
> +
> + /* just in case sensor puts some unexpected bytes on the bus */
> + if (!priv->buf)
> + return 0;
> +
> + if (priv->num + size >= priv->num_expected)
> + num = priv->num_expected - priv->num;
> + else
> + num = size;
> +
> + memcpy(priv->buf + priv->num, buf, num);
> + priv->num += num;
> +
> + if (priv->num == priv->num_expected) {
> + priv->buf = NULL;
> + complete(&priv->meas_ready);
> + }
> +
> + return num;
> +}
> +
> +static const struct serdev_device_ops scd30_serdev_ops = {
> + .receive_buf = scd30_serdev_receive_buf,
> + .write_wakeup = serdev_device_write_wakeup,
> +};
> +
> +static int scd30_serdev_probe(struct serdev_device *serdev)
> +{
> + struct device *dev = &serdev->dev;
> + struct scd30_serdev_priv *priv;
> + int irq, ret;
> +
> + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + init_completion(&priv->meas_ready);
> + serdev_device_set_client_ops(serdev, &scd30_serdev_ops);
> +
> + ret = devm_serdev_device_open(dev, serdev);
> + if (ret)
> + return ret;
> +
> + serdev_device_set_baudrate(serdev, 19200);
> + serdev_device_set_flow_control(serdev, false);
> +
> + ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
> + if (ret)
> + return ret;
> +
> + irq = fwnode_irq_get(dev_fwnode(dev), 0);
> +
> + return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command);
> +}
> +
> +static const struct of_device_id scd30_serdev_of_match[] = {
> + { .compatible = "sensirion,scd30" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);
> +
> +static struct serdev_device_driver scd30_serdev_driver = {
> + .driver = {
> + .name = KBUILD_MODNAME,
> + .of_match_table = scd30_serdev_of_match,
> + .pm = &scd30_pm_ops,
> + },
> + .probe = scd30_serdev_probe,
> +};
> +module_serdev_device_driver(scd30_serdev_driver);
> +
> +MODULE_AUTHOR("Tomasz Duszynski <[email protected]>");
> +MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
> +MODULE_LICENSE("GPL v2");
> --
> 2.27.0
>


--
With Best Regards,
Andy Shevchenko