linux/drivers/iio/chemical/scd30_serial.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Sensirion SCD30 carbon dioxide sensor serial driver
   4 *
   5 * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
   6 */
   7#include <linux/crc16.h>
   8#include <linux/device.h>
   9#include <linux/errno.h>
  10#include <linux/iio/iio.h>
  11#include <linux/jiffies.h>
  12#include <linux/mod_devicetable.h>
  13#include <linux/module.h>
  14#include <linux/property.h>
  15#include <linux/serdev.h>
  16#include <linux/string.h>
  17#include <linux/types.h>
  18#include <asm/unaligned.h>
  19
  20#include "scd30.h"
  21
  22#define SCD30_SERDEV_ADDR 0x61
  23#define SCD30_SERDEV_WRITE 0x06
  24#define SCD30_SERDEV_READ 0x03
  25#define SCD30_SERDEV_MAX_BUF_SIZE 17
  26#define SCD30_SERDEV_RX_HEADER_SIZE 3
  27#define SCD30_SERDEV_CRC_SIZE 2
  28#define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200)
  29
  30struct scd30_serdev_priv {
  31        struct completion meas_ready;
  32        char *buf;
  33        int num_expected;
  34        int num;
  35};
  36
  37static u16 scd30_serdev_cmd_lookup_tbl[] = {
  38        [CMD_START_MEAS] = 0x0036,
  39        [CMD_STOP_MEAS] = 0x0037,
  40        [CMD_MEAS_INTERVAL] = 0x0025,
  41        [CMD_MEAS_READY] = 0x0027,
  42        [CMD_READ_MEAS] = 0x0028,
  43        [CMD_ASC] = 0x003a,
  44        [CMD_FRC] = 0x0039,
  45        [CMD_TEMP_OFFSET] = 0x003b,
  46        [CMD_FW_VERSION] = 0x0020,
  47        [CMD_RESET] = 0x0034,
  48};
  49
  50static u16 scd30_serdev_calc_crc(const char *buf, int size)
  51{
  52        return crc16(0xffff, buf, size);
  53}
  54
  55static int scd30_serdev_xfer(struct scd30_state *state, char *txbuf, int txsize,
  56                             char *rxbuf, int rxsize)
  57{
  58        struct serdev_device *serdev = to_serdev_device(state->dev);
  59        struct scd30_serdev_priv *priv = state->priv;
  60        int ret;
  61
  62        priv->buf = rxbuf;
  63        priv->num_expected = rxsize;
  64        priv->num = 0;
  65
  66        ret = serdev_device_write(serdev, txbuf, txsize, SCD30_SERDEV_TIMEOUT);
  67        if (ret < 0)
  68                return ret;
  69        if (ret != txsize)
  70                return -EIO;
  71
  72        ret = wait_for_completion_interruptible_timeout(&priv->meas_ready, SCD30_SERDEV_TIMEOUT);
  73        if (ret < 0)
  74                return ret;
  75        if (!ret)
  76                return -ETIMEDOUT;
  77
  78        return 0;
  79}
  80
  81static int scd30_serdev_command(struct scd30_state *state, enum scd30_cmd cmd, u16 arg,
  82                                void *response, int size)
  83{
  84        /*
  85         * Communication over serial line is based on modbus protocol (or rather
  86         * its variation called modbus over serial to be precise). Upon
  87         * receiving a request device should reply with response.
  88         *
  89         * Frame below represents a request message. Each field takes
  90         * exactly one byte.
  91         *
  92         * +------+------+-----+-----+-------+-------+-----+-----+
  93         * | dev  | op   | reg | reg | byte1 | byte0 | crc | crc |
  94         * | addr | code | msb | lsb |       |       | lsb | msb |
  95         * +------+------+-----+-----+-------+-------+-----+-----+
  96         *
  97         * The message device replies with depends on the 'op code' field from
  98         * the request. In case it was set to SCD30_SERDEV_WRITE sensor should
  99         * reply with unchanged request. Otherwise 'op code' was set to
 100         * SCD30_SERDEV_READ and response looks like the one below. As with
 101         * request, each field takes one byte.
 102         *
 103         * +------+------+--------+-------+-----+-------+-----+-----+
 104         * | dev  | op   | num of | byte0 | ... | byteN | crc | crc |
 105         * | addr | code | bytes  |       |     |       | lsb | msb |
 106         * +------+------+--------+-------+-----+-------+-----+-----+
 107         */
 108        char txbuf[SCD30_SERDEV_MAX_BUF_SIZE] = { SCD30_SERDEV_ADDR },
 109             rxbuf[SCD30_SERDEV_MAX_BUF_SIZE];
 110        int ret, rxsize, txsize = 2;
 111        char *rsp = response;
 112        u16 crc;
 113
 114        put_unaligned_be16(scd30_serdev_cmd_lookup_tbl[cmd], txbuf + txsize);
 115        txsize += 2;
 116
 117        if (rsp) {
 118                txbuf[1] = SCD30_SERDEV_READ;
 119                if (cmd == CMD_READ_MEAS)
 120                        /* number of u16 words to read */
 121                        put_unaligned_be16(size / 2, txbuf + txsize);
 122                else
 123                        put_unaligned_be16(0x0001, txbuf + txsize);
 124                txsize += 2;
 125                crc = scd30_serdev_calc_crc(txbuf, txsize);
 126                put_unaligned_le16(crc, txbuf + txsize);
 127                txsize += 2;
 128                rxsize = SCD30_SERDEV_RX_HEADER_SIZE + size + SCD30_SERDEV_CRC_SIZE;
 129        } else {
 130                if ((cmd == CMD_STOP_MEAS) || (cmd == CMD_RESET))
 131                        arg = 0x0001;
 132
 133                txbuf[1] = SCD30_SERDEV_WRITE;
 134                put_unaligned_be16(arg, txbuf + txsize);
 135                txsize += 2;
 136                crc = scd30_serdev_calc_crc(txbuf, txsize);
 137                put_unaligned_le16(crc, txbuf + txsize);
 138                txsize += 2;
 139                rxsize = txsize;
 140        }
 141
 142        ret = scd30_serdev_xfer(state, txbuf, txsize, rxbuf, rxsize);
 143        if (ret)
 144                return ret;
 145
 146        switch (txbuf[1]) {
 147        case SCD30_SERDEV_WRITE:
 148                if (memcmp(txbuf, rxbuf, txsize)) {
 149                        dev_err(state->dev, "wrong message received\n");
 150                        return -EIO;
 151                }
 152                break;
 153        case SCD30_SERDEV_READ:
 154                if (rxbuf[2] != (rxsize - SCD30_SERDEV_RX_HEADER_SIZE - SCD30_SERDEV_CRC_SIZE)) {
 155                        dev_err(state->dev, "received data size does not match header\n");
 156                        return -EIO;
 157                }
 158
 159                rxsize -= SCD30_SERDEV_CRC_SIZE;
 160                crc = get_unaligned_le16(rxbuf + rxsize);
 161                if (crc != scd30_serdev_calc_crc(rxbuf, rxsize)) {
 162                        dev_err(state->dev, "data integrity check failed\n");
 163                        return -EIO;
 164                }
 165
 166                rxsize -= SCD30_SERDEV_RX_HEADER_SIZE;
 167                memcpy(rsp, rxbuf + SCD30_SERDEV_RX_HEADER_SIZE, rxsize);
 168                break;
 169        default:
 170                dev_err(state->dev, "received unknown op code\n");
 171                return -EIO;
 172        }
 173
 174        return 0;
 175}
 176
 177static int scd30_serdev_receive_buf(struct serdev_device *serdev,
 178                                    const unsigned char *buf, size_t size)
 179{
 180        struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev);
 181        struct scd30_serdev_priv *priv;
 182        struct scd30_state *state;
 183        int num;
 184
 185        if (!indio_dev)
 186                return 0;
 187
 188        state = iio_priv(indio_dev);
 189        priv = state->priv;
 190
 191        /* just in case sensor puts some unexpected bytes on the bus */
 192        if (!priv->buf)
 193                return 0;
 194
 195        if (priv->num + size >= priv->num_expected)
 196                num = priv->num_expected - priv->num;
 197        else
 198                num = size;
 199
 200        memcpy(priv->buf + priv->num, buf, num);
 201        priv->num += num;
 202
 203        if (priv->num == priv->num_expected) {
 204                priv->buf = NULL;
 205                complete(&priv->meas_ready);
 206        }
 207
 208        return num;
 209}
 210
 211static const struct serdev_device_ops scd30_serdev_ops = {
 212        .receive_buf = scd30_serdev_receive_buf,
 213        .write_wakeup = serdev_device_write_wakeup,
 214};
 215
 216static int scd30_serdev_probe(struct serdev_device *serdev)
 217{
 218        struct device *dev = &serdev->dev;
 219        struct scd30_serdev_priv *priv;
 220        int irq, ret;
 221
 222        priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
 223        if (!priv)
 224                return -ENOMEM;
 225
 226        init_completion(&priv->meas_ready);
 227        serdev_device_set_client_ops(serdev, &scd30_serdev_ops);
 228
 229        ret = devm_serdev_device_open(dev, serdev);
 230        if (ret)
 231                return ret;
 232
 233        serdev_device_set_baudrate(serdev, 19200);
 234        serdev_device_set_flow_control(serdev, false);
 235
 236        ret = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
 237        if (ret)
 238                return ret;
 239
 240        irq = fwnode_irq_get(dev_fwnode(dev), 0);
 241
 242        return scd30_probe(dev, irq, KBUILD_MODNAME, priv, scd30_serdev_command);
 243}
 244
 245static const struct of_device_id scd30_serdev_of_match[] = {
 246        { .compatible = "sensirion,scd30" },
 247        { }
 248};
 249MODULE_DEVICE_TABLE(of, scd30_serdev_of_match);
 250
 251static struct serdev_device_driver scd30_serdev_driver = {
 252        .driver = {
 253                .name = KBUILD_MODNAME,
 254                .of_match_table = scd30_serdev_of_match,
 255                .pm = &scd30_pm_ops,
 256        },
 257        .probe = scd30_serdev_probe,
 258};
 259module_serdev_device_driver(scd30_serdev_driver);
 260
 261MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
 262MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
 263MODULE_LICENSE("GPL v2");
 264