/**
 ******************************************************************************
 * @addtogroup PIOS PIOS Core hardware abstraction layer
 * @{
 * @addtogroup PIOS_IMU3000 IMU3000 Functions
 * @brief Deals with the hardware interface to the 3-axis gyro
 * @{
 *
 * @file       pios_IMU3000.c
 * @author     David "Buzz" Carlson (buzz@chebuzz.com)
 * 				The OpenPilot Team, http://www.openpilot.org Copyright (C) 2011.
 * @brief      IMU3000 3-axis gyor functions from INS
 * @see        The GNU Public License (GPL) Version 3
 *
 ******************************************************************************
 */
/* 
 * This program is free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation; either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
 * for more details.
 * 
 * You should have received a copy of the GNU General Public License along 
 * with this program; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/* Project Includes */
#include "pios.h"

#if defined(PIOS_INCLUDE_IMU3000)

/* Global Variables */

/* Local Variables */
#define DEG_TO_RAD (M_PI / 180.0)
static void PIOS_IMU3000_Config(struct pios_imu3000_cfg const * cfg);
static int32_t PIOS_IMU3000_Read(uint8_t address, uint8_t * buffer, uint8_t len);
static int32_t PIOS_IMU3000_Write(uint8_t address, uint8_t buffer);

#define PIOS_IMU3000_MAX_DOWNSAMPLE 10
static int16_t pios_imu3000_buffer[PIOS_IMU3000_MAX_DOWNSAMPLE * sizeof(struct pios_imu3000_data)];
static t_fifo_buffer pios_imu3000_fifo;

volatile bool imu3000_first_read = true;
volatile bool imu3000_configured = false;
volatile bool imu3000_cb_ready = true;

static struct pios_imu3000_cfg const * cfg;

/**
 * @brief Initialize the IMU3000 3-axis gyro sensor.
 * @return none
 */
void PIOS_IMU3000_Init(const struct pios_imu3000_cfg * new_cfg)
{
	cfg = new_cfg;
	
	fifoBuf_init(&pios_imu3000_fifo, (uint8_t *) pios_imu3000_buffer, sizeof(pios_imu3000_buffer));

	/* Configure EOC pin as input floating */
	GPIO_Init(cfg->drdy.gpio, &cfg->drdy.init);
	
	/* Configure the End Of Conversion (EOC) interrupt */
	SYSCFG_EXTILineConfig(cfg->eoc_exti.port_source, cfg->eoc_exti.pin_source);
	EXTI_Init(&cfg->eoc_exti.init);
	
	/* Enable and set EOC EXTI Interrupt to the lowest priority */
	NVIC_Init(&cfg->eoc_irq.init);

	/* Configure the IMU3000 Sensor */
	PIOS_IMU3000_Config(cfg);
}

/**
 * @brief Initialize the IMU3000 3-axis gyro sensor
 * \return none
 * \param[in] PIOS_IMU3000_ConfigTypeDef struct to be used to configure sensor.
*
*/
static void PIOS_IMU3000_Config(struct pios_imu3000_cfg const * cfg)
{
	imu3000_first_read = true;
	imu3000_cb_ready = true;
	
	// Reset chip and fifo
	while (PIOS_IMU3000_Write(PIOS_IMU3000_USER_CTRL_REG, 0x01 | 0x02) != 0);
	PIOS_DELAY_WaituS(20);
	while (PIOS_IMU3000_Write(PIOS_IMU3000_USER_CTRL_REG, 0x00) != 0);

	// FIFO storage
	while (PIOS_IMU3000_Write(PIOS_IMU3000_FIFO_EN_REG, cfg->Fifo_store) != 0);

	// Sample rate divider
	while (PIOS_IMU3000_Write(PIOS_IMU3000_SMPLRT_DIV_REG, cfg->Smpl_rate_div) != 0) ;

	// Digital low-pass filter and scale
	while (PIOS_IMU3000_Write(PIOS_IMU3000_DLPF_CFG_REG, cfg->filter | (cfg->range << 3)) != 0) ;

	// Interrupt configuration
	while (PIOS_IMU3000_Write(PIOS_IMU3000_USER_CTRL_REG, cfg->User_ctl) != 0) ;

	// Interrupt configuration
	while (PIOS_IMU3000_Write(PIOS_IMU3000_PWR_MGMT_REG, cfg->Pwr_mgmt_clk) != 0) ;
	
	// Interrupt configuration
	while (PIOS_IMU3000_Write(PIOS_IMU3000_INT_CFG_REG, cfg->Interrupt_cfg) != 0) ;
	
	imu3000_configured = true;
}

/**
 * @brief Read current X, Z, Y values (in that order)
 * \param[out] int16_t array of size 3 to store X, Z, and Y magnetometer readings
 * \returns The number of samples remaining in the fifo
*/
int32_t PIOS_IMU3000_ReadGyros(struct pios_imu3000_data * data)
{
	uint8_t buf[6];
	if(PIOS_IMU3000_Read(PIOS_IMU3000_GYRO_X_OUT_MSB, (uint8_t *) buf, sizeof(buf)) < 0)
		return -1;
	data->x = buf[0] << 8 | buf[1];
	data->y = buf[2] << 8 | buf[3];
	data->z = buf[4] << 8 | buf[5];
	return 0;
}


/**
 * @brief Read the identification bytes from the IMU3000 sensor
 * \return ID read from IMU3000 or -1 if failure
*/
int32_t PIOS_IMU3000_ReadID()
{
	uint8_t imu3000_id;
	if(PIOS_IMU3000_Read(0x00, (uint8_t *) &imu3000_id, 1) != 0)
		return -1;
	return imu3000_id;
}

/**
 * \brief Reads the data from the IMU3000 FIFO
 * \param[out] buffer destination buffer
 * \param[in] len maximum number of bytes which should be read
 * \note This returns the data as X, Y, Z the temperature
 * \return number of bytes transferred if operation was successful
 * \return -1 if error during I2C transfer
 */
int32_t PIOS_IMU3000_ReadFifo(struct pios_imu3000_data * buffer)
{
	if(fifoBuf_getUsed(&pios_imu3000_fifo) < sizeof(*buffer))
		return -1;
		
	fifoBuf_getData(&pios_imu3000_fifo, (uint8_t *) buffer, sizeof(*buffer));
	
	return 0;
}

/**
* @brief Reads one or more bytes from IMU3000 into a buffer
* \param[in] address IMU3000 register address (depends on size)
* \param[out] buffer destination buffer
* \param[in] len number of bytes which should be read
* \return 0 if operation was successful
* \return -1 if error during I2C transfer
* \return -2 if unable to claim i2c device
*/
static int32_t PIOS_IMU3000_Read(uint8_t address, uint8_t * buffer, uint8_t len)
{
	uint8_t addr_buffer[] = {
		address,
	};

	const struct pios_i2c_txn txn_list[] = {
		{
		 .info = __func__,
		 .addr = PIOS_IMU3000_I2C_ADDR,
		 .rw = PIOS_I2C_TXN_WRITE,
		 .len = sizeof(addr_buffer),
		 .buf = addr_buffer,
		 }
		,
		{
		 .info = __func__,
		 .addr = PIOS_IMU3000_I2C_ADDR,
		 .rw = PIOS_I2C_TXN_READ,
		 .len = len,
		 .buf = buffer,
		 }
	};

	return PIOS_I2C_Transfer(PIOS_I2C_GYRO_ADAPTER, txn_list, NELEMENTS(txn_list));
}

// Must allocate on stack to be persistent
static uint8_t cb_addr_buffer[] = {
	0,
};
static struct pios_i2c_txn cb_txn_list[] = {
	{
		.addr = PIOS_IMU3000_I2C_ADDR,
		.rw = PIOS_I2C_TXN_WRITE,
		.len = sizeof(cb_addr_buffer),
		.buf = cb_addr_buffer,
	}
	,
	{
		.addr = PIOS_IMU3000_I2C_ADDR,
		.rw = PIOS_I2C_TXN_READ,
		.len = 0,
		.buf = 0,
	}
};



static int32_t PIOS_IMU3000_Read_Callback(uint8_t address, uint8_t * buffer, uint8_t len, void *callback)
{
	cb_addr_buffer[0] = address;
	cb_txn_list[0].info = __func__,
	cb_txn_list[1].info = __func__;
	cb_txn_list[1].len = len;
	cb_txn_list[1].buf = buffer;
	
	return PIOS_I2C_Transfer_Callback(PIOS_I2C_GYRO_ADAPTER, cb_txn_list, NELEMENTS(cb_txn_list), callback);
}

/**
 * @brief Writes one or more bytes to the IMU3000
 * \param[in] address Register address
 * \param[in] buffer source buffer
 * \return 0 if operation was successful
 * \return -1 if error during I2C transfer
 * \return -2 if unable to claim i2c device
 */
static int32_t PIOS_IMU3000_Write(uint8_t address, uint8_t buffer)
{
	uint8_t data[] = {
		address,
		buffer,
	};

	const struct pios_i2c_txn txn_list[] = {
		{
		 .info = __func__,
		 .addr = PIOS_IMU3000_I2C_ADDR,
		 .rw = PIOS_I2C_TXN_WRITE,
		 .len = sizeof(data),
		 .buf = data,
		 }
		,
	};

	return PIOS_I2C_Transfer(PIOS_I2C_GYRO_ADAPTER, txn_list, NELEMENTS(txn_list));
}

float PIOS_IMU3000_GetScale() 
{
	switch (cfg->range) {
		case PIOS_IMU3000_SCALE_250_DEG:
			return DEG_TO_RAD / 131.0;
		case PIOS_IMU3000_SCALE_500_DEG:
			return DEG_TO_RAD / 65.5;
		case PIOS_IMU3000_SCALE_1000_DEG:
			return DEG_TO_RAD / 32.8;
		case PIOS_IMU3000_SCALE_2000_DEG:
			return DEG_TO_RAD / 16.4;
	}
	return 0;
}
/**
 * @brief Run self-test operation.
 * \return 0 if test succeeded
 * \return non-zero value if test succeeded
 */
uint8_t PIOS_IMU3000_Test(void)
{
	/* Verify that ID matches (IMU3000 ID is 0x69) */
	int32_t imu3000_id = PIOS_IMU3000_ReadID();
	if(imu3000_id < 0)
		return -1;
	
	if(imu3000_id != PIOS_IMU3000_I2C_ADDR)
		return -2;
	
	return 0;
}

static uint8_t imu3000_read_buffer[sizeof(struct pios_imu3000_data) + 2]; // Right now using ,Y,Z,fifo_footer
static void imu3000_callback()
{
	struct pios_imu3000_data data;

	if(fifoBuf_getFree(&pios_imu3000_fifo) < sizeof(data))
		goto out;

	if(imu3000_first_read) {
		data.temperature = imu3000_read_buffer[0] << 8 | imu3000_read_buffer[1];
		data.x = imu3000_read_buffer[2] << 8 | imu3000_read_buffer[3];
		data.y = imu3000_read_buffer[4] << 8 | imu3000_read_buffer[5];
		data.z = imu3000_read_buffer[6] << 8 | imu3000_read_buffer[7];
		
		imu3000_first_read = false;
	} else {
		// First two bytes are left over fifo from last call
		data.temperature = imu3000_read_buffer[2] << 8 | imu3000_read_buffer[3];
		data.x = imu3000_read_buffer[4] << 8 | imu3000_read_buffer[5];
		data.y = imu3000_read_buffer[6] << 8 | imu3000_read_buffer[7];
		data.z = imu3000_read_buffer[8] << 8 | imu3000_read_buffer[9];
	}
	
	fifoBuf_putData(&pios_imu3000_fifo, (uint8_t *) &data, sizeof(data));
	
out:
	imu3000_cb_ready = true;

}

/**
* @brief IRQ Handler
*/
uint32_t imu3000_irq = 0;
uint16_t fifo_level;
uint8_t fifo_level_data[2];
uint32_t cb_not_ready = 0;
void PIOS_IMU3000_IRQHandler(void)
{
	
	imu3000_irq++;

	if(!imu3000_configured)
		return;
		
	//PIOS_Assert(imu3000_cb_ready);
	if(!imu3000_cb_ready) {
		PIOS_LED_Toggle(LED2);
		cb_not_ready++;
		return;
	}

	// If at least one read doesnt succeed then the irq not reset and we will stall
	while(PIOS_IMU3000_Read(PIOS_IMU3000_FIFO_CNT_MSB, (uint8_t *) &fifo_level_data, sizeof(fifo_level_data)) != 0)
		PIOS_DELAY_WaituS(10);
				
	fifo_level = (fifo_level_data[0] << 8) + fifo_level_data[1];
	
	PIOS_DELAY_WaituS(10);
	
	if(imu3000_first_read) {
		// Stupid system for IMU3000.  If first read from buffer then we will read 4 sensors without fifo
		// footer.  After this we will read out a fifo footer
		if(fifo_level < sizeof(imu3000_read_buffer))
			return;
			
		imu3000_cb_ready = false;
		
		// Leave footer in buffer
		PIOS_IMU3000_Read_Callback(PIOS_IMU3000_FIFO_REG, imu3000_read_buffer, sizeof(imu3000_read_buffer) - 2, imu3000_callback);
		
	} else {
		// Stupid system for IMU3000.  Ensure something is left in buffer
		if(fifo_level < (sizeof(imu3000_read_buffer) + 2))
			return;
		
		imu3000_cb_ready = false;
		
		// Leave footer in buffer
		PIOS_IMU3000_Read_Callback(PIOS_IMU3000_FIFO_REG, imu3000_read_buffer, sizeof(imu3000_read_buffer), imu3000_callback);
		
	}
}

/**
 * The physical IRQ handler
 * Soon this will be generic in pios_exti and the BMA180 will register
 * against it.  Right now this is crap!
 */
void EXTI1_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line1) != RESET)
	{
		PIOS_IMU3000_IRQHandler();
		EXTI_ClearITPendingBit(EXTI_Line1);
	}
}

#endif

/**
 * @}
 * @}
 */