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