1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Plantower PMS7003 particulate matter sensor driver |
4 | * |
5 | * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> |
6 | */ |
7 | |
8 | #include <asm/unaligned.h> |
9 | #include <linux/completion.h> |
10 | #include <linux/device.h> |
11 | #include <linux/errno.h> |
12 | #include <linux/iio/buffer.h> |
13 | #include <linux/iio/iio.h> |
14 | #include <linux/iio/trigger_consumer.h> |
15 | #include <linux/iio/triggered_buffer.h> |
16 | #include <linux/jiffies.h> |
17 | #include <linux/kernel.h> |
18 | #include <linux/mod_devicetable.h> |
19 | #include <linux/module.h> |
20 | #include <linux/mutex.h> |
21 | #include <linux/serdev.h> |
22 | |
23 | #define PMS7003_DRIVER_NAME "pms7003" |
24 | |
25 | #define PMS7003_MAGIC 0x424d |
26 | /* last 2 data bytes hold frame checksum */ |
27 | #define PMS7003_MAX_DATA_LENGTH 28 |
28 | #define PMS7003_CHECKSUM_LENGTH 2 |
29 | #define PMS7003_PM10_OFFSET 10 |
30 | #define PMS7003_PM2P5_OFFSET 8 |
31 | #define PMS7003_PM1_OFFSET 6 |
32 | |
33 | #define PMS7003_TIMEOUT msecs_to_jiffies(6000) |
34 | #define PMS7003_CMD_LENGTH 7 |
35 | #define PMS7003_PM_MAX 1000 |
36 | #define PMS7003_PM_MIN 0 |
37 | |
38 | enum { |
39 | PM1, |
40 | PM2P5, |
41 | PM10, |
42 | }; |
43 | |
44 | enum pms7003_cmd { |
45 | CMD_WAKEUP, |
46 | CMD_ENTER_PASSIVE_MODE, |
47 | CMD_READ_PASSIVE, |
48 | CMD_SLEEP, |
49 | }; |
50 | |
51 | /* |
52 | * commands have following format: |
53 | * |
54 | * +------+------+-----+------+-----+-----------+-----------+ |
55 | * | 0x42 | 0x4d | cmd | 0x00 | arg | cksum msb | cksum lsb | |
56 | * +------+------+-----+------+-----+-----------+-----------+ |
57 | */ |
58 | static const u8 pms7003_cmd_tbl[][PMS7003_CMD_LENGTH] = { |
59 | [CMD_WAKEUP] = { 0x42, 0x4d, 0xe4, 0x00, 0x01, 0x01, 0x74 }, |
60 | [CMD_ENTER_PASSIVE_MODE] = { 0x42, 0x4d, 0xe1, 0x00, 0x00, 0x01, 0x70 }, |
61 | [CMD_READ_PASSIVE] = { 0x42, 0x4d, 0xe2, 0x00, 0x00, 0x01, 0x71 }, |
62 | [CMD_SLEEP] = { 0x42, 0x4d, 0xe4, 0x00, 0x00, 0x01, 0x73 }, |
63 | }; |
64 | |
65 | struct pms7003_frame { |
66 | u8 data[PMS7003_MAX_DATA_LENGTH]; |
67 | u16 expected_length; |
68 | u16 length; |
69 | }; |
70 | |
71 | struct pms7003_state { |
72 | struct serdev_device *serdev; |
73 | struct pms7003_frame frame; |
74 | struct completion frame_ready; |
75 | struct mutex lock; /* must be held whenever state gets touched */ |
76 | /* Used to construct scan to push to the IIO buffer */ |
77 | struct { |
78 | u16 data[3]; /* PM1, PM2P5, PM10 */ |
79 | s64 ts; |
80 | } scan; |
81 | }; |
82 | |
83 | static int pms7003_do_cmd(struct pms7003_state *state, enum pms7003_cmd cmd) |
84 | { |
85 | int ret; |
86 | |
87 | ret = serdev_device_write(state->serdev, pms7003_cmd_tbl[cmd], |
88 | PMS7003_CMD_LENGTH, PMS7003_TIMEOUT); |
89 | if (ret < PMS7003_CMD_LENGTH) |
90 | return ret < 0 ? ret : -EIO; |
91 | |
92 | ret = wait_for_completion_interruptible_timeout(x: &state->frame_ready, |
93 | PMS7003_TIMEOUT); |
94 | if (!ret) |
95 | ret = -ETIMEDOUT; |
96 | |
97 | return ret < 0 ? ret : 0; |
98 | } |
99 | |
100 | static u16 pms7003_get_pm(const u8 *data) |
101 | { |
102 | return clamp_val(get_unaligned_be16(data), |
103 | PMS7003_PM_MIN, PMS7003_PM_MAX); |
104 | } |
105 | |
106 | static irqreturn_t pms7003_trigger_handler(int irq, void *p) |
107 | { |
108 | struct iio_poll_func *pf = p; |
109 | struct iio_dev *indio_dev = pf->indio_dev; |
110 | struct pms7003_state *state = iio_priv(indio_dev); |
111 | struct pms7003_frame *frame = &state->frame; |
112 | int ret; |
113 | |
114 | mutex_lock(&state->lock); |
115 | ret = pms7003_do_cmd(state, cmd: CMD_READ_PASSIVE); |
116 | if (ret) { |
117 | mutex_unlock(lock: &state->lock); |
118 | goto err; |
119 | } |
120 | |
121 | state->scan.data[PM1] = |
122 | pms7003_get_pm(data: frame->data + PMS7003_PM1_OFFSET); |
123 | state->scan.data[PM2P5] = |
124 | pms7003_get_pm(data: frame->data + PMS7003_PM2P5_OFFSET); |
125 | state->scan.data[PM10] = |
126 | pms7003_get_pm(data: frame->data + PMS7003_PM10_OFFSET); |
127 | mutex_unlock(lock: &state->lock); |
128 | |
129 | iio_push_to_buffers_with_timestamp(indio_dev, data: &state->scan, |
130 | timestamp: iio_get_time_ns(indio_dev)); |
131 | err: |
132 | iio_trigger_notify_done(trig: indio_dev->trig); |
133 | |
134 | return IRQ_HANDLED; |
135 | } |
136 | |
137 | static int pms7003_read_raw(struct iio_dev *indio_dev, |
138 | struct iio_chan_spec const *chan, |
139 | int *val, int *val2, long mask) |
140 | { |
141 | struct pms7003_state *state = iio_priv(indio_dev); |
142 | struct pms7003_frame *frame = &state->frame; |
143 | int ret; |
144 | |
145 | switch (mask) { |
146 | case IIO_CHAN_INFO_PROCESSED: |
147 | switch (chan->type) { |
148 | case IIO_MASSCONCENTRATION: |
149 | mutex_lock(&state->lock); |
150 | ret = pms7003_do_cmd(state, cmd: CMD_READ_PASSIVE); |
151 | if (ret) { |
152 | mutex_unlock(lock: &state->lock); |
153 | return ret; |
154 | } |
155 | |
156 | *val = pms7003_get_pm(data: frame->data + chan->address); |
157 | mutex_unlock(lock: &state->lock); |
158 | |
159 | return IIO_VAL_INT; |
160 | default: |
161 | return -EINVAL; |
162 | } |
163 | } |
164 | |
165 | return -EINVAL; |
166 | } |
167 | |
168 | static const struct iio_info pms7003_info = { |
169 | .read_raw = pms7003_read_raw, |
170 | }; |
171 | |
172 | #define PMS7003_CHAN(_index, _mod, _addr) { \ |
173 | .type = IIO_MASSCONCENTRATION, \ |
174 | .modified = 1, \ |
175 | .channel2 = IIO_MOD_ ## _mod, \ |
176 | .address = _addr, \ |
177 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED), \ |
178 | .scan_index = _index, \ |
179 | .scan_type = { \ |
180 | .sign = 'u', \ |
181 | .realbits = 10, \ |
182 | .storagebits = 16, \ |
183 | .endianness = IIO_CPU, \ |
184 | }, \ |
185 | } |
186 | |
187 | static const struct iio_chan_spec pms7003_channels[] = { |
188 | PMS7003_CHAN(0, PM1, PMS7003_PM1_OFFSET), |
189 | PMS7003_CHAN(1, PM2P5, PMS7003_PM2P5_OFFSET), |
190 | PMS7003_CHAN(2, PM10, PMS7003_PM10_OFFSET), |
191 | IIO_CHAN_SOFT_TIMESTAMP(3), |
192 | }; |
193 | |
194 | static u16 pms7003_calc_checksum(struct pms7003_frame *frame) |
195 | { |
196 | u16 checksum = (PMS7003_MAGIC >> 8) + (u8)(PMS7003_MAGIC & 0xff) + |
197 | (frame->length >> 8) + (u8)frame->length; |
198 | int i; |
199 | |
200 | for (i = 0; i < frame->length - PMS7003_CHECKSUM_LENGTH; i++) |
201 | checksum += frame->data[i]; |
202 | |
203 | return checksum; |
204 | } |
205 | |
206 | static bool pms7003_frame_is_okay(struct pms7003_frame *frame) |
207 | { |
208 | int offset = frame->length - PMS7003_CHECKSUM_LENGTH; |
209 | u16 checksum = get_unaligned_be16(p: frame->data + offset); |
210 | |
211 | return checksum == pms7003_calc_checksum(frame); |
212 | } |
213 | |
214 | static int pms7003_receive_buf(struct serdev_device *serdev, |
215 | const unsigned char *buf, size_t size) |
216 | { |
217 | struct iio_dev *indio_dev = serdev_device_get_drvdata(serdev); |
218 | struct pms7003_state *state = iio_priv(indio_dev); |
219 | struct pms7003_frame *frame = &state->frame; |
220 | int num; |
221 | |
222 | if (!frame->expected_length) { |
223 | u16 magic; |
224 | |
225 | /* wait for SOF and data length */ |
226 | if (size < 4) |
227 | return 0; |
228 | |
229 | magic = get_unaligned_be16(p: buf); |
230 | if (magic != PMS7003_MAGIC) |
231 | return 2; |
232 | |
233 | num = get_unaligned_be16(p: buf + 2); |
234 | if (num <= PMS7003_MAX_DATA_LENGTH) { |
235 | frame->expected_length = num; |
236 | frame->length = 0; |
237 | } |
238 | |
239 | return 4; |
240 | } |
241 | |
242 | num = min(size, (size_t)(frame->expected_length - frame->length)); |
243 | memcpy(frame->data + frame->length, buf, num); |
244 | frame->length += num; |
245 | |
246 | if (frame->length == frame->expected_length) { |
247 | if (pms7003_frame_is_okay(frame)) |
248 | complete(&state->frame_ready); |
249 | |
250 | frame->expected_length = 0; |
251 | } |
252 | |
253 | return num; |
254 | } |
255 | |
256 | static const struct serdev_device_ops pms7003_serdev_ops = { |
257 | .receive_buf = pms7003_receive_buf, |
258 | .write_wakeup = serdev_device_write_wakeup, |
259 | }; |
260 | |
261 | static void pms7003_stop(void *data) |
262 | { |
263 | struct pms7003_state *state = data; |
264 | |
265 | pms7003_do_cmd(state, cmd: CMD_SLEEP); |
266 | } |
267 | |
268 | static const unsigned long pms7003_scan_masks[] = { 0x07, 0x00 }; |
269 | |
270 | static int pms7003_probe(struct serdev_device *serdev) |
271 | { |
272 | struct pms7003_state *state; |
273 | struct iio_dev *indio_dev; |
274 | int ret; |
275 | |
276 | indio_dev = devm_iio_device_alloc(parent: &serdev->dev, sizeof_priv: sizeof(*state)); |
277 | if (!indio_dev) |
278 | return -ENOMEM; |
279 | |
280 | state = iio_priv(indio_dev); |
281 | serdev_device_set_drvdata(serdev, data: indio_dev); |
282 | state->serdev = serdev; |
283 | indio_dev->info = &pms7003_info; |
284 | indio_dev->name = PMS7003_DRIVER_NAME; |
285 | indio_dev->channels = pms7003_channels; |
286 | indio_dev->num_channels = ARRAY_SIZE(pms7003_channels); |
287 | indio_dev->modes = INDIO_DIRECT_MODE; |
288 | indio_dev->available_scan_masks = pms7003_scan_masks; |
289 | |
290 | mutex_init(&state->lock); |
291 | init_completion(x: &state->frame_ready); |
292 | |
293 | serdev_device_set_client_ops(serdev, ops: &pms7003_serdev_ops); |
294 | ret = devm_serdev_device_open(&serdev->dev, serdev); |
295 | if (ret) |
296 | return ret; |
297 | |
298 | serdev_device_set_baudrate(serdev, 9600); |
299 | serdev_device_set_flow_control(serdev, false); |
300 | |
301 | ret = serdev_device_set_parity(serdev, parity: SERDEV_PARITY_NONE); |
302 | if (ret) |
303 | return ret; |
304 | |
305 | ret = pms7003_do_cmd(state, cmd: CMD_WAKEUP); |
306 | if (ret) { |
307 | dev_err(&serdev->dev, "failed to wakeup sensor\n" ); |
308 | return ret; |
309 | } |
310 | |
311 | ret = pms7003_do_cmd(state, cmd: CMD_ENTER_PASSIVE_MODE); |
312 | if (ret) { |
313 | dev_err(&serdev->dev, "failed to enter passive mode\n" ); |
314 | return ret; |
315 | } |
316 | |
317 | ret = devm_add_action_or_reset(&serdev->dev, pms7003_stop, state); |
318 | if (ret) |
319 | return ret; |
320 | |
321 | ret = devm_iio_triggered_buffer_setup(&serdev->dev, indio_dev, NULL, |
322 | pms7003_trigger_handler, NULL); |
323 | if (ret) |
324 | return ret; |
325 | |
326 | return devm_iio_device_register(&serdev->dev, indio_dev); |
327 | } |
328 | |
329 | static const struct of_device_id pms7003_of_match[] = { |
330 | { .compatible = "plantower,pms1003" }, |
331 | { .compatible = "plantower,pms3003" }, |
332 | { .compatible = "plantower,pms5003" }, |
333 | { .compatible = "plantower,pms6003" }, |
334 | { .compatible = "plantower,pms7003" }, |
335 | { .compatible = "plantower,pmsa003" }, |
336 | { } |
337 | }; |
338 | MODULE_DEVICE_TABLE(of, pms7003_of_match); |
339 | |
340 | static struct serdev_device_driver pms7003_driver = { |
341 | .driver = { |
342 | .name = PMS7003_DRIVER_NAME, |
343 | .of_match_table = pms7003_of_match, |
344 | }, |
345 | .probe = pms7003_probe, |
346 | }; |
347 | module_serdev_device_driver(pms7003_driver); |
348 | |
349 | MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>" ); |
350 | MODULE_DESCRIPTION("Plantower PMS7003 particulate matter sensor driver" ); |
351 | MODULE_LICENSE("GPL v2" ); |
352 | |