1 // SPDX-License-Identifier: GPL-2.0
3 * Sensirion SCD30 carbon dioxide sensor serial driver
5 * Copyright (c) 2020 Tomasz Duszynski <tomasz.duszynski@octakon.com>
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>
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)
30 struct scd30_serdev_priv
{
31 struct completion meas_ready
;
37 static 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,
45 [CMD_TEMP_OFFSET
] = 0x003b,
46 [CMD_FW_VERSION
] = 0x0020,
50 static u16
scd30_serdev_calc_crc(const char *buf
, int size
)
52 return crc16(0xffff, buf
, size
);
55 static int scd30_serdev_xfer(struct scd30_state
*state
, char *txbuf
, int txsize
,
56 char *rxbuf
, int rxsize
)
58 struct serdev_device
*serdev
= to_serdev_device(state
->dev
);
59 struct scd30_serdev_priv
*priv
= state
->priv
;
63 priv
->num_expected
= rxsize
;
66 ret
= serdev_device_write(serdev
, txbuf
, txsize
, SCD30_SERDEV_TIMEOUT
);
72 ret
= wait_for_completion_interruptible_timeout(&priv
->meas_ready
, SCD30_SERDEV_TIMEOUT
);
81 static int scd30_serdev_command(struct scd30_state
*state
, enum scd30_cmd cmd
, u16 arg
,
82 void *response
, int size
)
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.
89 * Frame below represents a request message. Each field takes
92 * +------+------+-----+-----+-------+-------+-----+-----+
93 * | dev | op | reg | reg | byte1 | byte0 | crc | crc |
94 * | addr | code | msb | lsb | | | lsb | msb |
95 * +------+------+-----+-----+-------+-------+-----+-----+
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.
103 * +------+------+--------+-------+-----+-------+-----+-----+
104 * | dev | op | num of | byte0 | ... | byteN | crc | crc |
105 * | addr | code | bytes | | | | lsb | msb |
106 * +------+------+--------+-------+-----+-------+-----+-----+
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
;
114 put_unaligned_be16(scd30_serdev_cmd_lookup_tbl
[cmd
], txbuf
+ txsize
);
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
);
123 put_unaligned_be16(0x0001, txbuf
+ txsize
);
125 crc
= scd30_serdev_calc_crc(txbuf
, txsize
);
126 put_unaligned_le16(crc
, txbuf
+ txsize
);
128 rxsize
= SCD30_SERDEV_RX_HEADER_SIZE
+ size
+ SCD30_SERDEV_CRC_SIZE
;
130 if ((cmd
== CMD_STOP_MEAS
) || (cmd
== CMD_RESET
))
133 txbuf
[1] = SCD30_SERDEV_WRITE
;
134 put_unaligned_be16(arg
, txbuf
+ txsize
);
136 crc
= scd30_serdev_calc_crc(txbuf
, txsize
);
137 put_unaligned_le16(crc
, txbuf
+ txsize
);
142 ret
= scd30_serdev_xfer(state
, txbuf
, txsize
, rxbuf
, rxsize
);
147 case SCD30_SERDEV_WRITE
:
148 if (memcmp(txbuf
, rxbuf
, txsize
)) {
149 dev_err(state
->dev
, "wrong message received\n");
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");
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");
166 rxsize
-= SCD30_SERDEV_RX_HEADER_SIZE
;
167 memcpy(rsp
, rxbuf
+ SCD30_SERDEV_RX_HEADER_SIZE
, rxsize
);
170 dev_err(state
->dev
, "received unknown op code\n");
177 static int scd30_serdev_receive_buf(struct serdev_device
*serdev
,
178 const unsigned char *buf
, size_t size
)
180 struct iio_dev
*indio_dev
= dev_get_drvdata(&serdev
->dev
);
181 struct scd30_serdev_priv
*priv
;
182 struct scd30_state
*state
;
188 state
= iio_priv(indio_dev
);
191 /* just in case sensor puts some unexpected bytes on the bus */
195 if (priv
->num
+ size
>= priv
->num_expected
)
196 num
= priv
->num_expected
- priv
->num
;
200 memcpy(priv
->buf
+ priv
->num
, buf
, num
);
203 if (priv
->num
== priv
->num_expected
) {
205 complete(&priv
->meas_ready
);
211 static const struct serdev_device_ops scd30_serdev_ops
= {
212 .receive_buf
= scd30_serdev_receive_buf
,
213 .write_wakeup
= serdev_device_write_wakeup
,
216 static int scd30_serdev_probe(struct serdev_device
*serdev
)
218 struct device
*dev
= &serdev
->dev
;
219 struct scd30_serdev_priv
*priv
;
222 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
226 init_completion(&priv
->meas_ready
);
227 serdev_device_set_client_ops(serdev
, &scd30_serdev_ops
);
229 ret
= devm_serdev_device_open(dev
, serdev
);
233 serdev_device_set_baudrate(serdev
, 19200);
234 serdev_device_set_flow_control(serdev
, false);
236 ret
= serdev_device_set_parity(serdev
, SERDEV_PARITY_NONE
);
240 irq
= fwnode_irq_get(dev_fwnode(dev
), 0);
242 return scd30_probe(dev
, irq
, KBUILD_MODNAME
, priv
, scd30_serdev_command
);
245 static const struct of_device_id scd30_serdev_of_match
[] = {
246 { .compatible
= "sensirion,scd30" },
249 MODULE_DEVICE_TABLE(of
, scd30_serdev_of_match
);
251 static struct serdev_device_driver scd30_serdev_driver
= {
253 .name
= KBUILD_MODNAME
,
254 .of_match_table
= scd30_serdev_of_match
,
257 .probe
= scd30_serdev_probe
,
259 module_serdev_device_driver(scd30_serdev_driver
);
261 MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>");
262 MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver");
263 MODULE_LICENSE("GPL v2");