1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * MS5611 pressure and temperature sensor driver |
4 | * |
5 | * Copyright (c) Tomasz Duszynski <tduszyns@gmail.com> |
6 | * |
7 | * Data sheet: |
8 | * http://www.meas-spec.com/downloads/MS5611-01BA03.pdf |
9 | * http://www.meas-spec.com/downloads/MS5607-02BA03.pdf |
10 | * |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/iio/iio.h> |
15 | #include <linux/delay.h> |
16 | #include <linux/regulator/consumer.h> |
17 | |
18 | #include <linux/iio/sysfs.h> |
19 | #include <linux/iio/buffer.h> |
20 | #include <linux/iio/triggered_buffer.h> |
21 | #include <linux/iio/trigger_consumer.h> |
22 | #include "ms5611.h" |
23 | |
24 | #define MS5611_INIT_OSR(_cmd, _conv_usec, _rate) \ |
25 | { .cmd = _cmd, .conv_usec = _conv_usec, .rate = _rate } |
26 | |
27 | static const struct ms5611_osr ms5611_avail_pressure_osr[] = { |
28 | MS5611_INIT_OSR(0x40, 600, 256), |
29 | MS5611_INIT_OSR(0x42, 1170, 512), |
30 | MS5611_INIT_OSR(0x44, 2280, 1024), |
31 | MS5611_INIT_OSR(0x46, 4540, 2048), |
32 | MS5611_INIT_OSR(0x48, 9040, 4096) |
33 | }; |
34 | |
35 | static const struct ms5611_osr ms5611_avail_temp_osr[] = { |
36 | MS5611_INIT_OSR(0x50, 600, 256), |
37 | MS5611_INIT_OSR(0x52, 1170, 512), |
38 | MS5611_INIT_OSR(0x54, 2280, 1024), |
39 | MS5611_INIT_OSR(0x56, 4540, 2048), |
40 | MS5611_INIT_OSR(0x58, 9040, 4096) |
41 | }; |
42 | |
43 | static const char ms5611_show_osr[] = "256 512 1024 2048 4096" ; |
44 | |
45 | static IIO_CONST_ATTR(oversampling_ratio_available, ms5611_show_osr); |
46 | |
47 | static struct attribute *ms5611_attributes[] = { |
48 | &iio_const_attr_oversampling_ratio_available.dev_attr.attr, |
49 | NULL, |
50 | }; |
51 | |
52 | static const struct attribute_group ms5611_attribute_group = { |
53 | .attrs = ms5611_attributes, |
54 | }; |
55 | |
56 | static bool ms5611_prom_is_valid(u16 *prom, size_t len) |
57 | { |
58 | int i, j; |
59 | uint16_t crc = 0, crc_orig = prom[7] & 0x000F; |
60 | |
61 | prom[7] &= 0xFF00; |
62 | |
63 | for (i = 0; i < len * 2; i++) { |
64 | if (i % 2 == 1) |
65 | crc ^= prom[i >> 1] & 0x00FF; |
66 | else |
67 | crc ^= prom[i >> 1] >> 8; |
68 | |
69 | for (j = 0; j < 8; j++) { |
70 | if (crc & 0x8000) |
71 | crc = (crc << 1) ^ 0x3000; |
72 | else |
73 | crc <<= 1; |
74 | } |
75 | } |
76 | |
77 | crc = (crc >> 12) & 0x000F; |
78 | |
79 | return crc == crc_orig; |
80 | } |
81 | |
82 | static int ms5611_read_prom(struct iio_dev *indio_dev) |
83 | { |
84 | int ret, i; |
85 | struct ms5611_state *st = iio_priv(indio_dev); |
86 | |
87 | for (i = 0; i < MS5611_PROM_WORDS_NB; i++) { |
88 | ret = st->read_prom_word(st, i, &st->prom[i]); |
89 | if (ret < 0) { |
90 | dev_err(&indio_dev->dev, |
91 | "failed to read prom at %d\n" , i); |
92 | return ret; |
93 | } |
94 | } |
95 | |
96 | if (!ms5611_prom_is_valid(prom: st->prom, MS5611_PROM_WORDS_NB)) { |
97 | dev_err(&indio_dev->dev, "PROM integrity check failed\n" ); |
98 | return -ENODEV; |
99 | } |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | static int ms5611_read_temp_and_pressure(struct iio_dev *indio_dev, |
105 | s32 *temp, s32 *pressure) |
106 | { |
107 | int ret; |
108 | struct ms5611_state *st = iio_priv(indio_dev); |
109 | |
110 | ret = st->read_adc_temp_and_pressure(st, temp, pressure); |
111 | if (ret < 0) { |
112 | dev_err(&indio_dev->dev, |
113 | "failed to read temperature and pressure\n" ); |
114 | return ret; |
115 | } |
116 | |
117 | return st->compensate_temp_and_pressure(st, temp, pressure); |
118 | } |
119 | |
120 | static int ms5611_temp_and_pressure_compensate(struct ms5611_state *st, |
121 | s32 *temp, s32 *pressure) |
122 | { |
123 | s32 t = *temp, p = *pressure; |
124 | s64 off, sens, dt; |
125 | |
126 | dt = t - (st->prom[5] << 8); |
127 | off = ((s64)st->prom[2] << 16) + ((st->prom[4] * dt) >> 7); |
128 | sens = ((s64)st->prom[1] << 15) + ((st->prom[3] * dt) >> 8); |
129 | |
130 | t = 2000 + ((st->prom[6] * dt) >> 23); |
131 | if (t < 2000) { |
132 | s64 off2, sens2, t2; |
133 | |
134 | t2 = (dt * dt) >> 31; |
135 | off2 = (5 * (t - 2000) * (t - 2000)) >> 1; |
136 | sens2 = off2 >> 1; |
137 | |
138 | if (t < -1500) { |
139 | s64 tmp = (t + 1500) * (t + 1500); |
140 | |
141 | off2 += 7 * tmp; |
142 | sens2 += (11 * tmp) >> 1; |
143 | } |
144 | |
145 | t -= t2; |
146 | off -= off2; |
147 | sens -= sens2; |
148 | } |
149 | |
150 | *temp = t; |
151 | *pressure = (((p * sens) >> 21) - off) >> 15; |
152 | |
153 | return 0; |
154 | } |
155 | |
156 | static int ms5607_temp_and_pressure_compensate(struct ms5611_state *st, |
157 | s32 *temp, s32 *pressure) |
158 | { |
159 | s32 t = *temp, p = *pressure; |
160 | s64 off, sens, dt; |
161 | |
162 | dt = t - (st->prom[5] << 8); |
163 | off = ((s64)st->prom[2] << 17) + ((st->prom[4] * dt) >> 6); |
164 | sens = ((s64)st->prom[1] << 16) + ((st->prom[3] * dt) >> 7); |
165 | |
166 | t = 2000 + ((st->prom[6] * dt) >> 23); |
167 | if (t < 2000) { |
168 | s64 off2, sens2, t2, tmp; |
169 | |
170 | t2 = (dt * dt) >> 31; |
171 | tmp = (t - 2000) * (t - 2000); |
172 | off2 = (61 * tmp) >> 4; |
173 | sens2 = tmp << 1; |
174 | |
175 | if (t < -1500) { |
176 | tmp = (t + 1500) * (t + 1500); |
177 | off2 += 15 * tmp; |
178 | sens2 += 8 * tmp; |
179 | } |
180 | |
181 | t -= t2; |
182 | off -= off2; |
183 | sens -= sens2; |
184 | } |
185 | |
186 | *temp = t; |
187 | *pressure = (((p * sens) >> 21) - off) >> 15; |
188 | |
189 | return 0; |
190 | } |
191 | |
192 | static int ms5611_reset(struct iio_dev *indio_dev) |
193 | { |
194 | int ret; |
195 | struct ms5611_state *st = iio_priv(indio_dev); |
196 | |
197 | ret = st->reset(st); |
198 | if (ret < 0) { |
199 | dev_err(&indio_dev->dev, "failed to reset device\n" ); |
200 | return ret; |
201 | } |
202 | |
203 | usleep_range(min: 3000, max: 4000); |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static irqreturn_t ms5611_trigger_handler(int irq, void *p) |
209 | { |
210 | struct iio_poll_func *pf = p; |
211 | struct iio_dev *indio_dev = pf->indio_dev; |
212 | struct ms5611_state *st = iio_priv(indio_dev); |
213 | /* Ensure buffer elements are naturally aligned */ |
214 | struct { |
215 | s32 channels[2]; |
216 | s64 ts __aligned(8); |
217 | } scan; |
218 | int ret; |
219 | |
220 | mutex_lock(&st->lock); |
221 | ret = ms5611_read_temp_and_pressure(indio_dev, temp: &scan.channels[1], |
222 | pressure: &scan.channels[0]); |
223 | mutex_unlock(lock: &st->lock); |
224 | if (ret < 0) |
225 | goto err; |
226 | |
227 | iio_push_to_buffers_with_timestamp(indio_dev, data: &scan, |
228 | timestamp: iio_get_time_ns(indio_dev)); |
229 | |
230 | err: |
231 | iio_trigger_notify_done(trig: indio_dev->trig); |
232 | |
233 | return IRQ_HANDLED; |
234 | } |
235 | |
236 | static int ms5611_read_raw(struct iio_dev *indio_dev, |
237 | struct iio_chan_spec const *chan, |
238 | int *val, int *val2, long mask) |
239 | { |
240 | int ret; |
241 | s32 temp, pressure; |
242 | struct ms5611_state *st = iio_priv(indio_dev); |
243 | |
244 | switch (mask) { |
245 | case IIO_CHAN_INFO_PROCESSED: |
246 | mutex_lock(&st->lock); |
247 | ret = ms5611_read_temp_and_pressure(indio_dev, |
248 | temp: &temp, pressure: &pressure); |
249 | mutex_unlock(lock: &st->lock); |
250 | if (ret < 0) |
251 | return ret; |
252 | |
253 | switch (chan->type) { |
254 | case IIO_TEMP: |
255 | *val = temp * 10; |
256 | return IIO_VAL_INT; |
257 | case IIO_PRESSURE: |
258 | *val = pressure / 1000; |
259 | *val2 = (pressure % 1000) * 1000; |
260 | return IIO_VAL_INT_PLUS_MICRO; |
261 | default: |
262 | return -EINVAL; |
263 | } |
264 | case IIO_CHAN_INFO_SCALE: |
265 | switch (chan->type) { |
266 | case IIO_TEMP: |
267 | *val = 10; |
268 | return IIO_VAL_INT; |
269 | case IIO_PRESSURE: |
270 | *val = 0; |
271 | *val2 = 1000; |
272 | return IIO_VAL_INT_PLUS_MICRO; |
273 | default: |
274 | return -EINVAL; |
275 | } |
276 | case IIO_CHAN_INFO_OVERSAMPLING_RATIO: |
277 | if (chan->type != IIO_TEMP && chan->type != IIO_PRESSURE) |
278 | break; |
279 | mutex_lock(&st->lock); |
280 | if (chan->type == IIO_TEMP) |
281 | *val = (int)st->temp_osr->rate; |
282 | else |
283 | *val = (int)st->pressure_osr->rate; |
284 | mutex_unlock(lock: &st->lock); |
285 | return IIO_VAL_INT; |
286 | } |
287 | |
288 | return -EINVAL; |
289 | } |
290 | |
291 | static const struct ms5611_osr *ms5611_find_osr(int rate, |
292 | const struct ms5611_osr *osr, |
293 | size_t count) |
294 | { |
295 | unsigned int r; |
296 | |
297 | for (r = 0; r < count; r++) |
298 | if ((unsigned short)rate == osr[r].rate) |
299 | break; |
300 | if (r >= count) |
301 | return NULL; |
302 | return &osr[r]; |
303 | } |
304 | |
305 | static int ms5611_write_raw(struct iio_dev *indio_dev, |
306 | struct iio_chan_spec const *chan, |
307 | int val, int val2, long mask) |
308 | { |
309 | struct ms5611_state *st = iio_priv(indio_dev); |
310 | const struct ms5611_osr *osr = NULL; |
311 | int ret; |
312 | |
313 | if (mask != IIO_CHAN_INFO_OVERSAMPLING_RATIO) |
314 | return -EINVAL; |
315 | |
316 | if (chan->type == IIO_TEMP) |
317 | osr = ms5611_find_osr(rate: val, osr: ms5611_avail_temp_osr, |
318 | ARRAY_SIZE(ms5611_avail_temp_osr)); |
319 | else if (chan->type == IIO_PRESSURE) |
320 | osr = ms5611_find_osr(rate: val, osr: ms5611_avail_pressure_osr, |
321 | ARRAY_SIZE(ms5611_avail_pressure_osr)); |
322 | if (!osr) |
323 | return -EINVAL; |
324 | |
325 | ret = iio_device_claim_direct_mode(indio_dev); |
326 | if (ret) |
327 | return ret; |
328 | |
329 | mutex_lock(&st->lock); |
330 | |
331 | if (chan->type == IIO_TEMP) |
332 | st->temp_osr = osr; |
333 | else |
334 | st->pressure_osr = osr; |
335 | |
336 | mutex_unlock(lock: &st->lock); |
337 | iio_device_release_direct_mode(indio_dev); |
338 | |
339 | return 0; |
340 | } |
341 | |
342 | static const unsigned long ms5611_scan_masks[] = {0x3, 0}; |
343 | |
344 | static const struct iio_chan_spec ms5611_channels[] = { |
345 | { |
346 | .type = IIO_PRESSURE, |
347 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | |
348 | BIT(IIO_CHAN_INFO_SCALE) | |
349 | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), |
350 | .scan_index = 0, |
351 | .scan_type = { |
352 | .sign = 's', |
353 | .realbits = 32, |
354 | .storagebits = 32, |
355 | .endianness = IIO_CPU, |
356 | }, |
357 | }, |
358 | { |
359 | .type = IIO_TEMP, |
360 | .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED) | |
361 | BIT(IIO_CHAN_INFO_SCALE) | |
362 | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), |
363 | .scan_index = 1, |
364 | .scan_type = { |
365 | .sign = 's', |
366 | .realbits = 32, |
367 | .storagebits = 32, |
368 | .endianness = IIO_CPU, |
369 | }, |
370 | }, |
371 | IIO_CHAN_SOFT_TIMESTAMP(2), |
372 | }; |
373 | |
374 | static const struct iio_info ms5611_info = { |
375 | .read_raw = &ms5611_read_raw, |
376 | .write_raw = &ms5611_write_raw, |
377 | .attrs = &ms5611_attribute_group, |
378 | }; |
379 | |
380 | static int ms5611_init(struct iio_dev *indio_dev) |
381 | { |
382 | int ret; |
383 | |
384 | /* Enable attached regulator if any. */ |
385 | ret = devm_regulator_get_enable(dev: indio_dev->dev.parent, id: "vdd" ); |
386 | if (ret) |
387 | return ret; |
388 | |
389 | ret = ms5611_reset(indio_dev); |
390 | if (ret < 0) |
391 | return ret; |
392 | |
393 | ret = ms5611_read_prom(indio_dev); |
394 | if (ret < 0) |
395 | return ret; |
396 | |
397 | return 0; |
398 | } |
399 | |
400 | int ms5611_probe(struct iio_dev *indio_dev, struct device *dev, |
401 | const char *name, int type) |
402 | { |
403 | int ret; |
404 | struct ms5611_state *st = iio_priv(indio_dev); |
405 | |
406 | mutex_init(&st->lock); |
407 | |
408 | switch (type) { |
409 | case MS5611: |
410 | st->compensate_temp_and_pressure = |
411 | ms5611_temp_and_pressure_compensate; |
412 | break; |
413 | case MS5607: |
414 | st->compensate_temp_and_pressure = |
415 | ms5607_temp_and_pressure_compensate; |
416 | break; |
417 | default: |
418 | return -EINVAL; |
419 | } |
420 | |
421 | st->temp_osr = |
422 | &ms5611_avail_temp_osr[ARRAY_SIZE(ms5611_avail_temp_osr) - 1]; |
423 | st->pressure_osr = |
424 | &ms5611_avail_pressure_osr[ARRAY_SIZE(ms5611_avail_pressure_osr) |
425 | - 1]; |
426 | indio_dev->name = name; |
427 | indio_dev->info = &ms5611_info; |
428 | indio_dev->channels = ms5611_channels; |
429 | indio_dev->num_channels = ARRAY_SIZE(ms5611_channels); |
430 | indio_dev->modes = INDIO_DIRECT_MODE; |
431 | indio_dev->available_scan_masks = ms5611_scan_masks; |
432 | |
433 | ret = ms5611_init(indio_dev); |
434 | if (ret < 0) |
435 | return ret; |
436 | |
437 | ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL, |
438 | ms5611_trigger_handler, NULL); |
439 | if (ret < 0) { |
440 | dev_err(dev, "iio triggered buffer setup failed\n" ); |
441 | return ret; |
442 | } |
443 | |
444 | ret = devm_iio_device_register(dev, indio_dev); |
445 | if (ret < 0) { |
446 | dev_err(dev, "unable to register iio device\n" ); |
447 | return ret; |
448 | } |
449 | |
450 | return 0; |
451 | } |
452 | EXPORT_SYMBOL_NS(ms5611_probe, IIO_MS5611); |
453 | |
454 | MODULE_AUTHOR("Tomasz Duszynski <tduszyns@gmail.com>" ); |
455 | MODULE_DESCRIPTION("MS5611 core driver" ); |
456 | MODULE_LICENSE("GPL v2" ); |
457 | |