1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Sensirion SPS30 particulate matter sensor serial driver |
4 | * |
5 | * Copyright (c) 2021 Tomasz Duszynski <tomasz.duszynski@octakon.com> |
6 | */ |
7 | #include <linux/completion.h> |
8 | #include <linux/device.h> |
9 | #include <linux/errno.h> |
10 | #include <linux/iio/iio.h> |
11 | #include <linux/minmax.h> |
12 | #include <linux/mod_devicetable.h> |
13 | #include <linux/module.h> |
14 | #include <linux/serdev.h> |
15 | #include <linux/types.h> |
16 | |
17 | #include "sps30.h" |
18 | |
19 | #define SPS30_SERIAL_DEV_NAME "sps30" |
20 | |
21 | #define SPS30_SERIAL_SOF_EOF 0x7e |
22 | #define SPS30_SERIAL_TIMEOUT msecs_to_jiffies(20) |
23 | #define SPS30_SERIAL_MAX_BUF_SIZE 263 |
24 | #define SPS30_SERIAL_ESCAPE_CHAR 0x7d |
25 | |
26 | #define SPS30_SERIAL_FRAME_MIN_SIZE 7 |
27 | #define SPS30_SERIAL_FRAME_ADR_OFFSET 1 |
28 | #define SPS30_SERIAL_FRAME_CMD_OFFSET 2 |
29 | #define SPS30_SERIAL_FRAME_MOSI_LEN_OFFSET 3 |
30 | #define SPS30_SERIAL_FRAME_MISO_STATE_OFFSET 3 |
31 | #define SPS30_SERIAL_FRAME_MISO_LEN_OFFSET 4 |
32 | #define SPS30_SERIAL_FRAME_MISO_DATA_OFFSET 5 |
33 | |
34 | #define SPS30_SERIAL_START_MEAS 0x00 |
35 | #define SPS30_SERIAL_STOP_MEAS 0x01 |
36 | #define SPS30_SERIAL_READ_MEAS 0x03 |
37 | #define SPS30_SERIAL_RESET 0xd3 |
38 | #define SPS30_SERIAL_CLEAN_FAN 0x56 |
39 | #define SPS30_SERIAL_PERIOD 0x80 |
40 | #define SPS30_SERIAL_DEV_INFO 0xd0 |
41 | #define SPS30_SERIAL_READ_VERSION 0xd1 |
42 | |
43 | struct sps30_serial_priv { |
44 | struct completion new_frame; |
45 | unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE]; |
46 | size_t num; |
47 | bool escaped; |
48 | bool done; |
49 | }; |
50 | |
51 | static int sps30_serial_xfer(struct sps30_state *state, const unsigned char *buf, size_t size) |
52 | { |
53 | struct serdev_device *serdev = to_serdev_device(d: state->dev); |
54 | struct sps30_serial_priv *priv = state->priv; |
55 | int ret; |
56 | |
57 | priv->num = 0; |
58 | priv->escaped = false; |
59 | priv->done = false; |
60 | |
61 | ret = serdev_device_write(serdev, buf, size, SPS30_SERIAL_TIMEOUT); |
62 | if (ret < 0) |
63 | return ret; |
64 | if (ret != size) |
65 | return -EIO; |
66 | |
67 | ret = wait_for_completion_interruptible_timeout(x: &priv->new_frame, SPS30_SERIAL_TIMEOUT); |
68 | if (ret < 0) |
69 | return ret; |
70 | if (!ret) |
71 | return -ETIMEDOUT; |
72 | |
73 | return 0; |
74 | } |
75 | |
76 | static const struct { |
77 | u8 byte; |
78 | u8 byte2; |
79 | } sps30_serial_bytes[] = { |
80 | { 0x11, 0x31 }, |
81 | { 0x13, 0x33 }, |
82 | { 0x7e, 0x5e }, |
83 | { 0x7d, 0x5d }, |
84 | }; |
85 | |
86 | static int sps30_serial_put_byte(u8 *buf, u8 byte) |
87 | { |
88 | int i; |
89 | |
90 | for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) { |
91 | if (sps30_serial_bytes[i].byte != byte) |
92 | continue; |
93 | |
94 | buf[0] = SPS30_SERIAL_ESCAPE_CHAR; |
95 | buf[1] = sps30_serial_bytes[i].byte2; |
96 | |
97 | return 2; |
98 | } |
99 | |
100 | buf[0] = byte; |
101 | |
102 | return 1; |
103 | } |
104 | |
105 | static u8 sps30_serial_get_byte(bool escaped, u8 byte2) |
106 | { |
107 | int i; |
108 | |
109 | if (!escaped) |
110 | return byte2; |
111 | |
112 | for (i = 0; i < ARRAY_SIZE(sps30_serial_bytes); i++) { |
113 | if (sps30_serial_bytes[i].byte2 != byte2) |
114 | continue; |
115 | |
116 | return sps30_serial_bytes[i].byte; |
117 | } |
118 | |
119 | return 0; |
120 | } |
121 | |
122 | static unsigned char sps30_serial_calc_chksum(const unsigned char *buf, size_t num) |
123 | { |
124 | unsigned int chksum = 0; |
125 | size_t i; |
126 | |
127 | for (i = 0; i < num; i++) |
128 | chksum += buf[i]; |
129 | |
130 | return ~chksum; |
131 | } |
132 | |
133 | static int sps30_serial_prep_frame(u8 *buf, u8 cmd, const u8 *arg, |
134 | size_t arg_size) |
135 | { |
136 | unsigned char chksum; |
137 | int num = 0; |
138 | size_t i; |
139 | |
140 | buf[num++] = SPS30_SERIAL_SOF_EOF; |
141 | buf[num++] = 0; |
142 | num += sps30_serial_put_byte(buf: buf + num, byte: cmd); |
143 | num += sps30_serial_put_byte(buf: buf + num, byte: arg_size); |
144 | |
145 | for (i = 0; i < arg_size; i++) |
146 | num += sps30_serial_put_byte(buf: buf + num, byte: arg[i]); |
147 | |
148 | /* SOF isn't checksummed */ |
149 | chksum = sps30_serial_calc_chksum(buf: buf + 1, num: num - 1); |
150 | num += sps30_serial_put_byte(buf: buf + num, byte: chksum); |
151 | buf[num++] = SPS30_SERIAL_SOF_EOF; |
152 | |
153 | return num; |
154 | } |
155 | |
156 | static bool sps30_serial_frame_valid(struct sps30_state *state, const unsigned char *buf) |
157 | { |
158 | struct sps30_serial_priv *priv = state->priv; |
159 | unsigned char chksum; |
160 | |
161 | if ((priv->num < SPS30_SERIAL_FRAME_MIN_SIZE) || |
162 | (priv->num != SPS30_SERIAL_FRAME_MIN_SIZE + |
163 | priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET])) { |
164 | dev_err(state->dev, "frame has invalid number of bytes\n" ); |
165 | return false; |
166 | } |
167 | |
168 | if ((priv->buf[SPS30_SERIAL_FRAME_ADR_OFFSET] != buf[SPS30_SERIAL_FRAME_ADR_OFFSET]) || |
169 | (priv->buf[SPS30_SERIAL_FRAME_CMD_OFFSET] != buf[SPS30_SERIAL_FRAME_CMD_OFFSET])) { |
170 | dev_err(state->dev, "frame has wrong ADR and CMD bytes\n" ); |
171 | return false; |
172 | } |
173 | |
174 | if (priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]) { |
175 | dev_err(state->dev, "frame with non-zero state received (0x%02x)\n" , |
176 | priv->buf[SPS30_SERIAL_FRAME_MISO_STATE_OFFSET]); |
177 | return false; |
178 | } |
179 | |
180 | /* SOF, checksum and EOF are not checksummed */ |
181 | chksum = sps30_serial_calc_chksum(buf: priv->buf + 1, num: priv->num - 3); |
182 | if (priv->buf[priv->num - 2] != chksum) { |
183 | dev_err(state->dev, "frame integrity check failed\n" ); |
184 | return false; |
185 | } |
186 | |
187 | return true; |
188 | } |
189 | |
190 | static int sps30_serial_command(struct sps30_state *state, unsigned char cmd, |
191 | const void *arg, size_t arg_size, void *rsp, size_t rsp_size) |
192 | { |
193 | struct sps30_serial_priv *priv = state->priv; |
194 | unsigned char buf[SPS30_SERIAL_MAX_BUF_SIZE]; |
195 | int ret, size; |
196 | |
197 | size = sps30_serial_prep_frame(buf, cmd, arg, arg_size); |
198 | ret = sps30_serial_xfer(state, buf, size); |
199 | if (ret) |
200 | return ret; |
201 | |
202 | if (!sps30_serial_frame_valid(state, buf)) |
203 | return -EIO; |
204 | |
205 | if (rsp) { |
206 | rsp_size = min_t(size_t, priv->buf[SPS30_SERIAL_FRAME_MISO_LEN_OFFSET], rsp_size); |
207 | memcpy(rsp, &priv->buf[SPS30_SERIAL_FRAME_MISO_DATA_OFFSET], rsp_size); |
208 | } |
209 | |
210 | return rsp_size; |
211 | } |
212 | |
213 | static size_t sps30_serial_receive_buf(struct serdev_device *serdev, |
214 | const u8 *buf, size_t size) |
215 | { |
216 | struct iio_dev *indio_dev = dev_get_drvdata(dev: &serdev->dev); |
217 | struct sps30_serial_priv *priv; |
218 | struct sps30_state *state; |
219 | size_t i; |
220 | u8 byte; |
221 | |
222 | if (!indio_dev) |
223 | return 0; |
224 | |
225 | state = iio_priv(indio_dev); |
226 | priv = state->priv; |
227 | |
228 | /* just in case device put some unexpected data on the bus */ |
229 | if (priv->done) |
230 | return size; |
231 | |
232 | /* wait for the start of frame */ |
233 | if (!priv->num && size && buf[0] != SPS30_SERIAL_SOF_EOF) |
234 | return 1; |
235 | |
236 | if (priv->num + size >= ARRAY_SIZE(priv->buf)) |
237 | size = ARRAY_SIZE(priv->buf) - priv->num; |
238 | |
239 | for (i = 0; i < size; i++) { |
240 | byte = buf[i]; |
241 | /* remove stuffed bytes on-the-fly */ |
242 | if (byte == SPS30_SERIAL_ESCAPE_CHAR) { |
243 | priv->escaped = true; |
244 | continue; |
245 | } |
246 | |
247 | byte = sps30_serial_get_byte(escaped: priv->escaped, byte2: byte); |
248 | if (priv->escaped && !byte) |
249 | dev_warn(state->dev, "unrecognized escaped char (0x%02x)\n" , byte); |
250 | |
251 | priv->buf[priv->num++] = byte; |
252 | |
253 | /* EOF received */ |
254 | if (!priv->escaped && byte == SPS30_SERIAL_SOF_EOF) { |
255 | if (priv->num < SPS30_SERIAL_FRAME_MIN_SIZE) |
256 | continue; |
257 | |
258 | priv->done = true; |
259 | complete(&priv->new_frame); |
260 | i++; |
261 | break; |
262 | } |
263 | |
264 | priv->escaped = false; |
265 | } |
266 | |
267 | return i; |
268 | } |
269 | |
270 | static const struct serdev_device_ops sps30_serial_device_ops = { |
271 | .receive_buf = sps30_serial_receive_buf, |
272 | .write_wakeup = serdev_device_write_wakeup, |
273 | }; |
274 | |
275 | static int sps30_serial_start_meas(struct sps30_state *state) |
276 | { |
277 | /* request BE IEEE754 formatted data */ |
278 | unsigned char buf[] = { 0x01, 0x03 }; |
279 | |
280 | return sps30_serial_command(state, SPS30_SERIAL_START_MEAS, arg: buf, arg_size: sizeof(buf), NULL, rsp_size: 0); |
281 | } |
282 | |
283 | static int sps30_serial_stop_meas(struct sps30_state *state) |
284 | { |
285 | return sps30_serial_command(state, SPS30_SERIAL_STOP_MEAS, NULL, arg_size: 0, NULL, rsp_size: 0); |
286 | } |
287 | |
288 | static int sps30_serial_reset(struct sps30_state *state) |
289 | { |
290 | int ret; |
291 | |
292 | ret = sps30_serial_command(state, SPS30_SERIAL_RESET, NULL, arg_size: 0, NULL, rsp_size: 0); |
293 | msleep(msecs: 500); |
294 | |
295 | return ret; |
296 | } |
297 | |
298 | static int sps30_serial_read_meas(struct sps30_state *state, __be32 *meas, size_t num) |
299 | { |
300 | int ret; |
301 | |
302 | /* measurements are ready within a second */ |
303 | if (msleep_interruptible(msecs: 1000)) |
304 | return -EINTR; |
305 | |
306 | ret = sps30_serial_command(state, SPS30_SERIAL_READ_MEAS, NULL, arg_size: 0, rsp: meas, rsp_size: num * sizeof(num)); |
307 | if (ret < 0) |
308 | return ret; |
309 | /* if measurements aren't ready sensor returns empty frame */ |
310 | if (ret == SPS30_SERIAL_FRAME_MIN_SIZE) |
311 | return -ETIMEDOUT; |
312 | if (ret != num * sizeof(*meas)) |
313 | return -EIO; |
314 | |
315 | return 0; |
316 | } |
317 | |
318 | static int sps30_serial_clean_fan(struct sps30_state *state) |
319 | { |
320 | return sps30_serial_command(state, SPS30_SERIAL_CLEAN_FAN, NULL, arg_size: 0, NULL, rsp_size: 0); |
321 | } |
322 | |
323 | static int sps30_serial_read_cleaning_period(struct sps30_state *state, __be32 *period) |
324 | { |
325 | unsigned char buf[] = { 0x00 }; |
326 | int ret; |
327 | |
328 | ret = sps30_serial_command(state, SPS30_SERIAL_PERIOD, arg: buf, arg_size: sizeof(buf), |
329 | rsp: period, rsp_size: sizeof(*period)); |
330 | if (ret < 0) |
331 | return ret; |
332 | if (ret != sizeof(*period)) |
333 | return -EIO; |
334 | |
335 | return 0; |
336 | } |
337 | |
338 | static int sps30_serial_write_cleaning_period(struct sps30_state *state, __be32 period) |
339 | { |
340 | unsigned char buf[5] = { 0x00 }; |
341 | |
342 | memcpy(buf + 1, &period, sizeof(period)); |
343 | |
344 | return sps30_serial_command(state, SPS30_SERIAL_PERIOD, arg: buf, arg_size: sizeof(buf), NULL, rsp_size: 0); |
345 | } |
346 | |
347 | static int sps30_serial_show_info(struct sps30_state *state) |
348 | { |
349 | /* |
350 | * tell device do return serial number and add extra nul byte just in case |
351 | * serial number isn't a valid string |
352 | */ |
353 | unsigned char buf[32 + 1] = { 0x03 }; |
354 | struct device *dev = state->dev; |
355 | int ret; |
356 | |
357 | ret = sps30_serial_command(state, SPS30_SERIAL_DEV_INFO, arg: buf, arg_size: 1, rsp: buf, rsp_size: sizeof(buf) - 1); |
358 | if (ret < 0) |
359 | return ret; |
360 | if (ret != sizeof(buf) - 1) |
361 | return -EIO; |
362 | |
363 | dev_info(dev, "serial number: %s\n" , buf); |
364 | |
365 | ret = sps30_serial_command(state, SPS30_SERIAL_READ_VERSION, NULL, arg_size: 0, rsp: buf, rsp_size: sizeof(buf) - 1); |
366 | if (ret < 0) |
367 | return ret; |
368 | if (ret < 2) |
369 | return -EIO; |
370 | |
371 | dev_info(dev, "fw version: %u.%u\n" , buf[0], buf[1]); |
372 | |
373 | return 0; |
374 | } |
375 | |
376 | static const struct sps30_ops sps30_serial_ops = { |
377 | .start_meas = sps30_serial_start_meas, |
378 | .stop_meas = sps30_serial_stop_meas, |
379 | .read_meas = sps30_serial_read_meas, |
380 | .reset = sps30_serial_reset, |
381 | .clean_fan = sps30_serial_clean_fan, |
382 | .read_cleaning_period = sps30_serial_read_cleaning_period, |
383 | .write_cleaning_period = sps30_serial_write_cleaning_period, |
384 | .show_info = sps30_serial_show_info, |
385 | }; |
386 | |
387 | static int sps30_serial_probe(struct serdev_device *serdev) |
388 | { |
389 | struct device *dev = &serdev->dev; |
390 | struct sps30_serial_priv *priv; |
391 | int ret; |
392 | |
393 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
394 | if (!priv) |
395 | return -ENOMEM; |
396 | |
397 | init_completion(x: &priv->new_frame); |
398 | serdev_device_set_client_ops(serdev, ops: &sps30_serial_device_ops); |
399 | |
400 | ret = devm_serdev_device_open(dev, serdev); |
401 | if (ret) |
402 | return ret; |
403 | |
404 | serdev_device_set_baudrate(serdev, 115200); |
405 | serdev_device_set_flow_control(serdev, false); |
406 | |
407 | ret = serdev_device_set_parity(serdev, parity: SERDEV_PARITY_NONE); |
408 | if (ret) |
409 | return ret; |
410 | |
411 | return sps30_probe(dev, SPS30_SERIAL_DEV_NAME, priv, ops: &sps30_serial_ops); |
412 | } |
413 | |
414 | static const struct of_device_id sps30_serial_of_match[] = { |
415 | { .compatible = "sensirion,sps30" }, |
416 | { } |
417 | }; |
418 | MODULE_DEVICE_TABLE(of, sps30_serial_of_match); |
419 | |
420 | static struct serdev_device_driver sps30_serial_driver = { |
421 | .driver = { |
422 | .name = KBUILD_MODNAME, |
423 | .of_match_table = sps30_serial_of_match, |
424 | }, |
425 | .probe = sps30_serial_probe, |
426 | }; |
427 | module_serdev_device_driver(sps30_serial_driver); |
428 | |
429 | MODULE_AUTHOR("Tomasz Duszynski <tomasz.duszynski@octakon.com>" ); |
430 | MODULE_DESCRIPTION("Sensirion SPS30 particulate matter sensor serial driver" ); |
431 | MODULE_LICENSE("GPL v2" ); |
432 | MODULE_IMPORT_NS(IIO_SPS30); |
433 | |