/******************************************************************//**
 * @file 		drv_adc.c
 * @brief 	ADC driver of RT-Thread RTOS for EFM32
 * 	COPYRIGHT (C) 2011, RT-Thread Development Team
 * @author 	onelife
 * @version 	0.4 beta
 **********************************************************************
 * @section License
 * The license and distribution terms for this file may be found in the file LICENSE in this 
 * distribution or at http://www.rt-thread.org/license/LICENSE
 **********************************************************************
 * @section Change Logs
 * Date			Author		Notes
 * 2011-02-21	onelife		Initial creation for EFM32
 *********************************************************************/
 
/******************************************************************//**
* @addtogroup efm32
* @{
*********************************************************************/

/* Includes -------------------------------------------------------------------*/
#include "board.h"
#include "drv_adc.h"

/* Private typedef -------------------------------------------------------------*/
/* Private define --------------------------------------------------------------*/
/* Private macro --------------------------------------------------------------*/
/* Private variables ------------------------------------------------------------*/
#ifdef RT_USING_ADC0
	static struct rt_device adc0_device;
#endif

/* Private function prototypes ---------------------------------------------------*/
rt_uint32_t efm32_adc_calibration(
	ADC_TypeDef 			*adc, 
	ADC_Ref_TypeDef			ref,
	ADC_SingleInput_TypeDef	input);

/* Private functions ------------------------------------------------------------*/
/******************************************************************//**
 * @brief
 *   Initialize ADC device
 *
 * @details
 *
 * @note
 *
 * @param[in] dev
 *   Pointer to device descriptor
 *
 * @return
 *   Error code
 *********************************************************************/
 static rt_err_t rt_adc_init(rt_device_t dev)
{
	RT_ASSERT(dev != RT_NULL);

	rt_uint32_t temp;
	
	struct efm32_adc_device_t *adc;

	adc = (struct efm32_adc_device_t *)(dev->user_data);

	temp = efm32_adc_calibration(adc->adc_device, ADC_INIT_REF, ADC_INIT_CH);

#ifdef RT_ADC_DEBUG
	rt_kprintf("adc->CAL = %x\n", temp);
#endif

	return RT_EOK;
}

/******************************************************************//**
* @brief
*	Configure ADC device
*
* @details
*
* @note
*
* @param[in] dev
*	Pointer to device descriptor
*
* @param[in] cmd
*	ADC control command
*
* @param[in] args
*	Arguments
*
* @return
*	Error code
*********************************************************************/
static rt_err_t rt_adc_control(
	rt_device_t 	dev, 
	rt_uint8_t 		cmd, 
	void 			*args)
{
	RT_ASSERT(dev != RT_NULL);

	struct efm32_adc_device_t *adc;

	adc = (struct efm32_adc_device_t *)(dev->user_data);

	switch (cmd)
	{
	case RT_DEVICE_CTRL_SUSPEND:
		/* Suspend device */
		dev->flag |= RT_DEVICE_FLAG_SUSPENDED;
		adc->adc_device->CMD = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP;
		break;

	case RT_DEVICE_CTRL_RESUME:
		/* Resume device */
		dev->flag &= ~RT_DEVICE_FLAG_SUSPENDED;
		
		switch (adc->mode)
		{
		case ADC_MODE_SINGLE:
			ADC_Start(adc->adc_device, adcStartSingle);
			break;
		
		case ADC_MODE_SCAN:
			ADC_Start(adc->adc_device, adcStartScan);
			break;
		
		case ADC_MODE_TAILGATE:
			ADC_Start(adc->adc_device, adcStartScanAndSingle);
			break;
		
		default:
			return -RT_ERROR;
		}
		break;

	case RT_DEVICE_CTRL_ADC_MODE:
		{
			/* change device setting */
			struct efm32_adc_control_t *control;

			control = (struct efm32_adc_control_t *)args;

			switch (control->mode)
			{
			case ADC_MODE_SINGLE:
				ADC_InitSingle(adc->adc_device, control->singleInit);
				break;

			case ADC_MODE_SCAN:
				ADC_InitScan(adc->adc_device, control->scanInit);
				break;

			case ADC_MODE_TAILGATE:
				ADC_InitSingle(adc->adc_device, control->singleInit);
				ADC_InitScan(adc->adc_device, control->scanInit);
				break;

			default:
				return -RT_ERROR;
			}

			adc->mode = control->mode;
		}
		break;
		
	case RT_DEVICE_CTRL_ADC_RESULT:
		switch (adc->mode)
		{
		case ADC_MODE_SINGLE:
			while (adc->adc_device->STATUS & ADC_STATUS_SINGLEACT);
			*((rt_uint32_t *)args) = ADC_DataSingleGet(adc->adc_device);
			break;

		case ADC_MODE_SCAN:
			while (adc->adc_device->STATUS & ADC_STATUS_SCANACT);
			*((rt_uint32_t *)args) = ADC_DataScanGet(adc->adc_device);
			break;

		case ADC_MODE_TAILGATE:
			while (adc->adc_device->STATUS & ADC_STATUS_SCANACT);
			*((rt_uint32_t *)args) = ADC_DataScanGet(adc->adc_device);
			
			while (adc->adc_device->STATUS & ADC_STATUS_SINGLEACT);
			*((rt_uint32_t *)args + 1) = ADC_DataSingleGet(adc->adc_device);
			break;

		default:
			return -RT_ERROR;
		}
		break;
	}

	return RT_EOK;
}

/******************************************************************//**
* @brief
*	Register ADC device
*
* @details
*
* @note
*
* @param[in] device
*	Pointer to device descriptor
*
* @param[in] name
*	Device name
*
* @param[in] flag
*	Configuration flags
*
* @param[in] adc
*	Pointer to ADC device descriptor 
*
* @return
*	Error code
*********************************************************************/
rt_err_t rt_hw_adc_register(
	rt_device_t		device, 
	const char		*name, 
	rt_uint32_t		flag, 
	struct efm32_adc_device_t *adc)
{
	RT_ASSERT(device != RT_NULL);

	device->type 		= RT_Device_Class_Char; /* fixme: should be adc type */
	device->rx_indicate = RT_NULL;
	device->tx_complete = RT_NULL;
	device->init 		= rt_adc_init;
	device->open		= RT_NULL;
	device->close		= RT_NULL;
	device->read 		= RT_NULL;
	device->write 		= RT_NULL;
	device->control 	= rt_adc_control;
	device->user_data	= adc;

	/* register a character device */
	return rt_device_register(device, name, flag);
}

/******************************************************************//**
* @brief
*	Initialize all ADC module related hardware and register ADC device to kernel
*
* @details
*
* @note
*
*********************************************************************/
void rt_hw_adc_init(void)
{
	struct efm32_adc_device_t 	*adc;
	ADC_Init_TypeDef 			init = ADC_INIT_DEFAULT;

	// TODO: Fixed oversampling rate?
	init.ovsRateSel	= adcOvsRateSel4096;
	init.timebase 	= ADC_TimebaseCalc(0);
	init.prescale 	= ADC_PrescaleCalc(ADC_CONVERT_FREQUENCY, 0);

	
#ifdef RT_USING_ADC0
	adc = rt_malloc(sizeof(struct efm32_adc_device_t));
	if (adc == RT_NULL)
	{
#ifdef RT_ADC_DEBUG
		rt_kprintf("no memory for ADC driver\n");
#endif
		return;
	}
	adc->adc_device	= ADC0;
	adc->mode 		= ADC_MODE_SINGLE;

	/* Enable clock for ADCn module */
	CMU_ClockEnable(cmuClock_ADC0, true);

	/* Reset */
	 ADC_Reset(ADC0);

	/* Configure ADC */
	ADC_Init(adc->adc_device, &init);

	rt_hw_adc_register(&adc0_device, RT_ADC0_NAME, EFM32_NO_DATA, adc);	
#endif
}


/***************************************************************************//**
 * @brief
 *   Calibrate offset and gain for the specified reference.
 *   Supports currently only single ended gain calibration.
 *   Could easily be expanded to support differential gain calibration.
 *
 * @details
 *   The offset calibration routine measures 0 V with the ADC, and adjust
 *   the calibration register until the converted value equals 0.
 *   The gain calibration routine needs an external reference voltage equal
 *   to the top value for the selected reference. For example if the 2.5 V
 *   reference is to be calibrated, the external supply must also equal 2.5V.
 *
 * @param[in] adc
 *   Pointer to ADC peripheral register block.
 *
 * @param[in] ref
 *   Reference used during calibration. Can be both external and internal
 *   references.
 *
 * @param[in] input
 *   Input channel used during calibration.
 *
 * @return
 *   The final value of the calibration register, note that the calibration
 *   register gets updated with this value during the calibration.
 *   No need to load the calibration values after the function returns.
 ******************************************************************************/
rt_uint32_t efm32_adc_calibration(
	ADC_TypeDef 			*adc, 
	ADC_Ref_TypeDef			ref,
	ADC_SingleInput_TypeDef	input)
{
	rt_uint32_t 	cal;
	rt_int32_t 		sample;
	rt_int8_t 		high, mid, low, tmp;
	ADC_InitSingle_TypeDef singleInit 	= ADC_INITSINGLE_DEFAULT;

	/* Init for single conversion use, measure diff 0 with selected reference. */
	singleInit.reference 	= ref;
	singleInit.input 		= adcSingleInpDiff0;
	singleInit.acqTime 		= adcAcqTime32;
	singleInit.diff 		= true;
	/* Enable oversampling rate */
	singleInit.resolution	= adcResOVS;
	ADC_InitSingle(adc, &singleInit);

	/* ADC is now set up for offset calibration */
	/* Offset calibration register is a 7 bit signed 2's complement value. */
	/* Use unsigned indexes for binary search, and convert when calibration */
	/* register is written to. */
	high = 63;
	low = -64;

	/* Do binary search for offset calibration*/
	while (low < high)
	{
		/* Calculate midpoint */
		mid = low + (high - low) / 2;

		/* Midpoint is converted to 2's complement and written to both scan and */
		/* single calibration registers */
		cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SCANOFFSET_MASK);
		tmp = mid < 0 ? (mid & 0x3F ^ 0x3F | 0x40) + 1 : mid;
		cal |= tmp << _ADC_CAL_SINGLEOFFSET_SHIFT;
		cal |= tmp << _ADC_CAL_SCANOFFSET_SHIFT;
#ifdef RT_ADC_DEBUG
		rt_kprintf("adc->CAL = %x, cal = %x, tmp = %x\n", adc->CAL, cal, tmp);
#endif			
		adc->CAL = cal;

		/* Do a conversion */
		ADC_Start(adc, adcStartSingle);

		/* Wait while conversion is active */
		while (adc->STATUS & ADC_STATUS_SINGLEACT) ;

		/* Get ADC result */
		sample = ADC_DataSingleGet(adc);

		/* Check result and decide in which part of to repeat search */
		/* Calibration register has negative effect on result */
		if (sample < 0)
		{
			/* Repeat search in bottom half. */
			high = mid;
		}
		else if (sample > 0)
		{
			/* Repeat search in top half. */
			low = mid + 1;
		}
		else
		{
			/* Found it, exit while loop */
			break;
		}
	}
#ifdef RT_ADC_DEBUG
	rt_kprintf("adc->CAL = %x\n", adc->CAL);
#endif

	/* Now do gain calibration, only input and diff settings needs to be changed */
	adc->SINGLECTRL &= ~(_ADC_SINGLECTRL_INPUTSEL_MASK | _ADC_SINGLECTRL_DIFF_MASK);
	adc->SINGLECTRL |= (input << _ADC_SINGLECTRL_INPUTSEL_SHIFT);
	adc->SINGLECTRL |= (false << _ADC_SINGLECTRL_DIFF_SHIFT);

	/* ADC is now set up for gain calibration */
	/* Gain calibration register is a 7 bit unsigned value. */
	high = 127;
	low = 0;

	/* Do binary search for gain calibration */
	while (low < high)
	{
		/* Calculate midpoint and write to calibration register */
		mid = low + (high - low) / 2;

		/* Midpoint is converted to 2's complement */
		cal      = adc->CAL & ~(_ADC_CAL_SINGLEGAIN_MASK | _ADC_CAL_SCANGAIN_MASK);
		cal     |= mid << _ADC_CAL_SINGLEGAIN_SHIFT;
		cal     |= mid << _ADC_CAL_SCANGAIN_SHIFT;
#ifdef RT_ADC_DEBUG
		rt_kprintf("adc->CAL = %x, cal = %x, mid = %x\n", adc->CAL, cal, mid);
#endif		
		adc->CAL = cal;

		/* Do a conversion */
		ADC_Start(adc, adcStartSingle);

		/* Wait while conversion is active */
		while (adc->STATUS & ADC_STATUS_SINGLEACT) ;

		/* Get ADC result */
		sample = ADC_DataSingleGet(adc);

		/* Check result and decide in which part to repeat search */
		/* Compare with a value atleast one LSB's less than top to avoid overshooting */
		/* Since oversampling is used, the result is 16 bits, but a couple of lsb's */
		/* applies to the 12 bit result value, if 0xffe is the top value in 12 bit, this */
		/* is in turn 0xffe0 in the 16 bit result. */
		/* Calibration register has positive effect on result */
		if (sample > 0xffd0)
		{
		  /* Repeat search in bottom half. */
		  high = mid;
		}
		else if (sample < 0xffd0)
		{
		  /* Repeat search in top half. */
		  low = mid + 1;
		}
		else
		{
		  /* Found it, exit while loop */
		  break;
		}
	}
#ifdef RT_ADC_DEBUG
	rt_kprintf("adc->CAL = %x\n", adc->CAL);
#endif

	return adc->CAL;
}

/******************************************************************//**
 * @}
*********************************************************************/
