/**
  ******************************************************************************
  * @addtogroup PIOS PIOS Core hardware abstraction layer
  * @{
  * @addtogroup PIOS_BMP085 BMP085 Functions
  * @brief Hardware functions to deal with the altitude pressure sensor
  * @{
  *
  * @file       pios_bmp085.c  
  * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
  * @brief      BMP085 Pressure Sensor Routines
  * @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_BMP085)
#if !defined(PIOS_INCLUDE_EXTI)
#error PIOS_EXTI Must be included in the project!
#endif

/* Glocal Variables */
ConversionTypeTypeDef CurrentRead;
xSemaphoreHandle PIOS_BMP085_EOC;

/* Local Variables */
static BMP085CalibDataTypeDef CalibData;

/* Straight from the datasheet */
static int32_t X1, X2, X3, B3, B5, B6, P;
static uint32_t B4, B7;
static volatile uint16_t RawTemperature;
static volatile uint32_t RawPressure;
static volatile uint32_t Pressure;
static volatile uint16_t Temperature;


/**
* Initialise the BMP085 sensor
*/
void PIOS_BMP085_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	/* Semaphore used by ISR to signal End-Of-Conversion */
	vSemaphoreCreateBinary(PIOS_BMP085_EOC);
	/* Must start off empty so that first transfer waits for EOC */
	xSemaphoreTake(PIOS_BMP085_EOC, portMAX_DELAY);

	/* Enable EOC GPIO clock */
	RCC_APB2PeriphClockCmd(PIOS_BMP085_EOC_CLK | RCC_APB2Periph_AFIO, ENABLE);

	/* Configure EOC pin as input floating */
	GPIO_InitStructure.GPIO_Pin = PIOS_BMP085_EOC_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(PIOS_BMP085_EOC_GPIO_PORT, &GPIO_InitStructure);
	
	/* Configure the End Of Conversion (EOC) interrupt */
	GPIO_EXTILineConfig(PIOS_BMP085_EOC_PORT_SOURCE, PIOS_BMP085_EOC_PIN_SOURCE);
	EXTI_InitStructure.EXTI_Line = PIOS_BMP085_EOC_EXTI_LINE;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);
	
	/* Enable and set EOC EXTI Interrupt to the lowest priority */
	NVIC_InitStructure.NVIC_IRQChannel = PIOS_BMP085_EOC_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = PIOS_BMP085_EOC_PRIO;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	/* Read all 22 bytes of calibration data in one transfer, this is a very optimised way of doing things */
	uint8_t Data[BMP085_CALIB_LEN];
	while (!PIOS_BMP085_Read(BMP085_CALIB_ADDR, Data, BMP085_CALIB_LEN)) continue;
	
	/* Parameters AC1-AC6 */
	CalibData.AC1 = (Data[0] << 8) | Data[1];
	CalibData.AC2 = (Data[2] << 8) | Data[3];
	CalibData.AC3 = (Data[4] << 8) | Data[5];
	CalibData.AC4 = (Data[6] << 8) | Data[7];
	CalibData.AC5 = (Data[8] << 8) | Data[9];
	CalibData.AC6 = (Data[10] << 8) | Data[11];

	/* Parameters B1, B2 */
	CalibData.B1 =  (Data[12] << 8) | Data[13];
	CalibData.B2 =  (Data[14] << 8) | Data[15];

	/* Parameters MB, MC, MD */
	CalibData.MB =  (Data[16] << 8) | Data[17];
	CalibData.MC =  (Data[18] << 8) | Data[19];
	CalibData.MD =  (Data[20] << 8) | Data[21];
}


/**
* Start the ADC conversion
* \param[in] PresOrTemp BMP085_PRES_ADDR or BMP085_TEMP_ADDR
* \return Raw ADC value
*/
void PIOS_BMP085_StartADC(ConversionTypeTypeDef Type)
{
	/* Start the conversion */
	if(Type == TemperatureConv) {
		while (!PIOS_BMP085_Write(BMP085_CTRL_ADDR, BMP085_TEMP_ADDR)) continue;
	} else if(Type == PressureConv) {
		while (!PIOS_BMP085_Write(BMP085_CTRL_ADDR, BMP085_PRES_ADDR)) continue;
	}
	
	CurrentRead = Type;
}


/**
* Read the ADC conversion value (once ADC conversion has completed)
* \param[in] PresOrTemp BMP085_PRES_ADDR or BMP085_TEMP_ADDR
* \return Raw ADC value
*/
void PIOS_BMP085_ReadADC(void)
{
	uint8_t Data[3];
	Data[0] = 0;
	Data[1] = 0;
	Data[2] = 0;
	
	/* Read and store the 16bit result */
	if(CurrentRead == TemperatureConv) {
		/* Read the temperature conversion */
		while (!PIOS_BMP085_Read(BMP085_ADC_MSB, Data, 2)) continue;
		RawTemperature = ((Data[0] << 8) | Data[1]);

		X1 = (RawTemperature - CalibData.AC6) * CalibData.AC5 >> 15;
		X2 = ((int32_t)CalibData.MC << 11) / (X1 + CalibData.MD);
		B5 = X1 + X2;
		Temperature = (B5 + 8) >> 4;
	} else {
		/* Read the pressure conversion */
		while (!PIOS_BMP085_Read(BMP085_ADC_MSB, Data, 3)) continue;
		RawPressure = ((Data[0] << 16) | (Data[1] << 8) | Data[2]) >> (8 - BMP085_OVERSAMPLING);

		B6 = B5 - 4000;
		X1 = (CalibData.B2 * (B6 * B6 >> 12)) >> 11;
		X2 = CalibData.AC2 * B6 >> 11;
		X3 = X1 + X2;
		B3 = ((((int32_t)CalibData.AC1 * 4 + X3) << BMP085_OVERSAMPLING) + 2) >> 2;
		X1 = CalibData.AC3 * B6 >> 13;
		X2 = (CalibData.B1 * (B6 * B6 >> 12)) >> 16;
		X3 = ((X1 + X2) + 2) >> 2;
		B4 = (CalibData.AC4 * (uint32_t) (X3 + 32768)) >> 15;
		B7 = ((uint32_t)RawPressure - B3) * (50000 >> BMP085_OVERSAMPLING);
		P = B7 < 0x80000000 ? (B7 * 2) / B4 : (B7 / B4) * 2;

		X1 = (P >> 8) * (P >> 8);
		X1 = (X1 * 3038) >> 16;
		X2 = (-7357 * P) >> 16;
		Pressure = P + ((X1 + X2 + 3791) >> 4);
	}
}

int16_t PIOS_BMP085_GetTemperature(void)
{
	return Temperature;
}

int32_t PIOS_BMP085_GetPressure(void)
{
	return Pressure;
}

/**
* Reads one or more bytes into a buffer
* \param[in] address BMP085 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 BMP085 blocked by another task (retry it!)
* \return -4 if invalid length
*/
bool PIOS_BMP085_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 = BMP085_I2C_ADDR,
      .rw   = PIOS_I2C_TXN_WRITE,
      .len  = sizeof(addr_buffer),
      .buf  = addr_buffer,
    },
    {
      .info = __func__,
      .addr = BMP085_I2C_ADDR,
      .rw   = PIOS_I2C_TXN_READ,
      .len  = len,
      .buf  = buffer,
    }
  };

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


/**
* Writes one or more bytes to the BMP085
* \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 BMP085 blocked by another task (retry it!)
*/
bool PIOS_BMP085_Write(uint8_t address, uint8_t buffer)
{
  uint8_t data[] = {
    address,
    buffer,
  };

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

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


#endif
