75bd3009d3
Many i2c device drivers used similar code to access registers on the ICs they drive. This commit implements that functionality in libi2cdriver and updates the drivers to use the library instead of their own register access functions. The net result is 375+ fewer lines of code and less work for people developing new drivers. The two exceptions were cat24c256 and parts of tda19988. They access the bus in uncommon ways. It doesn't make sense at this time to move their read/write functions into libi2cdriver. Change-Id: Id8280b71af33b710a49944d7f20a7262be9f5988
630 lines
15 KiB
C
630 lines
15 KiB
C
/* Driver for the BMP085 Preassure and Temperature Sensor */
|
|
|
|
#include <minix/ds.h>
|
|
#include <minix/drivers.h>
|
|
#include <minix/i2c.h>
|
|
#include <minix/i2cdriver.h>
|
|
#include <minix/chardriver.h>
|
|
#include <minix/log.h>
|
|
|
|
/* Control Register for triggering a measurement */
|
|
#define CTRL_REG 0xf4
|
|
|
|
/* temperature sensor - it only has one 'mode' - conversion time 4.5 ms */
|
|
#define CMD_TRIG_T 0x2e
|
|
#define UDELAY_T (4500)
|
|
|
|
/* pressure sensor - ultra low power mode - conversion time 4.5 ms */
|
|
#define CMD_TRIG_P_ULP 0x34
|
|
#define MODE_ULP 0x00
|
|
#define UDELAY_ULP (4500)
|
|
|
|
/* pressure sensor - standard mode - conversion time 7.5 ms */
|
|
#define CMD_TRIG_P_STD 0x74
|
|
#define MODE_STD 0x01
|
|
#define UDELAY_STD (7500)
|
|
|
|
/* pressure sensor - high resolution mode - conversion time 13.5 ms */
|
|
#define CMD_TRIG_P_HR 0xb4
|
|
#define MODE_HR 0x02
|
|
#define UDELAY_HR (13500)
|
|
|
|
/* pressure sensor - ultra high resolution mode - conversion time 25.5 ms */
|
|
#define CMD_TRIG_P_UHR 0xf4
|
|
#define MODE_UHR 0x03
|
|
#define UDELAY_UHR (25500)
|
|
|
|
/* Values for the different modes of operation */
|
|
struct pressure_cmd
|
|
{
|
|
uint8_t cmd;
|
|
uint8_t mode;
|
|
uint16_t udelay;
|
|
};
|
|
|
|
/* Table of available modes and their parameters. */
|
|
static struct pressure_cmd pressure_cmds[4] = {
|
|
{CMD_TRIG_P_ULP, MODE_ULP, UDELAY_ULP},
|
|
{CMD_TRIG_P_STD, MODE_STD, UDELAY_STD},
|
|
{CMD_TRIG_P_HR, MODE_HR, UDELAY_HR},
|
|
{CMD_TRIG_P_UHR, MODE_UHR, UDELAY_UHR}
|
|
};
|
|
|
|
/* Default to standard mode.
|
|
* There isn't code to configure the resolution at runtime, but it should
|
|
* easy to implement by setting p_cmd to the right element of pressure_cmds.
|
|
*/
|
|
static struct pressure_cmd *p_cmd = &pressure_cmds[MODE_STD];
|
|
|
|
/* Chip Identification */
|
|
#define CHIPID_REG 0xd0
|
|
#define BMP085_CHIPID 0x55
|
|
|
|
/*
|
|
* There is also a version register at 0xd1, but documentation seems to be
|
|
* lacking. The sample code says high 4 bytes are AL version and low 4 are ML.
|
|
*/
|
|
|
|
/* Calibration coefficients
|
|
*
|
|
* These are unique to each chip and must be read when starting the driver.
|
|
* Validate them by checking that none are 0x0000 nor 0xffff. Types and
|
|
* names are from the datasheet.
|
|
*/
|
|
struct calibration
|
|
{
|
|
int16_t ac1;
|
|
int16_t ac2;
|
|
int16_t ac3;
|
|
uint16_t ac4;
|
|
uint16_t ac5;
|
|
uint16_t ac6;
|
|
int16_t b1;
|
|
int16_t b2;
|
|
int16_t mb;
|
|
int16_t mc;
|
|
int16_t md;
|
|
} cal;
|
|
|
|
/* Register locations for calibration coefficients */
|
|
#define AC1_MSB_REG 0xaa
|
|
#define AC1_LSB_REG 0xab
|
|
#define AC2_MSB_REG 0xac
|
|
#define AC2_LSB_REG 0xad
|
|
#define AC3_MSB_REG 0xae
|
|
#define AC3_LSB_REG 0xaf
|
|
#define AC4_MSB_REG 0xb0
|
|
#define AC4_LSB_REG 0xb1
|
|
#define AC5_MSB_REG 0xb2
|
|
#define AC5_LSB_REG 0xb3
|
|
#define AC6_MSB_REG 0xb4
|
|
#define AC6_LSB_REG 0xb5
|
|
#define B1_MSB_REG 0xb6
|
|
#define B1_LSB_REG 0xb7
|
|
#define B2_MSB_REG 0xb8
|
|
#define B2_LSB_REG 0xb9
|
|
#define MB_MSB_REG 0xba
|
|
#define MB_LSB_REG 0xbb
|
|
#define MC_MSB_REG 0xbc
|
|
#define MC_LSB_REG 0xbd
|
|
#define MD_MSB_REG 0xbe
|
|
#define MD_LSB_REG 0xbf
|
|
|
|
#define CAL_COEF_FIRST AC1_MSB_REG
|
|
#define CAL_COEF_LAST MD_LSB_REG
|
|
|
|
#define CAL_COEF_IS_VALID(x) (x != 0x0000 && x != 0xffff)
|
|
|
|
#define SENSOR_VAL_MSB_REG 0xf6
|
|
#define SENSOR_VAL_LSB_REG 0xf7
|
|
#define SENSOR_VAL_XLSB_REG 0xf8
|
|
|
|
/* logging - use with log_warn(), log_info(), log_debug(), log_trace(), etc */
|
|
static struct log log = {
|
|
.name = "bmp085",
|
|
.log_level = LEVEL_INFO,
|
|
.log_func = default_log
|
|
};
|
|
|
|
/* Only one valid slave address. It isn't configurable. */
|
|
static i2c_addr_t valid_addrs[5] = {
|
|
0x77, 0x00
|
|
};
|
|
|
|
/* Buffer to store output string returned when reading from device file. */
|
|
#define BUFFER_LEN 64
|
|
char buffer[BUFFER_LEN + 1];
|
|
|
|
/* the bus that this device is on (counting starting at 1) */
|
|
static uint32_t bus;
|
|
|
|
/* slave address of the device */
|
|
static i2c_addr_t address;
|
|
|
|
/* endpoint for the driver for the bus itself. */
|
|
static endpoint_t bus_endpoint;
|
|
|
|
/* main device functions */
|
|
static int bmp085_init(void);
|
|
static int version_check(void);
|
|
static int read_cal_coef(void);
|
|
static int measure(int32_t * temperature, int32_t * pressure);
|
|
|
|
/* libchardriver callbacks */
|
|
static struct device *bmp085_prepare(dev_t UNUSED(dev));
|
|
static int bmp085_transfer(endpoint_t endpt, int opcode, u64_t position,
|
|
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
|
|
unsigned int UNUSED(flags));
|
|
static int bmp085_other(message * m);
|
|
|
|
/* SEF Function */
|
|
static int sef_cb_lu_state_save(int);
|
|
static int lu_state_restore(void);
|
|
static int sef_cb_init(int type, sef_init_info_t * info);
|
|
static void sef_local_startup(void);
|
|
|
|
/* Entry points to this driver from libchardriver. */
|
|
static struct chardriver bmp085_tab = {
|
|
.cdr_open = do_nop,
|
|
.cdr_close = do_nop,
|
|
.cdr_ioctl = nop_ioctl,
|
|
.cdr_prepare = bmp085_prepare,
|
|
.cdr_transfer = bmp085_transfer,
|
|
.cdr_cleanup = nop_cleanup,
|
|
.cdr_alarm = nop_alarm,
|
|
.cdr_cancel = nop_cancel,
|
|
.cdr_select = nop_select,
|
|
.cdr_other = bmp085_other
|
|
};
|
|
|
|
static struct device bmp085_device = {
|
|
.dv_base = 0,
|
|
.dv_size = 0
|
|
};
|
|
|
|
/*
|
|
* Initialize the driver. Checks the CHIPID against a known value and
|
|
* reads the calibration coefficients.
|
|
*
|
|
* The chip does have a soft reset register (0xe0), but there
|
|
* doesn't appear to be any documentation or example usage for it.
|
|
*/
|
|
static int
|
|
bmp085_init(void)
|
|
{
|
|
int r;
|
|
int32_t t, p;
|
|
|
|
r = version_check();
|
|
if (r != OK) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
r = read_cal_coef();
|
|
if (r != OK) {
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
version_check(void)
|
|
{
|
|
int r;
|
|
uint8_t chipid;
|
|
|
|
r = i2creg_read8(bus_endpoint, address, CHIPID_REG, &chipid);
|
|
if (r != OK) {
|
|
log_warn(&log, "Couldn't read CHIPID\n");
|
|
return -1;
|
|
}
|
|
|
|
if (chipid != BMP085_CHIPID) {
|
|
log_warn(&log, "Bad CHIPID\n");
|
|
return -1;
|
|
}
|
|
|
|
log_debug(&log, "CHIPID OK\n");
|
|
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Read the calibration data from the chip. Each individual chip has a unique
|
|
* set of calibration parameters that get used to compute the true temperature
|
|
* and pressure.
|
|
*/
|
|
static int
|
|
read_cal_coef(void)
|
|
{
|
|
int r;
|
|
|
|
/* Populate the calibration struct with values */
|
|
r = i2creg_read16(bus_endpoint, address, AC1_MSB_REG, &cal.ac1);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.ac1 = %d\n", cal.ac1);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, AC2_MSB_REG, &cal.ac2);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.ac2 = %d\n", cal.ac2);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, AC3_MSB_REG, &cal.ac3);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.ac3 = %d\n", cal.ac3);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, AC4_MSB_REG, &cal.ac4);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.ac4 = %u\n", cal.ac4);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, AC5_MSB_REG, &cal.ac5);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.ac5 = %u\n", cal.ac5);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, AC6_MSB_REG, &cal.ac6);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.ac6 = %u\n", cal.ac6);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, B1_MSB_REG, &cal.b1);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.b1 = %d\n", cal.b1);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, B2_MSB_REG, &cal.b2);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.b2 = %d\n", cal.b2);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, MB_MSB_REG, &cal.mb);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.mb = %d\n", cal.mb);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, MC_MSB_REG, &cal.mc);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.mc = %d\n", cal.mc);
|
|
|
|
r = i2creg_read16(bus_endpoint, address, MD_MSB_REG, &cal.md);
|
|
if (r != OK) {
|
|
return -1;
|
|
}
|
|
log_debug(&log, "cal.md = %d\n", cal.md);
|
|
|
|
/* Validate. Data sheet says values should not be 0x0000 nor 0xffff */
|
|
if (!CAL_COEF_IS_VALID(cal.ac1) ||
|
|
!CAL_COEF_IS_VALID(cal.ac2) ||
|
|
!CAL_COEF_IS_VALID(cal.ac3) ||
|
|
!CAL_COEF_IS_VALID(cal.ac4) ||
|
|
!CAL_COEF_IS_VALID(cal.ac5) ||
|
|
!CAL_COEF_IS_VALID(cal.ac6) ||
|
|
!CAL_COEF_IS_VALID(cal.b1) ||
|
|
!CAL_COEF_IS_VALID(cal.b2) ||
|
|
!CAL_COEF_IS_VALID(cal.mb) ||
|
|
!CAL_COEF_IS_VALID(cal.mc) || !CAL_COEF_IS_VALID(cal.md)) {
|
|
|
|
log_warn(&log, "Invalid calibration data found on chip.\n");
|
|
return -1;
|
|
}
|
|
|
|
log_debug(&log, "Read Cal Data OK\n");
|
|
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Measure the uncompensated temperature and uncompensated pressure from the
|
|
* chip and apply the formulas to determine the true temperature and pressure.
|
|
* Note, the data sheet is light on the details when it comes to defining the
|
|
* meaning of each variable, so this function has a lot of cryptic names in it.
|
|
*/
|
|
static int
|
|
measure(int32_t * temperature, int32_t * pressure)
|
|
{
|
|
int r;
|
|
|
|
/* Types are given in the datasheet. Their long translates to 32-bits */
|
|
|
|
int16_t ut; /* uncompensated temperature */
|
|
int32_t up; /* uncompensated pressure */
|
|
int32_t x1;
|
|
int32_t x2;
|
|
int32_t x3;
|
|
int32_t b3;
|
|
uint32_t b4;
|
|
int32_t b5;
|
|
int32_t b6;
|
|
uint32_t b7;
|
|
int32_t t; /* true temperature (in 0.1C) */
|
|
int32_t p; /* true pressure (in Pa) */
|
|
|
|
log_debug(&log, "Triggering Temp Reading...\n");
|
|
|
|
/* trigger temperature reading */
|
|
r = i2creg_write8(bus_endpoint, address, CTRL_REG, CMD_TRIG_T);
|
|
if (r != OK) {
|
|
log_warn(&log, "Failed to trigger temperature reading.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* wait for sampling to be completed. */
|
|
micro_delay(UDELAY_T);
|
|
|
|
/* read the uncompensated temperature */
|
|
r = i2creg_read16(bus_endpoint, address, SENSOR_VAL_MSB_REG, &ut);
|
|
if (r != OK) {
|
|
log_warn(&log, "Failed to read temperature.\n");
|
|
return -1;
|
|
}
|
|
|
|
log_debug(&log, "ut = %d\n", ut);
|
|
|
|
log_debug(&log, "Triggering Pressure Reading...\n");
|
|
|
|
/* trigger pressure reading */
|
|
r = i2creg_write8(bus_endpoint, address, CTRL_REG, p_cmd->cmd);
|
|
if (r != OK) {
|
|
log_warn(&log, "Failed to trigger pressure reading.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* wait for sampling to be completed. */
|
|
micro_delay(p_cmd->udelay);
|
|
|
|
/* read the uncompensated pressure */
|
|
r = i2creg_read24(bus_endpoint, address, SENSOR_VAL_MSB_REG, &up);
|
|
if (r != OK) {
|
|
log_warn(&log, "Failed to read pressure.\n");
|
|
return -1;
|
|
}
|
|
|
|
/* shift by 8 - oversampling setting */
|
|
up = (up >> (8 - p_cmd->mode));
|
|
|
|
log_debug(&log, "up = %d\n", up);
|
|
|
|
/* convert uncompensated temperature to true temperature */
|
|
x1 = ((ut - cal.ac6) * cal.ac5) / (1 << 15);
|
|
x2 = (cal.mc * (1 << 11)) / (x1 + cal.md);
|
|
b5 = x1 + x2;
|
|
t = (b5 + 8) / (1 << 4);
|
|
|
|
/* save the result */
|
|
*temperature = t;
|
|
|
|
log_debug(&log, "t = %d\n", t);
|
|
|
|
/* Convert uncompensated pressure to true pressure.
|
|
* This is really how the data sheet suggests doing it.
|
|
* There is no alternative approach suggested. Other open
|
|
* source drivers I've found use this method.
|
|
*/
|
|
b6 = b5 - 4000;
|
|
x1 = ((cal.b2 * ((b6 * b6) >> 12)) >> 11);
|
|
x2 = ((cal.ac2 * b6) >> 11);
|
|
x3 = x1 + x2;
|
|
b3 = (((((cal.ac1 * 4) + x3) << p_cmd->mode) + 2) >> 2);
|
|
x1 = ((cal.ac3 * b6) >> 13);
|
|
x2 = ((cal.b1 * ((b6 * b6) >> 12)) >> 16);
|
|
x3 = (((x1 + x2) + 2) >> 2);
|
|
b4 = ((cal.ac4 * ((uint32_t) (x3 + 32768))) >> 15);
|
|
b7 = ((uint32_t) up - b3) * (50000 >> p_cmd->mode);
|
|
p = (b7 < 0x80000000) ? (b7 * 2) / b4 : (b7 / b4) * 2;
|
|
x1 = (p >> 8) * (p >> 8);
|
|
x1 = ((x1 * 3038) >> 16);
|
|
x2 = ((-7357 * p) >> 16);
|
|
p = p + ((x1 + x2 + 3791) >> 4);
|
|
|
|
*pressure = p;
|
|
|
|
log_debug(&log, "p = %d\n", p);
|
|
|
|
return OK;
|
|
}
|
|
|
|
static struct device *
|
|
bmp085_prepare(dev_t UNUSED(dev))
|
|
{
|
|
return &bmp085_device;
|
|
}
|
|
|
|
static int
|
|
bmp085_transfer(endpoint_t endpt, int opcode, u64_t position,
|
|
iovec_t * iov, unsigned nr_req, endpoint_t UNUSED(user_endpt),
|
|
unsigned int UNUSED(flags))
|
|
{
|
|
int bytes, r;
|
|
uint32_t temperature, pressure;
|
|
|
|
r = measure(&temperature, &pressure);
|
|
if (r != OK) {
|
|
return EIO;
|
|
}
|
|
|
|
memset(buffer, '\0', BUFFER_LEN + 1);
|
|
snprintf(buffer, BUFFER_LEN, "%-16s: %d.%01d\n%-16s: %d\n",
|
|
"TEMPERATURE", temperature / 10, temperature % 10, "PRESSURE",
|
|
pressure);
|
|
|
|
log_trace(&log, "%s", buffer);
|
|
|
|
bytes = strlen(buffer) - position < iov->iov_size ?
|
|
strlen(buffer) - position : iov->iov_size;
|
|
|
|
if (bytes <= 0) {
|
|
return OK;
|
|
}
|
|
|
|
switch (opcode) {
|
|
case DEV_GATHER_S:
|
|
r = sys_safecopyto(endpt, (cp_grant_id_t) iov->iov_addr, 0,
|
|
(vir_bytes) (buffer + position), bytes);
|
|
iov->iov_size -= bytes;
|
|
break;
|
|
default:
|
|
return EINVAL;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
bmp085_other(message * m)
|
|
{
|
|
int r;
|
|
|
|
switch (m->m_type) {
|
|
case NOTIFY_MESSAGE:
|
|
if (m->m_source == DS_PROC_NR) {
|
|
log_debug(&log,
|
|
"bus driver changed state, update endpoint\n");
|
|
i2cdriver_handle_bus_update(&bus_endpoint, bus,
|
|
address);
|
|
}
|
|
r = OK;
|
|
break;
|
|
default:
|
|
log_warn(&log, "Invalid message type (0x%x)\n", m->m_type);
|
|
r = EINVAL;
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
sef_cb_lu_state_save(int UNUSED(state))
|
|
{
|
|
ds_publish_u32("bus", bus, DSF_OVERWRITE);
|
|
ds_publish_u32("address", address, DSF_OVERWRITE);
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
lu_state_restore(void)
|
|
{
|
|
/* Restore the state. */
|
|
u32_t value;
|
|
|
|
ds_retrieve_u32("bus", &value);
|
|
ds_delete_u32("bus");
|
|
bus = (int) value;
|
|
|
|
ds_retrieve_u32("address", &value);
|
|
ds_delete_u32("address");
|
|
address = (int) value;
|
|
|
|
return OK;
|
|
}
|
|
|
|
static int
|
|
sef_cb_init(int type, sef_init_info_t * UNUSED(info))
|
|
{
|
|
int r;
|
|
|
|
if (type == SEF_INIT_LU) {
|
|
/* Restore the state. */
|
|
lu_state_restore();
|
|
}
|
|
|
|
/* look-up the endpoint for the bus driver */
|
|
bus_endpoint = i2cdriver_bus_endpoint(bus);
|
|
if (bus_endpoint == 0) {
|
|
log_warn(&log, "Couldn't find bus driver.\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* claim the device */
|
|
r = i2cdriver_reserve_device(bus_endpoint, address);
|
|
if (r != OK) {
|
|
log_warn(&log, "Couldn't reserve device 0x%x (r=%d)\n",
|
|
address, r);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
r = bmp085_init();
|
|
if (r != OK) {
|
|
log_warn(&log, "Couldn't initialize device\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (type != SEF_INIT_LU) {
|
|
|
|
/* sign up for updates about the i2c bus going down/up */
|
|
r = i2cdriver_subscribe_bus_updates(bus);
|
|
if (r != OK) {
|
|
log_warn(&log, "Couldn't subscribe to bus updates\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
i2cdriver_announce(bus);
|
|
log_debug(&log, "announced\n");
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
static void
|
|
sef_local_startup(void)
|
|
{
|
|
/*
|
|
* Register init callbacks. Use the same function for all event types
|
|
*/
|
|
sef_setcb_init_fresh(sef_cb_init);
|
|
sef_setcb_init_lu(sef_cb_init);
|
|
sef_setcb_init_restart(sef_cb_init);
|
|
|
|
/*
|
|
* Register live update callbacks.
|
|
*/
|
|
/* Agree to update immediately when LU is requested in a valid state. */
|
|
sef_setcb_lu_prepare(sef_cb_lu_prepare_always_ready);
|
|
/* Support live update starting from any standard state. */
|
|
sef_setcb_lu_state_isvalid(sef_cb_lu_state_isvalid_standard);
|
|
/* Register a custom routine to save the state. */
|
|
sef_setcb_lu_state_save(sef_cb_lu_state_save);
|
|
|
|
/* Let SEF perform startup. */
|
|
sef_startup();
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int r;
|
|
|
|
env_setargs(argc, argv);
|
|
|
|
r = i2cdriver_env_parse(&bus, &address, valid_addrs);
|
|
if (r < 0) {
|
|
log_warn(&log, "Expecting -args 'bus=X address=0x77'\n");
|
|
log_warn(&log, "Example -args 'bus=1 address=0x77'\n");
|
|
return EXIT_FAILURE;
|
|
} else if (r > 0) {
|
|
log_warn(&log,
|
|
"Invalid slave address for device, expecting 0x77\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
sef_local_startup();
|
|
|
|
chardriver_task(&bmp085_tab, CHARDRIVER_SYNC);
|
|
|
|
return 0;
|
|
}
|