xref: /minix/minix/drivers/sensors/bmp085/bmp085.c (revision 83133719)
1 /* Driver for the BMP085 Preassure and Temperature Sensor */
2 
3 #include <minix/ds.h>
4 #include <minix/drivers.h>
5 #include <minix/i2c.h>
6 #include <minix/i2cdriver.h>
7 #include <minix/chardriver.h>
8 #include <minix/log.h>
9 
10 /* Control Register for triggering a measurement */
11 #define CTRL_REG 0xf4
12 
13 /* temperature sensor - it only has one 'mode' - conversion time 4.5 ms */
14 #define CMD_TRIG_T 0x2e
15 #define UDELAY_T (4500)
16 
17 /* pressure sensor - ultra low power mode - conversion time 4.5 ms */
18 #define CMD_TRIG_P_ULP 0x34
19 #define MODE_ULP 0x00
20 #define UDELAY_ULP (4500)
21 
22 /* pressure sensor - standard mode - conversion time 7.5 ms */
23 #define CMD_TRIG_P_STD 0x74
24 #define MODE_STD 0x01
25 #define UDELAY_STD (7500)
26 
27 /* pressure sensor - high resolution mode - conversion time 13.5 ms */
28 #define CMD_TRIG_P_HR 0xb4
29 #define MODE_HR 0x02
30 #define UDELAY_HR (13500)
31 
32 /* pressure sensor - ultra high resolution mode - conversion time 25.5 ms */
33 #define CMD_TRIG_P_UHR 0xf4
34 #define MODE_UHR 0x03
35 #define UDELAY_UHR (25500)
36 
37 /* Values for the different modes of operation */
38 struct pressure_cmd
39 {
40 	uint8_t cmd;
41 	uint8_t mode;
42 	uint16_t udelay;
43 };
44 
45 /* Table of available modes and their parameters. */
46 static struct pressure_cmd pressure_cmds[4] = {
47 	{CMD_TRIG_P_ULP, MODE_ULP, UDELAY_ULP},
48 	{CMD_TRIG_P_STD, MODE_STD, UDELAY_STD},
49 	{CMD_TRIG_P_HR, MODE_HR, UDELAY_HR},
50 	{CMD_TRIG_P_UHR, MODE_UHR, UDELAY_UHR}
51 };
52 
53 /* Default to standard mode.
54  * There isn't code to configure the resolution at runtime, but it should
55  * easy to implement by setting p_cmd to the right element of pressure_cmds.
56  */
57 static struct pressure_cmd *p_cmd = &pressure_cmds[MODE_STD];
58 
59 /* Chip Identification */
60 #define CHIPID_REG 0xd0
61 #define BMP085_CHIPID 0x55
62 
63 /*
64  * There is also a version register at 0xd1, but documentation seems to be
65  * lacking. The sample code says high 4 bytes are AL version and low 4 are ML.
66  */
67 
68 /* Calibration coefficients
69  *
70  * These are unique to each chip and must be read when starting the driver.
71  * Validate them by checking that none are 0x0000 nor 0xffff. Types and
72  * names are from the datasheet.
73  */
74 struct calibration
75 {
76 	int16_t ac1;
77 	int16_t ac2;
78 	int16_t ac3;
79 	uint16_t ac4;
80 	uint16_t ac5;
81 	uint16_t ac6;
82 	int16_t b1;
83 	int16_t b2;
84 	int16_t mb;
85 	int16_t mc;
86 	int16_t md;
87 } cal;
88 
89 /* Register locations for calibration coefficients */
90 #define AC1_MSB_REG 0xaa
91 #define AC1_LSB_REG 0xab
92 #define AC2_MSB_REG 0xac
93 #define AC2_LSB_REG 0xad
94 #define AC3_MSB_REG 0xae
95 #define AC3_LSB_REG 0xaf
96 #define AC4_MSB_REG 0xb0
97 #define AC4_LSB_REG 0xb1
98 #define AC5_MSB_REG 0xb2
99 #define AC5_LSB_REG 0xb3
100 #define AC6_MSB_REG 0xb4
101 #define AC6_LSB_REG 0xb5
102 #define B1_MSB_REG 0xb6
103 #define B1_LSB_REG 0xb7
104 #define B2_MSB_REG 0xb8
105 #define B2_LSB_REG 0xb9
106 #define MB_MSB_REG 0xba
107 #define MB_LSB_REG 0xbb
108 #define MC_MSB_REG 0xbc
109 #define MC_LSB_REG 0xbd
110 #define MD_MSB_REG 0xbe
111 #define MD_LSB_REG 0xbf
112 
113 #define CAL_COEF_FIRST AC1_MSB_REG
114 #define CAL_COEF_LAST MD_LSB_REG
115 
116 #define CAL_COEF_IS_VALID(x) (x != 0x0000 && x != 0xffff)
117 
118 #define SENSOR_VAL_MSB_REG 0xf6
119 #define SENSOR_VAL_LSB_REG 0xf7
120 #define SENSOR_VAL_XLSB_REG 0xf8
121 
122 /* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
123 static struct log log = {
124 	.name = "bmp085",
125 	.log_level = LEVEL_INFO,
126 	.log_func = default_log
127 };
128 
129 /* Only one valid slave address. It isn't configurable. */
130 static i2c_addr_t valid_addrs[5] = {
131 	0x77, 0x00
132 };
133 
134 /* Buffer to store output string returned when reading from device file. */
135 #define BUFFER_LEN 64
136 char buffer[BUFFER_LEN + 1];
137 
138 /* the bus that this device is on (counting starting at 1) */
139 static uint32_t bus;
140 
141 /* slave address of the device */
142 static i2c_addr_t address;
143 
144 /* endpoint for the driver for the bus itself. */
145 static endpoint_t bus_endpoint;
146 
147 /* main device functions */
148 static int bmp085_init(void);
149 static int version_check(void);
150 static int read_cal_coef(void);
151 static int measure(int32_t * temperature, int32_t * pressure);
152 
153 /* libchardriver callbacks */
154 static ssize_t bmp085_read(devminor_t minor, u64_t position, endpoint_t endpt,
155     cp_grant_id_t grant, size_t size, int flags, cdev_id_t id);
156 static void bmp085_other(message * m, int ipc_status);
157 
158 /* SEF Function */
159 static int sef_cb_lu_state_save(int);
160 static int lu_state_restore(void);
161 static int sef_cb_init(int type, sef_init_info_t * info);
162 static void sef_local_startup(void);
163 
164 /* Entry points to this driver from libchardriver. */
165 static struct chardriver bmp085_tab = {
166 	.cdr_read	= bmp085_read,
167 	.cdr_other	= bmp085_other
168 };
169 
170 /*
171  * Initialize the driver. Checks the CHIPID against a known value and
172  * reads the calibration coefficients.
173  *
174  * The chip does have a soft reset register (0xe0), but there
175  * doesn't appear to be any documentation or example usage for it.
176  */
177 static int
178 bmp085_init(void)
179 {
180 	int r;
181 	int32_t t, p;
182 
183 	r = version_check();
184 	if (r != OK) {
185 		return EXIT_FAILURE;
186 	}
187 
188 	r = read_cal_coef();
189 	if (r != OK) {
190 		return EXIT_FAILURE;
191 	}
192 
193 	return OK;
194 }
195 
196 static int
197 version_check(void)
198 {
199 	int r;
200 	uint8_t chipid;
201 
202 	r = i2creg_read8(bus_endpoint, address, CHIPID_REG, &chipid);
203 	if (r != OK) {
204 		log_warn(&log, "Couldn't read CHIPID\n");
205 		return -1;
206 	}
207 
208 	if (chipid != BMP085_CHIPID) {
209 		log_warn(&log, "Bad CHIPID\n");
210 		return -1;
211 	}
212 
213 	log_debug(&log, "CHIPID OK\n");
214 
215 	return OK;
216 }
217 
218 /*
219  * Read the calibration data from the chip. Each individual chip has a unique
220  * set of calibration parameters that get used to compute the true temperature
221  * and pressure.
222  */
223 static int
224 read_cal_coef(void)
225 {
226 	int r;
227 
228 	/* Populate the calibration struct with values */
229 	r = i2creg_read16(bus_endpoint, address, AC1_MSB_REG, &cal.ac1);
230 	if (r != OK) {
231 		return -1;
232 	}
233 	log_debug(&log, "cal.ac1 = %d\n", cal.ac1);
234 
235 	r = i2creg_read16(bus_endpoint, address, AC2_MSB_REG, &cal.ac2);
236 	if (r != OK) {
237 		return -1;
238 	}
239 	log_debug(&log, "cal.ac2 = %d\n", cal.ac2);
240 
241 	r = i2creg_read16(bus_endpoint, address, AC3_MSB_REG, &cal.ac3);
242 	if (r != OK) {
243 		return -1;
244 	}
245 	log_debug(&log, "cal.ac3 = %d\n", cal.ac3);
246 
247 	r = i2creg_read16(bus_endpoint, address, AC4_MSB_REG, &cal.ac4);
248 	if (r != OK) {
249 		return -1;
250 	}
251 	log_debug(&log, "cal.ac4 = %u\n", cal.ac4);
252 
253 	r = i2creg_read16(bus_endpoint, address, AC5_MSB_REG, &cal.ac5);
254 	if (r != OK) {
255 		return -1;
256 	}
257 	log_debug(&log, "cal.ac5 = %u\n", cal.ac5);
258 
259 	r = i2creg_read16(bus_endpoint, address, AC6_MSB_REG, &cal.ac6);
260 	if (r != OK) {
261 		return -1;
262 	}
263 	log_debug(&log, "cal.ac6 = %u\n", cal.ac6);
264 
265 	r = i2creg_read16(bus_endpoint, address, B1_MSB_REG, &cal.b1);
266 	if (r != OK) {
267 		return -1;
268 	}
269 	log_debug(&log, "cal.b1 = %d\n", cal.b1);
270 
271 	r = i2creg_read16(bus_endpoint, address, B2_MSB_REG, &cal.b2);
272 	if (r != OK) {
273 		return -1;
274 	}
275 	log_debug(&log, "cal.b2 = %d\n", cal.b2);
276 
277 	r = i2creg_read16(bus_endpoint, address, MB_MSB_REG, &cal.mb);
278 	if (r != OK) {
279 		return -1;
280 	}
281 	log_debug(&log, "cal.mb = %d\n", cal.mb);
282 
283 	r = i2creg_read16(bus_endpoint, address, MC_MSB_REG, &cal.mc);
284 	if (r != OK) {
285 		return -1;
286 	}
287 	log_debug(&log, "cal.mc = %d\n", cal.mc);
288 
289 	r = i2creg_read16(bus_endpoint, address, MD_MSB_REG, &cal.md);
290 	if (r != OK) {
291 		return -1;
292 	}
293 	log_debug(&log, "cal.md = %d\n", cal.md);
294 
295 	/* Validate. Data sheet says values should not be 0x0000 nor 0xffff */
296 	if (!CAL_COEF_IS_VALID(cal.ac1) ||
297 	    !CAL_COEF_IS_VALID(cal.ac2) ||
298 	    !CAL_COEF_IS_VALID(cal.ac3) ||
299 	    !CAL_COEF_IS_VALID(cal.ac4) ||
300 	    !CAL_COEF_IS_VALID(cal.ac5) ||
301 	    !CAL_COEF_IS_VALID(cal.ac6) ||
302 	    !CAL_COEF_IS_VALID(cal.b1) ||
303 	    !CAL_COEF_IS_VALID(cal.b2) ||
304 	    !CAL_COEF_IS_VALID(cal.mb) ||
305 	    !CAL_COEF_IS_VALID(cal.mc) || !CAL_COEF_IS_VALID(cal.md)) {
306 
307 		log_warn(&log, "Invalid calibration data found on chip.\n");
308 		return -1;
309 	}
310 
311 	log_debug(&log, "Read Cal Data OK\n");
312 
313 	return OK;
314 }
315 
316 /*
317  * Measure the uncompensated temperature and uncompensated pressure from the
318  * chip and apply the formulas to determine the true temperature and pressure.
319  * Note, the data sheet is light on the details when it comes to defining the
320  * meaning of each variable, so this function has a lot of cryptic names in it.
321  */
322 static int
323 measure(int32_t * temperature, int32_t * pressure)
324 {
325 	int r;
326 
327 	/* Types are given in the datasheet. Their long translates to 32-bits */
328 
329 	int16_t ut;		/* uncompensated temperature */
330 	int32_t up;		/* uncompensated pressure */
331 	int32_t x1;
332 	int32_t x2;
333 	int32_t x3;
334 	int32_t b3;
335 	uint32_t b4;
336 	int32_t b5;
337 	int32_t b6;
338 	uint32_t b7;
339 	int32_t t;		/* true temperature (in 0.1C) */
340 	int32_t p;		/* true pressure (in Pa) */
341 
342 	log_debug(&log, "Triggering Temp Reading...\n");
343 
344 	/* trigger temperature reading */
345 	r = i2creg_write8(bus_endpoint, address, CTRL_REG, CMD_TRIG_T);
346 	if (r != OK) {
347 		log_warn(&log, "Failed to trigger temperature reading.\n");
348 		return -1;
349 	}
350 
351 	/* wait for sampling to be completed. */
352 	micro_delay(UDELAY_T);
353 
354 	/* read the uncompensated temperature */
355 	r = i2creg_read16(bus_endpoint, address, SENSOR_VAL_MSB_REG, &ut);
356 	if (r != OK) {
357 		log_warn(&log, "Failed to read temperature.\n");
358 		return -1;
359 	}
360 
361 	log_debug(&log, "ut = %d\n", ut);
362 
363 	log_debug(&log, "Triggering Pressure Reading...\n");
364 
365 	/* trigger pressure reading */
366 	r = i2creg_write8(bus_endpoint, address, CTRL_REG, p_cmd->cmd);
367 	if (r != OK) {
368 		log_warn(&log, "Failed to trigger pressure reading.\n");
369 		return -1;
370 	}
371 
372 	/* wait for sampling to be completed. */
373 	micro_delay(p_cmd->udelay);
374 
375 	/* read the uncompensated pressure */
376 	r = i2creg_read24(bus_endpoint, address, SENSOR_VAL_MSB_REG, &up);
377 	if (r != OK) {
378 		log_warn(&log, "Failed to read pressure.\n");
379 		return -1;
380 	}
381 
382 	/* shift by 8 - oversampling setting */
383 	up = (up >> (8 - p_cmd->mode));
384 
385 	log_debug(&log, "up = %d\n", up);
386 
387 	/* convert uncompensated temperature to true temperature */
388 	x1 = ((ut - cal.ac6) * cal.ac5) / (1 << 15);
389 	x2 = (cal.mc * (1 << 11)) / (x1 + cal.md);
390 	b5 = x1 + x2;
391 	t = (b5 + 8) / (1 << 4);
392 
393 	/* save the result */
394 	*temperature = t;
395 
396 	log_debug(&log, "t = %d\n", t);
397 
398 	/* Convert uncompensated pressure to true pressure.
399 	 * This is really how the data sheet suggests doing it.
400 	 * There is no alternative approach suggested. Other open
401 	 * source drivers I've found use this method.
402 	 */
403 	b6 = b5 - 4000;
404 	x1 = ((cal.b2 * ((b6 * b6) >> 12)) >> 11);
405 	x2 = ((cal.ac2 * b6) >> 11);
406 	x3 = x1 + x2;
407 	b3 = (((((cal.ac1 * 4) + x3) << p_cmd->mode) + 2) >> 2);
408 	x1 = ((cal.ac3 * b6) >> 13);
409 	x2 = ((cal.b1 * ((b6 * b6) >> 12)) >> 16);
410 	x3 = (((x1 + x2) + 2) >> 2);
411 	b4 = ((cal.ac4 * ((uint32_t) (x3 + 32768))) >> 15);
412 	b7 = ((uint32_t) up - b3) * (50000 >> p_cmd->mode);
413 	p = (b7 < 0x80000000) ? (b7 * 2) / b4 : (b7 / b4) * 2;
414 	x1 = (p >> 8) * (p >> 8);
415 	x1 = ((x1 * 3038) >> 16);
416 	x2 = ((-7357 * p) >> 16);
417 	p = p + ((x1 + x2 + 3791) >> 4);
418 
419 	*pressure = p;
420 
421 	log_debug(&log, "p = %d\n", p);
422 
423 	return OK;
424 }
425 
426 static ssize_t
427 bmp085_read(devminor_t UNUSED(minor), u64_t position, endpoint_t endpt,
428     cp_grant_id_t grant, size_t size, int UNUSED(flags), cdev_id_t UNUSED(id))
429 {
430 	u64_t dev_size;
431 	int r;
432 	uint32_t temperature, pressure;
433 
434 	r = measure(&temperature, &pressure);
435 	if (r != OK) {
436 		return EIO;
437 	}
438 
439 	memset(buffer, '\0', BUFFER_LEN + 1);
440 	snprintf(buffer, BUFFER_LEN, "%-16s: %d.%01d\n%-16s: %d\n",
441 	    "TEMPERATURE", temperature / 10, temperature % 10, "PRESSURE",
442 	    pressure);
443 
444 	log_trace(&log, "%s", buffer);
445 
446 	dev_size = (u64_t)strlen(buffer);
447 	if (position >= dev_size) return 0;
448 	if (position + size > dev_size)
449 		size = (size_t)(dev_size - position);
450 
451 	r = sys_safecopyto(endpt, grant, 0,
452 	    (vir_bytes)(buffer + (size_t)position), size);
453 
454 	return (r != OK) ? r : size;
455 }
456 
457 static void
458 bmp085_other(message * m, int ipc_status)
459 {
460 	int r;
461 
462 	if (is_ipc_notify(ipc_status)) {
463 		if (m->m_source == DS_PROC_NR) {
464 			log_debug(&log,
465 			    "bus driver changed state, update endpoint\n");
466 			i2cdriver_handle_bus_update(&bus_endpoint, bus,
467 			    address);
468 		}
469 		return;
470 	}
471 
472 	log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
473 }
474 
475 static int
476 sef_cb_lu_state_save(int UNUSED(state))
477 {
478 	ds_publish_u32("bus", bus, DSF_OVERWRITE);
479 	ds_publish_u32("address", address, DSF_OVERWRITE);
480 	return OK;
481 }
482 
483 static int
484 lu_state_restore(void)
485 {
486 	/* Restore the state. */
487 	u32_t value;
488 
489 	ds_retrieve_u32("bus", &value);
490 	ds_delete_u32("bus");
491 	bus = (int) value;
492 
493 	ds_retrieve_u32("address", &value);
494 	ds_delete_u32("address");
495 	address = (int) value;
496 
497 	return OK;
498 }
499 
500 static int
501 sef_cb_init(int type, sef_init_info_t * UNUSED(info))
502 {
503 	int r;
504 
505 	if (type == SEF_INIT_LU) {
506 		/* Restore the state. */
507 		lu_state_restore();
508 	}
509 
510 	/* look-up the endpoint for the bus driver */
511 	bus_endpoint = i2cdriver_bus_endpoint(bus);
512 	if (bus_endpoint == 0) {
513 		log_warn(&log, "Couldn't find bus driver.\n");
514 		return EXIT_FAILURE;
515 	}
516 
517 	/* claim the device */
518 	r = i2cdriver_reserve_device(bus_endpoint, address);
519 	if (r != OK) {
520 		log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
521 		    address, r);
522 		return EXIT_FAILURE;
523 	}
524 
525 	r = bmp085_init();
526 	if (r != OK) {
527 		log_warn(&log, "Couldn't initialize device\n");
528 		return EXIT_FAILURE;
529 	}
530 
531 	if (type != SEF_INIT_LU) {
532 
533 		/* sign up for updates about the i2c bus going down/up */
534 		r = i2cdriver_subscribe_bus_updates(bus);
535 		if (r != OK) {
536 			log_warn(&log, "Couldn't subscribe to bus updates\n");
537 			return EXIT_FAILURE;
538 		}
539 
540 		i2cdriver_announce(bus);
541 		log_debug(&log, "announced\n");
542 	}
543 
544 	return OK;
545 }
546 
547 static void
548 sef_local_startup(void)
549 {
550 	/*
551 	 * Register init callbacks. Use the same function for all event types
552 	 */
553 	sef_setcb_init_fresh(sef_cb_init);
554 	sef_setcb_init_lu(sef_cb_init);
555 	sef_setcb_init_restart(sef_cb_init);
556 
557 	/*
558 	 * Register live update callbacks.
559 	 */
560 	/* Agree to update immediately when LU is requested in a valid state. */
561 	sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
562 	/* Support live update starting from any standard state. */
563 	sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
564 	/* Register a custom routine to save the state. */
565 	sef_setcb_lu_state_save(sef_cb_lu_state_save);
566 
567 	/* Let SEF perform startup. */
568 	sef_startup();
569 }
570 
571 int
572 main(int argc, char *argv[])
573 {
574 	int r;
575 
576 	env_setargs(argc, argv);
577 
578 	r = i2cdriver_env_parse(&bus, &address, valid_addrs);
579 	if (r < 0) {
580 		log_warn(&log, "Expecting -args 'bus=X address=0x77'\n");
581 		log_warn(&log, "Example -args 'bus=1 address=0x77'\n");
582 		return EXIT_FAILURE;
583 	} else if (r > 0) {
584 		log_warn(&log,
585 		    "Invalid slave address for device, expecting 0x77\n");
586 		return EXIT_FAILURE;
587 	}
588 
589 	sef_local_startup();
590 
591 	chardriver_task(&bmp085_tab);
592 
593 	return 0;
594 }
595