scd30_serial.c (7124B)
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 = pm_sleep_ptr(&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"); 264MODULE_IMPORT_NS(IIO_SCD30);