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 3 |
27 | #define SCD30_SERDEV_CRC_SIZE 2 |
28 | #define SCD30_SERDEV_TIMEOUT msecs_to_jiffies(200) |
29 | |
30 | struct scd30_serdev_priv { |
31 | struct completion meas_ready; |
32 | char *buf; |
33 | int num_expected; |
34 | int num; |
35 | }; |
36 | |
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, |
43 | [CMD_ASC] = 0x003a, |
44 | [CMD_FRC] = 0x0039, |
45 | [CMD_TEMP_OFFSET] = 0x003b, |
46 | [CMD_FW_VERSION] = 0x0020, |
47 | [CMD_RESET] = 0x0034, |
48 | }; |
49 | |
50 | static u16 scd30_serdev_calc_crc(const char *buf, int size) |
51 | { |
52 | return crc16(crc: 0xffff, buffer: buf, len: size); |
53 | } |
54 | |
55 | static 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(d: 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(x: &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 | |
81 | static 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(val: scd30_serdev_cmd_lookup_tbl[cmd], p: 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(val: size / 2, p: txbuf + txsize); |
122 | else |
123 | put_unaligned_be16(val: 0x0001, p: txbuf + txsize); |
124 | txsize += 2; |
125 | crc = scd30_serdev_calc_crc(buf: txbuf, size: txsize); |
126 | put_unaligned_le16(val: crc, p: 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(val: arg, p: txbuf + txsize); |
135 | txsize += 2; |
136 | crc = scd30_serdev_calc_crc(buf: txbuf, size: txsize); |
137 | put_unaligned_le16(val: crc, p: 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(p: txbuf, q: rxbuf, size: 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(p: rxbuf + rxsize); |
161 | if (crc != scd30_serdev_calc_crc(buf: rxbuf, size: 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 | |
177 | static size_t scd30_serdev_receive_buf(struct serdev_device *serdev, |
178 | const u8 *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 | size_t 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 | |
211 | static const struct serdev_device_ops scd30_serdev_ops = { |
212 | .receive_buf = scd30_serdev_receive_buf, |
213 | .write_wakeup = serdev_device_write_wakeup, |
214 | }; |
215 | |
216 | static 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, size: sizeof(*priv), GFP_KERNEL); |
223 | if (!priv) |
224 | return -ENOMEM; |
225 | |
226 | init_completion(x: &priv->meas_ready); |
227 | serdev_device_set_client_ops(serdev, ops: &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, parity: SERDEV_PARITY_NONE); |
237 | if (ret) |
238 | return ret; |
239 | |
240 | irq = fwnode_irq_get(dev_fwnode(dev), index: 0); |
241 | |
242 | return scd30_probe(dev, irq, KBUILD_MODNAME, priv, command: scd30_serdev_command); |
243 | } |
244 | |
245 | static const struct of_device_id scd30_serdev_of_match[] = { |
246 | { .compatible = "sensirion,scd30" }, |
247 | { } |
248 | }; |
249 | MODULE_DEVICE_TABLE(of, scd30_serdev_of_match); |
250 | |
251 | static 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 | }; |
259 | module_serdev_device_driver(scd30_serdev_driver); |
260 | |
261 | MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>" ); |
262 | MODULE_DESCRIPTION("Sensirion SCD30 carbon dioxide sensor serial driver" ); |
263 | MODULE_LICENSE("GPL v2" ); |
264 | MODULE_IMPORT_NS(IIO_SCD30); |
265 | |