/**
 * Return true if the MPU6000 has new data available for reading.
 *
 * We use the data ready pin if it is available.  Otherwise, read the
 * status register.
 */
bool AP_InertialSensor_MPU6000::_data_ready()
{
    if (_drdy_pin) {
        return _drdy_pin->read() != 0;
    }

    if (hal.scheduler->in_timerprocess()) {
        bool got = _spi_sem->take_nonblocking();
        if (got) {
            uint8_t status = _register_read(MPUREG_INT_STATUS);
            _spi_sem->give();
            return (status & BIT_RAW_RDY_INT) != 0;
        } else {
            return false;
        }
    } else {
        bool got = _spi_sem->take(10);
        if (got) {
            uint8_t status = _register_read(MPUREG_INT_STATUS);
            _spi_sem->give();
            return (status & BIT_RAW_RDY_INT) != 0;
        } else {
            hal.scheduler->panic(
                PSTR("PANIC: AP_InertialSensor_MPU6000::_data_ready failed to "
                     "take SPI semaphore synchronously"));
        }
    }
    return false;
}
void AP_InertialSensor_LSM303D::disable_i2c(void)
{
    uint8_t a = _register_read(0x02);
    _register_write(0x02, (0x10 | a));
    a = _register_read(0x02);
    _register_write(0x02, (0xF7 & a));
    a = _register_read(0x15);
    _register_write(0x15, (0x80 | a));
    a = _register_read(0x02);
    _register_write(0x02, (0xE7 & a));
}
/*
  initialise the sensor
 */
bool AP_InertialSensor_MPU9250::_init_sensor(void)
{
    _spi = hal.spi->device(AP_HAL::SPIDevice_MPU9250);
    _spi_sem = _spi->get_semaphore();

    // we need to suspend timers to prevent other SPI drivers grabbing
    // the bus while we do the long initialisation
    hal.scheduler->suspend_timer_procs();

    uint8_t whoami = _register_read(MPUREG_WHOAMI);
    if (whoami != 0x71) {
        // TODO: we should probably accept multiple chip
        // revisions. This is the one on the PXF
        hal.console->printf("MPU9250: unexpected WHOAMI 0x%x\n", (unsigned)whoami);
        return false;
    }

    uint8_t tries = 0;
    do {
        bool success = _hardware_init();
        if (success) {
            hal.scheduler->delay(10);
            if (!_spi_sem->take(100)) {
                hal.console->printf("MPU9250: Unable to get semaphore");
                return false;
            }
            uint8_t status = _register_read(MPUREG_INT_STATUS);
            if ((status & BIT_RAW_RDY_INT) != 0) {
                _spi_sem->give();
                break;
            }
            _spi_sem->give();
        }
        if (tries++ > 5) {
            return false;
        }
    } while (1);

    hal.scheduler->resume_timer_procs();

    _gyro_instance = _imu.register_gyro();
    _accel_instance = _imu.register_accel();

    _product_id = AP_PRODUCT_ID_MPU9250;

    // start the timer process to read samples
    hal.scheduler->register_timer_process(AP_HAL_MEMBERPROC(&AP_InertialSensor_MPU9250::_poll_data));

#if MPU9250_DEBUG
    _dump_registers();
#endif
    return true;
}
void AP_InertialSensor_L3GD20::disable_i2c(void)
{
	uint8_t retries = 10;
	while (retries--) {
		// add retries
		uint8_t a = _register_read(0x05);
		_register_write(0x05, (0x20 | a));
		if (_register_read(0x05) == (a | 0x20)) {
			return;
		}
	}	
	hal.scheduler->panic(PSTR("L3GD20: Unable to disable I2C"));
}
void AP_InertialSensor_LSM303D::_read_data_transaction_mag() {
    if (_register_read(ADDR_CTRL_REG7) != _reg7_expected) {
            hal.console->println_P(
                        PSTR("LSM303D _read_data_transaction_accel: _reg7_expected unexpected"));
        // reset();
        return;
    }

    struct {
        uint8_t     cmd;
        uint8_t     status;
        int16_t     x;
        int16_t     y;
        int16_t     z;
    } raw_mag_report;

    /* fetch data from the sensor */
    memset(&raw_mag_report, 0, sizeof(raw_mag_report));
    raw_mag_report.cmd = ADDR_STATUS_M | DIR_READ | ADDR_INCREMENT;
    _spi->transaction((uint8_t *)&raw_mag_report, (uint8_t *)&raw_mag_report, sizeof(raw_mag_report));

    _mag_sum.x = raw_mag_report.x;
    _mag_sum.y = raw_mag_report.y;
    _mag_sum.z = raw_mag_report.z;
}
/**
 * Return true if the MPU6000 has new data available for reading.
 *
 * We use the data ready pin if it is available.  Otherwise, read the
 * status register.
 */
bool AP_InertialSensor_MPU6000::_data_ready()
{
    if (_drdy_pin) {
        return _drdy_pin->read() != 0;
    }
    uint8_t status = _register_read(MPUREG_INT_STATUS);
    return (status & BIT_RAW_RDY_INT) != 0;
}
void AP_InertialSensor_LSM303D::_register_modify(uint8_t reg, uint8_t clearbits, uint8_t setbits)
{
    uint8_t val;

    val = _register_read(reg);
    val &= ~clearbits;
    val |= setbits;
    _register_write(reg, val);
}
/*
  useful when debugging SPI bus errors
 */
void AP_InertialSensor_L3GD20::_register_write_check(uint8_t reg, uint8_t val)
{
    uint8_t readed;
    _register_write(reg, val);
    readed = _register_read(reg);
    if (readed != val){
	hal.console->printf_P(PSTR("Values doesn't match; written: %02x; read: %02x "), val, readed);
    }
#if L3GD20_DEBUG
    hal.console->printf_P(PSTR("Values written: %02x; readed: %02x "), val, readed);
#endif
}
// dump all config registers - used for debug
void AP_InertialSensor_MPU6000::_dump_registers(void)
{
    hal.console->println_P(PSTR("MPU6000 registers"));
    for (uint8_t reg=MPUREG_PRODUCT_ID; reg<=108; reg++) {
        uint8_t v = _register_read(reg);
        hal.console->printf_P(PSTR("%02x:%02x "), (unsigned)reg, (unsigned)v);
        if ((reg - (MPUREG_PRODUCT_ID-1)) % 16 == 0) {
            hal.console->println();
        }
    }
    hal.console->println();
}
// dump all config registers - used for debug
void AP_InertialSensor_L3GD20::_dump_registers(void)
{
    hal.console->println_P(PSTR("L3GD20 registers"));
    if (_spi_sem->take(100)) {
        for (uint8_t reg=ADDR_WHO_AM_I; reg<=56; reg++) { // 0x38 = 56
            uint8_t v = _register_read(reg);
            hal.console->printf_P(PSTR("%02x:%02x "), (unsigned)reg, (unsigned)v);
            if ((reg - (ADDR_WHO_AM_I-1)) % 16 == 0) {
                hal.console->println();
            }
        }
        hal.console->println();
        _spi_sem->give();
    }
}
bool AP_Compass_AK8963::_calibrate()
{
    error("CALIBRATTION START\n");
    _register_write(AK8963_CNTL1, AK8963_FUSE_MODE | _magnetometer_adc_resolution); /* Enable FUSE-mode in order to be able to read calibreation data */

    hal.scheduler->delay(10);

    uint8_t response[3];
    _register_read(AK8963_ASAX, 0x03, response);

    for (int i = 0; i < 3; i++) {
        float data = response[i];
        magnetometer_ASA[i] = ((data-128)/256+1);
        error("%d: %lf\n", i, magnetometer_ASA[i]);
    }

    error("CALIBRATTION END\n");

    return true;
}
bool AP_Compass_AK8963::init()
{
    _healthy[0] = true;

    _field[0].x = 0.0f;
    _field[0].y = 0.0f;
    _field[0].z = 0.0f;

    hal.scheduler->suspend_timer_procs();
    if (!_backend->sem_take_blocking()) {
        error("_spi_sem->take failed\n");
        return false;
    }


    if (!_backend_init()) {
        _backend->sem_give();
        return false;
    }

    _register_write(AK8963_CNTL2, AK8963_RESET); /* Reset AK8963 */

    hal.scheduler->delay(1000);

    int id_mismatch_count;
    uint8_t deviceid;
    for (id_mismatch_count = 0; id_mismatch_count < 5; id_mismatch_count++) {
        _register_read(AK8963_WIA, 0x01, &deviceid); /* Read AK8963's id */

        if (deviceid == AK8963_Device_ID) {
            break;
        }

        error("trying to read AK8963's ID once more...\n");
        _backend_reset();
        hal.scheduler->delay(100);
        _dump_registers();
    } 

    if (id_mismatch_count == 5) {
        _initialised = false;
        hal.console->printf("WRONG AK8963 DEVICE ID: 0x%x\n", (unsigned)deviceid);
        hal.scheduler->panic(PSTR("AK8963: bad DEVICE ID"));
    }

    _calibrate();

    _initialised = true;

#if AK8963_SELFTEST
    if (_self_test()) {    
        _initialised = true;
    } else {
        _initialised = false;
    }
#endif

    /* Register value to continuous measurement */
    _register_write(AK8963_CNTL1, AK8963_CONTINUOUS_MODE2 | _magnetometer_adc_resolution);

    _backend->sem_give();

    hal.scheduler->resume_timer_procs();
    hal.scheduler->register_timer_process( AP_HAL_MEMBERPROC(&AP_Compass_AK8963::_update));

    _start_conversion();

    _initialised = true;
    return _initialised;
}
uint16_t AP_InertialSensor_L3GD20::_init_sensor( Sample_rate sample_rate )
{
    if (_initialised) return _L3GD20_product_id;
    _initialised = true;

    _spi = hal.spi->device(AP_HAL::SPIDevice_L3GD20);
    _spi_sem = _spi->get_semaphore();

#ifdef L3GD20_DRDY_PIN
    _drdy_pin = hal.gpio->channel(L3GD20_DRDY_PIN);
    _drdy_pin->mode(HAL_GPIO_INPUT);
#endif

    hal.scheduler->suspend_timer_procs();

    // Test WHOAMI
    uint8_t whoami = _register_read(ADDR_WHO_AM_I);
    if (whoami != WHO_I_AM) {
        // TODO: we should probably accept multiple chip
        // revisions. This is the one on the PXF
        hal.console->printf("L3GD20: unexpected WHOAMI 0x%x\n", (unsigned)whoami);
        hal.scheduler->panic(PSTR("L3GD20: bad WHOAMI"));
    }

    uint8_t tries = 0;
    do {
        bool success = _hardware_init(sample_rate);
        if (success) {
            hal.scheduler->delay(5+2);
            if (!_spi_sem->take(100)) {
                hal.scheduler->panic(PSTR("L3GD20: Unable to get semaphore"));
            }
            if (_data_ready()) {
                _spi_sem->give();
                break;
            } else {
                hal.console->println_P(
                        PSTR("L3GD20 startup failed: no data ready"));
            }
            _spi_sem->give();
        }
        if (tries++ > 5) {
            hal.scheduler->panic(PSTR("PANIC: failed to boot L3GD20 5 times")); 
        }
    } while (1);

    hal.scheduler->resume_timer_procs();
    

    /* read the first lot of data.
     * _read_data_transaction requires the spi semaphore to be taken by
     * its caller. */
    _last_sample_time_micros = hal.scheduler->micros();
    hal.scheduler->delay(10);
    if (_spi_sem->take(100)) {
        _read_data_transaction();
        _spi_sem->give();
    }

    // start the timer process to read samples
    hal.scheduler->register_timer_process(AP_HAL_MEMBERPROC(&AP_InertialSensor_L3GD20::_poll_data));

#if L3GD20_DEBUG
    _dump_registers();
#endif
    return _L3GD20_product_id;
}
/*
  initialise the sensor
 */
uint16_t AP_InertialSensor_MPU9250::_init_sensor( Sample_rate sample_rate )
{
    if (_initialised) return _mpu9250_product_id;
    _initialised = true;

    _spi = hal.spi->device(AP_HAL::SPIDevice_MPU9250);
    _spi_sem = _spi->get_semaphore();

    // we need to suspend timers to prevent other SPI drivers grabbing
    // the bus while we do the long initialisation
    hal.scheduler->suspend_timer_procs();

    uint8_t whoami = _register_read(MPUREG_WHOAMI);
    if (whoami != 0x71) {
        // TODO: we should probably accept multiple chip
        // revisions. This is the one on the PXF
        hal.console->printf("MPU9250: unexpected WHOAMI 0x%x\n", (unsigned)whoami);
        hal.scheduler->panic("MPU9250: bad WHOAMI");
    }

    uint8_t tries = 0;
    do {
        bool success = _hardware_init(sample_rate);
        if (success) {
            hal.scheduler->delay(10);
            if (!_spi_sem->take(100)) {
                hal.scheduler->panic(PSTR("MPU9250: Unable to get semaphore"));
            }
            uint8_t status = _register_read(MPUREG_INT_STATUS);
            if ((status & BIT_RAW_RDY_INT) != 0) {
                _spi_sem->give();
                break;
            } else {
                hal.console->println_P(
                        PSTR("MPU9250 startup failed: no data ready"));
            }
            _spi_sem->give();
        }
        if (tries++ > 5) {
            hal.scheduler->panic(PSTR("PANIC: failed to boot MPU9250 5 times"));
        }
    } while (1);

    hal.scheduler->resume_timer_procs();

    /* read the first lot of data.
     * _read_data_transaction requires the spi semaphore to be taken by
     * its caller. */
    hal.scheduler->delay(10);
    if (_spi_sem->take(100)) {
        _read_data_transaction();
        _spi_sem->give();
    }

    // start the timer process to read samples
    hal.scheduler->register_timer_process(AP_HAL_MEMBERPROC(&AP_InertialSensor_MPU9250::_poll_data));

#if MPU9250_DEBUG
    _dump_registers();
#endif
    return _mpu9250_product_id;
}
bool AP_InertialSensor_MPU6000::hardware_init(Sample_rate sample_rate)
{
    if (!_spi_sem->take(100)) {
        hal.scheduler->panic(PSTR("MPU6000: Unable to get semaphore"));
    }

    // Chip reset
    uint8_t tries;
    for (tries = 0; tries<5; tries++) {
        register_write(MPUREG_PWR_MGMT_1, BIT_PWR_MGMT_1_DEVICE_RESET);
        hal.scheduler->delay(100);

        // Wake up device and select GyroZ clock. Note that the
        // MPU6000 starts up in sleep mode, and it can take some time
        // for it to come out of sleep
        register_write(MPUREG_PWR_MGMT_1, BIT_PWR_MGMT_1_CLK_ZGYRO);
        hal.scheduler->delay(5);

        // check it has woken up
        if (_register_read(MPUREG_PWR_MGMT_1) == BIT_PWR_MGMT_1_CLK_ZGYRO) {
            break;
        }
#if MPU6000_DEBUG
        _dump_registers();
#endif
    }
    if (tries == 5) {
        hal.console->println_P(PSTR("Failed to boot MPU6000 5 times"));
        _spi_sem->give();
        return false;
    }

    register_write(MPUREG_PWR_MGMT_2, 0x00);            // only used for wake-up in accelerometer only low power mode
    hal.scheduler->delay(1);

    // Disable I2C bus (recommended on datasheet)
    register_write(MPUREG_USER_CTRL, BIT_USER_CTRL_I2C_IF_DIS);
    hal.scheduler->delay(1);

    uint8_t default_filter;

    // sample rate and filtering
    // to minimise the effects of aliasing we choose a filter
    // that is less than half of the sample rate
    switch (sample_rate) {
    case RATE_50HZ:
        // this is used for plane and rover, where noise resistance is
        // more important than update rate. Tests on an aerobatic plane
        // show that 10Hz is fine, and makes it very noise resistant
        default_filter = BITS_DLPF_CFG_10HZ;
        _sample_shift = 2;
        break;
    case RATE_100HZ:
        default_filter = BITS_DLPF_CFG_20HZ;
        _sample_shift = 1;
        break;
    case RATE_200HZ:
    default:
        default_filter = BITS_DLPF_CFG_20HZ;
        _sample_shift = 0;
        break;
    }

    _set_filter_register(_mpu6000_filter, default_filter);

    // set sample rate to 200Hz, and use _sample_divider to give
    // the requested rate to the application
    register_write(MPUREG_SMPLRT_DIV, MPUREG_SMPLRT_200HZ);
    hal.scheduler->delay(1);

    register_write(MPUREG_GYRO_CONFIG, BITS_GYRO_FS_2000DPS);  // Gyro scale 2000º/s
    hal.scheduler->delay(1);

    // read the product ID rev c has 1/2 the sensitivity of rev d
    _mpu6000_product_id = _register_read(MPUREG_PRODUCT_ID);
    //Serial.printf("Product_ID= 0x%x\n", (unsigned) _mpu6000_product_id);

    if ((_mpu6000_product_id == MPU6000ES_REV_C4) || (_mpu6000_product_id == MPU6000ES_REV_C5) ||
        (_mpu6000_product_id == MPU6000_REV_C4)   || (_mpu6000_product_id == MPU6000_REV_C5)) {
        // Accel scale 8g (4096 LSB/g)
        // Rev C has different scaling than rev D
        register_write(MPUREG_ACCEL_CONFIG,1<<3);
    } else {
        // Accel scale 8g (4096 LSB/g)
        register_write(MPUREG_ACCEL_CONFIG,2<<3);
    }
    hal.scheduler->delay(1);

    // configure interrupt to fire when new data arrives
    register_write(MPUREG_INT_ENABLE, BIT_RAW_RDY_EN);
    hal.scheduler->delay(1);

    // clear interrupt on any read, and hold the data ready pin high
    // until we clear the interrupt
    register_write(MPUREG_INT_PIN_CFG, BIT_INT_RD_CLEAR | BIT_LATCH_INT_EN);
    hal.scheduler->delay(1);

    _spi_sem->give();

    return true;
}