123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Sensirion SCD30 carbon dioxide sensor serial driver
- *
- * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
- */
- #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 <tomasz.duszynski@octakon.com>");
- MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
- MODULE_LICENSE("GPL v2");
|