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