xref: /minix/minix/drivers/sensors/bmp085/bmp085.c (revision 0a6a1f1d)
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 && (uint16_t)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 /* Entry points to this driver from libchardriver. */
159 static struct chardriver bmp085_tab = {
160 	.cdr_read	= bmp085_read,
161 	.cdr_other	= bmp085_other
162 };
163 
164 /*
165  * Initialize the driver. Checks the CHIPID against a known value and
166  * reads the calibration coefficients.
167  *
168  * The chip does have a soft reset register (0xe0), but there
169  * doesn't appear to be any documentation or example usage for it.
170  */
171 static int
172 bmp085_init(void)
173 {
174 	int r;
175 	int32_t t, p;
176 
177 	r = version_check();
178 	if (r != OK) {
179 		return EXIT_FAILURE;
180 	}
181 
182 	r = read_cal_coef();
183 	if (r != OK) {
184 		return EXIT_FAILURE;
185 	}
186 
187 	return OK;
188 }
189 
190 static int
191 version_check(void)
192 {
193 	int r;
194 	uint8_t chipid;
195 
196 	r = i2creg_read8(bus_endpoint, address, CHIPID_REG, &chipid);
197 	if (r != OK) {
198 		log_warn(&log, "Couldn't read CHIPID\n");
199 		return -1;
200 	}
201 
202 	if (chipid != BMP085_CHIPID) {
203 		log_warn(&log, "Bad CHIPID\n");
204 		return -1;
205 	}
206 
207 	log_debug(&log, "CHIPID OK\n");
208 
209 	return OK;
210 }
211 
212 /*
213  * Read the calibration data from the chip. Each individual chip has a unique
214  * set of calibration parameters that get used to compute the true temperature
215  * and pressure.
216  */
217 static int
218 read_cal_coef(void)
219 {
220 	int r;
221 
222 	/* Populate the calibration struct with values */
223 	r = i2creg_read16(bus_endpoint, address, AC1_MSB_REG, &cal.ac1);
224 	if (r != OK) {
225 		return -1;
226 	}
227 	log_debug(&log, "cal.ac1 = %d\n", cal.ac1);
228 
229 	r = i2creg_read16(bus_endpoint, address, AC2_MSB_REG, &cal.ac2);
230 	if (r != OK) {
231 		return -1;
232 	}
233 	log_debug(&log, "cal.ac2 = %d\n", cal.ac2);
234 
235 	r = i2creg_read16(bus_endpoint, address, AC3_MSB_REG, &cal.ac3);
236 	if (r != OK) {
237 		return -1;
238 	}
239 	log_debug(&log, "cal.ac3 = %d\n", cal.ac3);
240 
241 	r = i2creg_read16(bus_endpoint, address, AC4_MSB_REG, &cal.ac4);
242 	if (r != OK) {
243 		return -1;
244 	}
245 	log_debug(&log, "cal.ac4 = %u\n", cal.ac4);
246 
247 	r = i2creg_read16(bus_endpoint, address, AC5_MSB_REG, &cal.ac5);
248 	if (r != OK) {
249 		return -1;
250 	}
251 	log_debug(&log, "cal.ac5 = %u\n", cal.ac5);
252 
253 	r = i2creg_read16(bus_endpoint, address, AC6_MSB_REG, &cal.ac6);
254 	if (r != OK) {
255 		return -1;
256 	}
257 	log_debug(&log, "cal.ac6 = %u\n", cal.ac6);
258 
259 	r = i2creg_read16(bus_endpoint, address, B1_MSB_REG, &cal.b1);
260 	if (r != OK) {
261 		return -1;
262 	}
263 	log_debug(&log, "cal.b1 = %d\n", cal.b1);
264 
265 	r = i2creg_read16(bus_endpoint, address, B2_MSB_REG, &cal.b2);
266 	if (r != OK) {
267 		return -1;
268 	}
269 	log_debug(&log, "cal.b2 = %d\n", cal.b2);
270 
271 	r = i2creg_read16(bus_endpoint, address, MB_MSB_REG, &cal.mb);
272 	if (r != OK) {
273 		return -1;
274 	}
275 	log_debug(&log, "cal.mb = %d\n", cal.mb);
276 
277 	r = i2creg_read16(bus_endpoint, address, MC_MSB_REG, &cal.mc);
278 	if (r != OK) {
279 		return -1;
280 	}
281 	log_debug(&log, "cal.mc = %d\n", cal.mc);
282 
283 	r = i2creg_read16(bus_endpoint, address, MD_MSB_REG, &cal.md);
284 	if (r != OK) {
285 		return -1;
286 	}
287 	log_debug(&log, "cal.md = %d\n", cal.md);
288 
289 	/* Validate. Data sheet says values should not be 0x0000 nor 0xffff */
290 	if (!CAL_COEF_IS_VALID(cal.ac1) ||
291 	    !CAL_COEF_IS_VALID(cal.ac2) ||
292 	    !CAL_COEF_IS_VALID(cal.ac3) ||
293 	    !CAL_COEF_IS_VALID(cal.ac4) ||
294 	    !CAL_COEF_IS_VALID(cal.ac5) ||
295 	    !CAL_COEF_IS_VALID(cal.ac6) ||
296 	    !CAL_COEF_IS_VALID(cal.b1) ||
297 	    !CAL_COEF_IS_VALID(cal.b2) ||
298 	    !CAL_COEF_IS_VALID(cal.mb) ||
299 	    !CAL_COEF_IS_VALID(cal.mc) || !CAL_COEF_IS_VALID(cal.md)) {
300 
301 		log_warn(&log, "Invalid calibration data found on chip.\n");
302 		return -1;
303 	}
304 
305 	log_debug(&log, "Read Cal Data OK\n");
306 
307 	return OK;
308 }
309 
310 /*
311  * Measure the uncompensated temperature and uncompensated pressure from the
312  * chip and apply the formulas to determine the true temperature and pressure.
313  * Note, the data sheet is light on the details when it comes to defining the
314  * meaning of each variable, so this function has a lot of cryptic names in it.
315  */
316 static int
317 measure(int32_t * temperature, int32_t * pressure)
318 {
319 	int r;
320 
321 	/* Types are given in the datasheet. Their long translates to 32-bits */
322 
323 	int16_t ut;		/* uncompensated temperature */
324 	int32_t up;		/* uncompensated pressure */
325 	int32_t x1;
326 	int32_t x2;
327 	int32_t x3;
328 	int32_t b3;
329 	uint32_t b4;
330 	int32_t b5;
331 	int32_t b6;
332 	uint32_t b7;
333 	int32_t t;		/* true temperature (in 0.1C) */
334 	int32_t p;		/* true pressure (in Pa) */
335 
336 	log_debug(&log, "Triggering Temp Reading...\n");
337 
338 	/* trigger temperature reading */
339 	r = i2creg_write8(bus_endpoint, address, CTRL_REG, CMD_TRIG_T);
340 	if (r != OK) {
341 		log_warn(&log, "Failed to trigger temperature reading.\n");
342 		return -1;
343 	}
344 
345 	/* wait for sampling to be completed. */
346 	micro_delay(UDELAY_T);
347 
348 	/* read the uncompensated temperature */
349 	r = i2creg_read16(bus_endpoint, address, SENSOR_VAL_MSB_REG, &ut);
350 	if (r != OK) {
351 		log_warn(&log, "Failed to read temperature.\n");
352 		return -1;
353 	}
354 
355 	log_debug(&log, "ut = %d\n", ut);
356 
357 	log_debug(&log, "Triggering Pressure Reading...\n");
358 
359 	/* trigger pressure reading */
360 	r = i2creg_write8(bus_endpoint, address, CTRL_REG, p_cmd->cmd);
361 	if (r != OK) {
362 		log_warn(&log, "Failed to trigger pressure reading.\n");
363 		return -1;
364 	}
365 
366 	/* wait for sampling to be completed. */
367 	micro_delay(p_cmd->udelay);
368 
369 	/* read the uncompensated pressure */
370 	r = i2creg_read24(bus_endpoint, address, SENSOR_VAL_MSB_REG, &up);
371 	if (r != OK) {
372 		log_warn(&log, "Failed to read pressure.\n");
373 		return -1;
374 	}
375 
376 	/* shift by 8 - oversampling setting */
377 	up = (up >> (8 - p_cmd->mode));
378 
379 	log_debug(&log, "up = %d\n", up);
380 
381 	/* convert uncompensated temperature to true temperature */
382 	x1 = ((ut - cal.ac6) * cal.ac5) / (1 << 15);
383 	x2 = (cal.mc * (1 << 11)) / (x1 + cal.md);
384 	b5 = x1 + x2;
385 	t = (b5 + 8) / (1 << 4);
386 
387 	/* save the result */
388 	*temperature = t;
389 
390 	log_debug(&log, "t = %d\n", t);
391 
392 	/* Convert uncompensated pressure to true pressure.
393 	 * This is really how the data sheet suggests doing it.
394 	 * There is no alternative approach suggested. Other open
395 	 * source drivers I've found use this method.
396 	 */
397 	b6 = b5 - 4000;
398 	x1 = ((cal.b2 * ((b6 * b6) >> 12)) >> 11);
399 	x2 = ((cal.ac2 * b6) >> 11);
400 	x3 = x1 + x2;
401 	b3 = (((((cal.ac1 * 4) + x3) << p_cmd->mode) + 2) >> 2);
402 	x1 = ((cal.ac3 * b6) >> 13);
403 	x2 = ((cal.b1 * ((b6 * b6) >> 12)) >> 16);
404 	x3 = (((x1 + x2) + 2) >> 2);
405 	b4 = ((cal.ac4 * ((uint32_t) (x3 + 32768))) >> 15);
406 	b7 = ((uint32_t) up - b3) * (50000 >> p_cmd->mode);
407 	p = (b7 < 0x80000000) ? (b7 * 2) / b4 : (b7 / b4) * 2;
408 	x1 = (p >> 8) * (p >> 8);
409 	x1 = ((x1 * 3038) >> 16);
410 	x2 = ((-7357 * p) >> 16);
411 	p = p + ((x1 + x2 + 3791) >> 4);
412 
413 	*pressure = p;
414 
415 	log_debug(&log, "p = %d\n", p);
416 
417 	return OK;
418 }
419 
420 static ssize_t
421 bmp085_read(devminor_t UNUSED(minor), u64_t position, endpoint_t endpt,
422     cp_grant_id_t grant, size_t size, int UNUSED(flags), cdev_id_t UNUSED(id))
423 {
424 	u64_t dev_size;
425 	int r;
426 	uint32_t temperature, pressure;
427 
428 	r = measure(&temperature, &pressure);
429 	if (r != OK) {
430 		return EIO;
431 	}
432 
433 	memset(buffer, '\0', BUFFER_LEN + 1);
434 	snprintf(buffer, BUFFER_LEN, "%-16s: %d.%01d\n%-16s: %d\n",
435 	    "TEMPERATURE", temperature / 10, temperature % 10, "PRESSURE",
436 	    pressure);
437 
438 	log_trace(&log, "%s", buffer);
439 
440 	dev_size = (u64_t)strlen(buffer);
441 	if (position >= dev_size) return 0;
442 	if (position + size > dev_size)
443 		size = (size_t)(dev_size - position);
444 
445 	r = sys_safecopyto(endpt, grant, 0,
446 	    (vir_bytes)(buffer + (size_t)position), size);
447 
448 	return (r != OK) ? r : size;
449 }
450 
451 static void
452 bmp085_other(message * m, int ipc_status)
453 {
454 	int r;
455 
456 	if (is_ipc_notify(ipc_status)) {
457 		if (m->m_source == DS_PROC_NR) {
458 			log_debug(&log,
459 			    "bus driver changed state, update endpoint\n");
460 			i2cdriver_handle_bus_update(&bus_endpoint, bus,
461 			    address);
462 		}
463 		return;
464 	}
465 
466 	log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
467 }
468 
469 static int
470 sef_cb_lu_state_save(int UNUSED(result), int UNUSED(flags))
471 {
472 	ds_publish_u32("bus", bus, DSF_OVERWRITE);
473 	ds_publish_u32("address", address, DSF_OVERWRITE);
474 	return OK;
475 }
476 
477 static int
478 lu_state_restore(void)
479 {
480 	/* Restore the state. */
481 	u32_t value;
482 
483 	ds_retrieve_u32("bus", &value);
484 	ds_delete_u32("bus");
485 	bus = (int) value;
486 
487 	ds_retrieve_u32("address", &value);
488 	ds_delete_u32("address");
489 	address = (int) value;
490 
491 	return OK;
492 }
493 
494 static int
495 sef_cb_init(int type, sef_init_info_t * UNUSED(info))
496 {
497 	int r;
498 
499 	if (type == SEF_INIT_LU) {
500 		/* Restore the state. */
501 		lu_state_restore();
502 	}
503 
504 	/* look-up the endpoint for the bus driver */
505 	bus_endpoint = i2cdriver_bus_endpoint(bus);
506 	if (bus_endpoint == 0) {
507 		log_warn(&log, "Couldn't find bus driver.\n");
508 		return EXIT_FAILURE;
509 	}
510 
511 	/* claim the device */
512 	r = i2cdriver_reserve_device(bus_endpoint, address);
513 	if (r != OK) {
514 		log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
515 		    address, r);
516 		return EXIT_FAILURE;
517 	}
518 
519 	r = bmp085_init();
520 	if (r != OK) {
521 		log_warn(&log, "Couldn't initialize device\n");
522 		return EXIT_FAILURE;
523 	}
524 
525 	if (type != SEF_INIT_LU) {
526 
527 		/* sign up for updates about the i2c bus going down/up */
528 		r = i2cdriver_subscribe_bus_updates(bus);
529 		if (r != OK) {
530 			log_warn(&log, "Couldn't subscribe to bus updates\n");
531 			return EXIT_FAILURE;
532 		}
533 
534 		i2cdriver_announce(bus);
535 		log_debug(&log, "announced\n");
536 	}
537 
538 	return OK;
539 }
540 
541 static void
542 sef_local_startup(void)
543 {
544 	/*
545 	 * Register init callbacks. Use the same function for all event types
546 	 */
547 	sef_setcb_init_fresh(sef_cb_init);
548 	sef_setcb_init_lu(sef_cb_init);
549 	sef_setcb_init_restart(sef_cb_init);
550 
551 	/*
552 	 * Register live update callbacks.
553 	 */
554 	sef_setcb_lu_state_save(sef_cb_lu_state_save);
555 
556 	/* Let SEF perform startup. */
557 	sef_startup();
558 }
559 
560 int
561 main(int argc, char *argv[])
562 {
563 	int r;
564 
565 	env_setargs(argc, argv);
566 
567 	r = i2cdriver_env_parse(&bus, &address, valid_addrs);
568 	if (r < 0) {
569 		log_warn(&log, "Expecting -args 'bus=X address=0x77'\n");
570 		log_warn(&log, "Example -args 'bus=1 address=0x77'\n");
571 		return EXIT_FAILURE;
572 	} else if (r > 0) {
573 		log_warn(&log,
574 		    "Invalid slave address for device, expecting 0x77\n");
575 		return EXIT_FAILURE;
576 	}
577 
578 	sef_local_startup();
579 
580 	chardriver_task(&bmp085_tab);
581 
582 	return 0;
583 }
584