Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Sensirion SCD30 carbon dioxide sensor serial driver
0004  *
0005  * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
0006  */
0007 #include <linux/crc16.h>
0008 #include <linux/device.h>
0009 #include <linux/errno.h>
0010 #include <linux/iio/iio.h>
0011 #include <linux/jiffies.h>
0012 #include <linux/mod_devicetable.h>
0013 #include <linux/module.h>
0014 #include <linux/property.h>
0015 #include <linux/serdev.h>
0016 #include <linux/string.h>
0017 #include <linux/types.h>
0018 #include <asm/unaligned.h>
0019 
0020 #include "scd30.h"
0021 
0022 #define SCD30_SERDEV_ADDR 0x61
0023 #define SCD30_SERDEV_WRITE 0x06
0024 #define SCD30_SERDEV_READ 0x03
0025 #define SCD30_SERDEV_MAX_BUF_SIZE 17
0026 #define SCD30_SERDEV_RX_HEADER_SIZE 3
0027 #define SCD30_SERDEV_CRC_SIZE 2
0028 #define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)
0029 
0030 struct scd30_serdev_priv {
0031     struct completion meas_ready;
0032     char *buf;
0033     int num_expected;
0034     int num;
0035 };
0036 
0037 static u16 scd30_serdev_cmd_lookup_tbl[] = {
0038     [CMD_START_MEAS] = 0x0036,
0039     [CMD_STOP_MEAS] = 0x0037,
0040     [CMD_MEAS_INTERVAL] = 0x0025,
0041     [CMD_MEAS_READY] = 0x0027,
0042     [CMD_READ_MEAS] = 0x0028,
0043     [CMD_ASC] = 0x003a,
0044     [CMD_FRC] = 0x0039,
0045     [CMD_TEMP_OFFSET] = 0x003b,
0046     [CMD_FW_VERSION] = 0x0020,
0047     [CMD_RESET] = 0x0034,
0048 };
0049 
0050 static u16 scd30_serdev_calc_crc(const char *buf, int size)
0051 {
0052     return crc16(0xffff, buf, size);
0053 }
0054 
0055 static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,
0056                  char *rxbuf, int rxsize)
0057 {
0058     struct serdev_device *serdev = to_serdev_device(state->dev);
0059     struct scd30_serdev_priv *priv = state->priv;
0060     int ret;
0061 
0062     priv->buf = rxbuf;
0063     priv->num_expected = rxsize;
0064     priv->num = 0;
0065 
0066     ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);
0067     if (ret < 0)
0068         return ret;
0069     if (ret != txsize)
0070         return -EIO;
0071 
0072     ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT);
0073     if (ret < 0)
0074         return ret;
0075     if (!ret)
0076         return -ETIMEDOUT;
0077 
0078     return 0;
0079 }
0080 
0081 static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
0082                 void *response, int size)
0083 {
0084     /*
0085      * Communication over serial line is based on modbus protocol (or rather
0086      * its variation called modbus over serial to be precise). Upon
0087      * receiving a request device should reply with response.
0088      *
0089      * Frame below represents a request message. Each field takes
0090      * exactly one byte.
0091      *
0092      * +------+------+-----+-----+-------+-------+-----+-----+
0093      * | dev  | op   | reg | reg | byte1 | byte0 | crc | crc |
0094      * | addr | code | msb | lsb |       |       | lsb | msb |
0095      * +------+------+-----+-----+-------+-------+-----+-----+
0096      *
0097      * The message device replies with depends on the 'op code' field from
0098      * the request. In case it was set to SCD30_SERDEV_WRITE sensor should
0099      * reply with unchanged request. Otherwise 'op code' was set to
0100      * SCD30_SERDEV_READ and response looks like the one below. As with
0101      * request, each field takes one byte.
0102      *
0103      * +------+------+--------+-------+-----+-------+-----+-----+
0104      * | dev  | op   | num of | byte0 | ... | byteN | crc | crc |
0105      * | addr | code | bytes  |       |     |       | lsb | msb |
0106      * +------+------+--------+-------+-----+-------+-----+-----+
0107      */
0108     char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },
0109          rxbuf[SCD30_SERDEV_MAX_BUF_SIZE];
0110     int ret, rxsize, txsize = 2;
0111     char *rsp = response;
0112     u16 crc;
0113 
0114     put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);
0115     txsize += 2;
0116 
0117     if (rsp) {
0118         txbuf[1] = SCD30_SERDEV_READ;
0119         if (cmd == CMD_READ_MEAS)
0120             /* number of u16 words to read */
0121             put_unaligned_be16(size / 2, txbuf + txsize);
0122         else
0123             put_unaligned_be16(0x0001, txbuf + txsize);
0124         txsize += 2;
0125         crc = scd30_serdev_calc_crc(txbuf, txsize);
0126         put_unaligned_le16(crc, txbuf + txsize);
0127         txsize += 2;
0128         rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE;
0129     } else {
0130         if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
0131             arg = 0x0001;
0132 
0133         txbuf[1] = SCD30_SERDEV_WRITE;
0134         put_unaligned_be16(arg, txbuf + txsize);
0135         txsize += 2;
0136         crc = scd30_serdev_calc_crc(txbuf, txsize);
0137         put_unaligned_le16(crc, txbuf + txsize);
0138         txsize += 2;
0139         rxsize = txsize;
0140     }
0141 
0142     ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);
0143     if (ret)
0144         return ret;
0145 
0146     switch (txbuf[1]) {
0147     case SCD30_SERDEV_WRITE:
0148         if (memcmp(txbuf, rxbuf, txsize)) {
0149             dev_err(state->dev, "wrong message received\n");
0150             return -EIO;
0151         }
0152         break;
0153     case SCD30_SERDEV_READ:
0154         if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) {
0155             dev_err(state->dev, "received data size does not match header\n");
0156             return -EIO;
0157         }
0158 
0159         rxsize -= SCD30_SERDEV_CRC_SIZE;
0160         crc = get_unaligned_le16(rxbuf + rxsize);
0161         if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {
0162             dev_err(state->dev, "data integrity check failed\n");
0163             return -EIO;
0164         }
0165 
0166         rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;
0167         memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);
0168         break;
0169     default:
0170         dev_err(state->dev, "received unknown op code\n");
0171         return -EIO;
0172     }
0173 
0174     return 0;
0175 }
0176 
0177 static int scd30_serdev_receive_buf(struct serdev_device *serdev,
0178                     const unsigned char *buf, size_t size)
0179 {
0180     struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
0181     struct scd30_serdev_priv *priv;
0182     struct scd30_state *state;
0183     int num;
0184 
0185     if (!indio_dev)
0186         return 0;
0187 
0188     state = iio_priv(indio_dev);
0189     priv = state->priv;
0190 
0191     /* just in case sensor puts some unexpected bytes on the bus */
0192     if (!priv->buf)
0193         return 0;
0194 
0195     if (priv->num + size >= priv->num_expected)
0196         num = priv->num_expected - priv->num;
0197     else
0198         num = size;
0199 
0200     memcpy(priv->buf + priv->num, buf, num);
0201     priv->num += num;
0202 
0203     if (priv->num == priv->num_expected) {
0204         priv->buf = NULL;
0205         complete(&priv->meas_ready);
0206     }
0207 
0208     return num;
0209 }
0210 
0211 static const struct serdev_device_ops scd30_serdev_ops = {
0212     .receive_buf = scd30_serdev_receive_buf,
0213     .write_wakeup = serdev_device_write_wakeup,
0214 };
0215 
0216 static int scd30_serdev_probe(struct serdev_device *serdev)
0217 {
0218     struct device *dev = &serdev->dev;
0219     struct scd30_serdev_priv *priv;
0220     int irq, ret;
0221 
0222     priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
0223     if (!priv)
0224         return -ENOMEM;
0225 
0226     init_completion(&priv->meas_ready);
0227     serdev_device_set_client_ops(serdev, &scd30_serdev_ops);
0228 
0229     ret = devm_serdev_device_open(dev, serdev);
0230     if (ret)
0231         return ret;
0232 
0233     serdev_device_set_baudrate(serdev, 19200);
0234     serdev_device_set_flow_control(serdev, false);
0235 
0236     ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
0237     if (ret)
0238         return ret;
0239 
0240     irq = fwnode_irq_get(dev_fwnode(dev), 0);
0241 
0242     return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command);
0243 }
0244 
0245 static const struct of_device_id scd30_serdev_of_match[] = {
0246     { .compatible = "sensirion,scd30" },
0247     { }
0248 };
0249 MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);
0250 
0251 static struct serdev_device_driver scd30_serdev_driver = {
0252     .driver = {
0253         .name = KBUILD_MODNAME,
0254         .of_match_table = scd30_serdev_of_match,
0255         .pm = pm_sleep_ptr(&scd30_pm_ops),
0256     },
0257     .probe = scd30_serdev_probe,
0258 };
0259 module_serdev_device_driver(scd30_serdev_driver);
0260 
0261 MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
0262 MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
0263 MODULE_LICENSE("GPL v2");
0264 MODULE_IMPORT_NS(IIO_SCD30);