custom message type for VM_INFO
[minix3.git] / drivers / bmp085 / bmp085.c
blob7782fbf25ef0e81b9c998bf5b46cd6d5bfad714a
1 /* Driver for the BMP085 Preassure and Temperature Sensor */
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>
10 /* Control Register for triggering a measurement */
11 #define CTRL_REG 0xf4
13 /* temperature sensor - it only has one 'mode' - conversion time 4.5 ms */
14 #define CMD_TRIG_T 0x2e
15 #define UDELAY_T (4500)
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)
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)
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)
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)
37 /* Values for the different modes of operation */
38 struct pressure_cmd
40 uint8_t cmd;
41 uint8_t mode;
42 uint16_t udelay;
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}
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.
57 static struct pressure_cmd *p_cmd = &pressure_cmds[MODE_STD];
59 /* Chip Identification */
60 #define CHIPID_REG 0xd0
61 #define BMP085_CHIPID 0x55
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.
68 /* Calibration coefficients
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.
74 struct calibration
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;
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
113 #define CAL_COEF_FIRST AC1_MSB_REG
114 #define CAL_COEF_LAST MD_LSB_REG
116 #define CAL_COEF_IS_VALID(x) (x != 0x0000 && x != 0xffff)
118 #define SENSOR_VAL_MSB_REG 0xf6
119 #define SENSOR_VAL_LSB_REG 0xf7
120 #define SENSOR_VAL_XLSB_REG 0xf8
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
129 /* Only one valid slave address. It isn't configurable. */
130 static i2c_addr_t valid_addrs[5] = {
131 0x77, 0x00
134 /* Buffer to store output string returned when reading from device file. */
135 #define BUFFER_LEN 64
136 char buffer[BUFFER_LEN + 1];
138 /* the bus that this device is on (counting starting at 1) */
139 static uint32_t bus;
141 /* slave address of the device */
142 static i2c_addr_t address;
144 /* endpoint for the driver for the bus itself. */
145 static endpoint_t bus_endpoint;
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);
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);
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);
164 /* Entry points to this driver from libchardriver. */
165 static struct chardriver bmp085_tab = {
166 .cdr_read = bmp085_read,
167 .cdr_other = bmp085_other
171 * Initialize the driver. Checks the CHIPID against a known value and
172 * reads the calibration coefficients.
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.
177 static int
178 bmp085_init(void)
180 int r;
181 int32_t t, p;
183 r = version_check();
184 if (r != OK) {
185 return EXIT_FAILURE;
188 r = read_cal_coef();
189 if (r != OK) {
190 return EXIT_FAILURE;
193 return OK;
196 static int
197 version_check(void)
199 int r;
200 uint8_t chipid;
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;
208 if (chipid != BMP085_CHIPID) {
209 log_warn(&log, "Bad CHIPID\n");
210 return -1;
213 log_debug(&log, "CHIPID OK\n");
215 return OK;
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.
223 static int
224 read_cal_coef(void)
226 int r;
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;
233 log_debug(&log, "cal.ac1 = %d\n", cal.ac1);
235 r = i2creg_read16(bus_endpoint, address, AC2_MSB_REG, &cal.ac2);
236 if (r != OK) {
237 return -1;
239 log_debug(&log, "cal.ac2 = %d\n", cal.ac2);
241 r = i2creg_read16(bus_endpoint, address, AC3_MSB_REG, &cal.ac3);
242 if (r != OK) {
243 return -1;
245 log_debug(&log, "cal.ac3 = %d\n", cal.ac3);
247 r = i2creg_read16(bus_endpoint, address, AC4_MSB_REG, &cal.ac4);
248 if (r != OK) {
249 return -1;
251 log_debug(&log, "cal.ac4 = %u\n", cal.ac4);
253 r = i2creg_read16(bus_endpoint, address, AC5_MSB_REG, &cal.ac5);
254 if (r != OK) {
255 return -1;
257 log_debug(&log, "cal.ac5 = %u\n", cal.ac5);
259 r = i2creg_read16(bus_endpoint, address, AC6_MSB_REG, &cal.ac6);
260 if (r != OK) {
261 return -1;
263 log_debug(&log, "cal.ac6 = %u\n", cal.ac6);
265 r = i2creg_read16(bus_endpoint, address, B1_MSB_REG, &cal.b1);
266 if (r != OK) {
267 return -1;
269 log_debug(&log, "cal.b1 = %d\n", cal.b1);
271 r = i2creg_read16(bus_endpoint, address, B2_MSB_REG, &cal.b2);
272 if (r != OK) {
273 return -1;
275 log_debug(&log, "cal.b2 = %d\n", cal.b2);
277 r = i2creg_read16(bus_endpoint, address, MB_MSB_REG, &cal.mb);
278 if (r != OK) {
279 return -1;
281 log_debug(&log, "cal.mb = %d\n", cal.mb);
283 r = i2creg_read16(bus_endpoint, address, MC_MSB_REG, &cal.mc);
284 if (r != OK) {
285 return -1;
287 log_debug(&log, "cal.mc = %d\n", cal.mc);
289 r = i2creg_read16(bus_endpoint, address, MD_MSB_REG, &cal.md);
290 if (r != OK) {
291 return -1;
293 log_debug(&log, "cal.md = %d\n", cal.md);
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)) {
307 log_warn(&log, "Invalid calibration data found on chip.\n");
308 return -1;
311 log_debug(&log, "Read Cal Data OK\n");
313 return OK;
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.
322 static int
323 measure(int32_t * temperature, int32_t * pressure)
325 int r;
327 /* Types are given in the datasheet. Their long translates to 32-bits */
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) */
342 log_debug(&log, "Triggering Temp Reading...\n");
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;
351 /* wait for sampling to be completed. */
352 micro_delay(UDELAY_T);
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;
361 log_debug(&log, "ut = %d\n", ut);
363 log_debug(&log, "Triggering Pressure Reading...\n");
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;
372 /* wait for sampling to be completed. */
373 micro_delay(p_cmd->udelay);
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;
382 /* shift by 8 - oversampling setting */
383 up = (up >> (8 - p_cmd->mode));
385 log_debug(&log, "up = %d\n", up);
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);
393 /* save the result */
394 *temperature = t;
396 log_debug(&log, "t = %d\n", t);
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.
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);
419 *pressure = p;
421 log_debug(&log, "p = %d\n", p);
423 return OK;
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))
430 u64_t dev_size;
431 int r;
432 uint32_t temperature, pressure;
434 r = measure(&temperature, &pressure);
435 if (r != OK) {
436 return EIO;
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);
444 log_trace(&log, "%s", buffer);
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);
451 r = sys_safecopyto(endpt, grant, 0,
452 (vir_bytes)(buffer + (size_t)position), size);
454 return (r != OK) ? r : size;
457 static void
458 bmp085_other(message * m, int ipc_status)
460 int r;
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);
469 return;
472 log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
475 static int
476 sef_cb_lu_state_save(int UNUSED(state))
478 ds_publish_u32("bus", bus, DSF_OVERWRITE);
479 ds_publish_u32("address", address, DSF_OVERWRITE);
480 return OK;
483 static int
484 lu_state_restore(void)
486 /* Restore the state. */
487 u32_t value;
489 ds_retrieve_u32("bus", &value);
490 ds_delete_u32("bus");
491 bus = (int) value;
493 ds_retrieve_u32("address", &value);
494 ds_delete_u32("address");
495 address = (int) value;
497 return OK;
500 static int
501 sef_cb_init(int type, sef_init_info_t * UNUSED(info))
503 int r;
505 if (type == SEF_INIT_LU) {
506 /* Restore the state. */
507 lu_state_restore();
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;
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;
525 r = bmp085_init();
526 if (r != OK) {
527 log_warn(&log, "Couldn't initialize device\n");
528 return EXIT_FAILURE;
531 if (type != SEF_INIT_LU) {
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;
540 i2cdriver_announce(bus);
541 log_debug(&log, "announced\n");
544 return OK;
547 static void
548 sef_local_startup(void)
551 * Register init callbacks. Use the same function for all event types
553 sef_setcb_init_fresh(sef_cb_init);
554 sef_setcb_init_lu(sef_cb_init);
555 sef_setcb_init_restart(sef_cb_init);
558 * Register live update callbacks.
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);
567 /* Let SEF perform startup. */
568 sef_startup();
572 main(int argc, char *argv[])
574 int r;
576 env_setargs(argc, argv);
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;
589 sef_local_startup();
591 chardriver_task(&bmp085_tab);
593 return 0;