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