/**
  ******************************************************************************
  * @file    hal_adc.c
  * @brief   This file provides firmware functions to manage the following
  *          functionalities of the Analog to Digital Convertor (ADC)
  *          peripheral:
  *           + Initialization and de-initialization functions
  *             ++ Initialization and Configuration of ADC
  *           + Operation functions
  *             ++ Start, stop, get result of conversions of regular
  *                group, using 3 possible modes: polling, interruption or DMA.
  *           + Control functions
  *             ++ Channels configuration on regular group
  *             ++ Channels configuration on injected group
  *             ++ Analog Watchdog configuration
  *           + State functions
  *             ++ ADC state machine management
  *             ++ Interrupts and flags management
  *          Other functions (extended functions) are available in file
  *
  * @version V1.0
  * @date    15 Dec 2017
  * @author  AE Team.
  * @note
  *
  * Copyright (C) Shanghai Eastsoft Microelectronics Co. Ltd. All rights reserved.
  *
  *********************************************************************************
  */

#include "hal_adc.h"


/** @addtogroup ES32FXXX_HAL
  * @{
  */

/** @defgroup ADC ADC
  * @brief ADC module driver
  * @{
  */

#ifdef HAL_ADC

/** @addtogroup ADC_Private_Functions
  * @{
  */
#ifdef HAL_DMA
static void adc_dma_reg_conv_cplt(void *arg);
static void adc_dma_error(void *arg);
#endif
/**
  * @}
  */


/** @defgroup ADC_Public_Functions ADC Public Functions
  * @{
  */

/** @defgroup ADC_Public_Functions_Group1 Initialization/de-initialization functions
  * @brief    Initialization and Configuration functions
  * @{
  */

/**
  * @brief  Initializes the ADC peripheral and regular group according to
  *         parameters specified in structure "adc_handle_t".
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_init(adc_handle_t* hperh)
{
	hal_status_t tmp_status = OK;

	if (hperh == NULL)
		return ERROR;

	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_DATA_ALIGN_TYPE(hperh->init.data_align));
	assert_param(IS_ADC_SCAN_MODE_TYPE(hperh->init.scan_conv_mode));
	assert_param(IS_ADC_CLK_DIV_TYPE(hperh->init.clk_div));
	assert_param(IS_ADC_NEG_REF_VOLTAGE_TYPE(hperh->init.neg_ref_voltage));
	assert_param(IS_POS_REF_VOLTAGE_TYPE(hperh->init.pos_ref_voltage));
	assert_param(IS_ADC_RESOLUTION_OF_CONV_TYPE(hperh->init.resolution_of_conv));
	assert_param(IS_ADC_NBR_OF_REGULAR_TYPE(hperh->init.nbr_of_conv));
	assert_param(IS_ADC_NBR_OF_DISC_CONV_TYPE(hperh->init.nbr_of_disc_conv));
	assert_param(IS_FUNC_STATE(hperh->init.continuous_conv_mode));
	assert_param(IS_FUNC_STATE(hperh->init.discontinuous_conv_mode));
	assert_param(IS_FUNC_STATE(hperh->init.diff));
	assert_param(IS_FUNC_STATE(hperh->init.lsen));
	assert_param(IS_FUNC_STATE(hperh->init.vrbuf));
	assert_param(IS_FUNC_STATE(hperh->init.vcmen));
	assert_param(IS_FUNC_STATE(hperh->init.vrefen));
	assert_param(IS_FUNC_STATE(hperh->init.gaincal));
	assert_param(IS_FUNC_STATE(hperh->init.offcal));
	assert_param(IS_ADC_REOCS_MODE_TYPE(hperh->init.reocs_mode));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->regular_trig_conv_mode));

	if (hperh->state ==  ADC_STATE_RESET ) {
		hperh->error_code = ADC_ERROR_NONE;
		hperh->lock 	  = UNLOCK;

		if (hperh->adc_init_cbk != NULL)
			hperh->adc_init_cbk(hperh);
	}

	ADC_DISABLE(hperh);
	hperh->state           = ADC_STATE_BUSY_INTERNAL;
	hperh->perh->CR2.ALIGN = hperh->init.data_align;
	hperh->perh->CR2.CONT  = hperh->init.continuous_conv_mode;
	hperh->perh->CR1.SCAN  = hperh->init.scan_conv_mode;
	hperh->perh->CR1.RES   = hperh->init.resolution_of_conv;

	/* Enable discontinuous mode only if continuous mode is enabled */
	if (hperh->init.discontinuous_conv_mode == ENABLE) {
		if (hperh->init.continuous_conv_mode == ENABLE) {
			hperh->perh->CR1.RDISCEN = ENABLE;
			hperh->perh->CR1.DISCNUM = hperh->init.nbr_of_disc_conv;
			hperh->perh->SQLR.RSQL   = hperh->init.nbr_of_conv;
		}
		else {
			hperh->state 	  |= ADC_STATE_ERROR;
			hperh->error_code |= ADC_ERROR_INTERNAL;
			tmp_status 	  =  ERROR;
		}
	}
	else {
		hperh->perh->CR1.RDISCEN = DISABLE;
	}

	/* Configuration of regular group sequencer:
	 * - if scan mode is disabled, regular channels sequence length is set to
	 *   0x00: 1 channel converted (channel on regular rank 1)
	 *   Parameter "nbr_of_conv" is discarded.
	 *   Note: Scan mode is present by hardware on this device and, if
	 *   disabled, discards automatically nb of conversions.
	 * - if scan mode is enabled, regular channels sequence length is set to
	 *   parameter "nbr_of_conv"                                              */
	if (hperh->init.scan_conv_mode == ADC_SCAN_ENABLE)
		hperh->perh->SQLR.RSQL = hperh->init.nbr_of_conv;

	if (hperh->init.continuous_conv_mode == ENABLE)
		hperh->perh->SQLR.RSQL = hperh->init.nbr_of_conv;

	hperh->perh->CCR.GAINCAL = hperh->init.gaincal;
	hperh->perh->CCR.OFFCAL  = hperh->init.offcal;
	hperh->perh->CCR.DIFF    = hperh->init.diff;

	/* if the ADC CLK less than 1MHZ,PWRMOD should be Enable*/
	hperh->perh->CCR.PWRMOD = hperh->init.lsen;
	hperh->perh->CCR.VRBUF  = hperh->init.vrbuf;
	hperh->perh->CCR.VCMEN  = hperh->init.vcmen;
	hperh->perh->CCR.VREFEN = hperh->init.vrefen;
	hperh->perh->CCR.CKDIV  = hperh->init.clk_div;
	hperh->perh->CCR.VRNS   = hperh->init.neg_ref_voltage;
	hperh->perh->CCR.VRPS   = hperh->init.pos_ref_voltage;
	hperh->perh->CR2.REOCS  = hperh->init.reocs_mode;

	if ((hperh->init.pos_ref_voltage == ADC_POS_REF_VOLT_2V)
		        && (hperh->init.vrefen == DISABLE)) {
		hperh->state	  |= ADC_STATE_ERROR;
		hperh->error_code |= ADC_ERROR_INTERNAL;
		tmp_status	  =  ERROR;
	}

	if ((hperh->init.pos_ref_voltage == ADC_POS_REF_VOLT_VREEFP_BUF )
		        && (hperh->init.vrbuf == DISABLE)) {
		hperh->state	  |= ADC_STATE_ERROR;
		hperh->error_code |= ADC_ERROR_INTERNAL;
		tmp_status	   = ERROR;
	}

	if (hperh->regular_trig_conv_mode != ADC_TRIG_CONV_SOFT)
		pis_create(&hperh->regular_pis_handle);

	if (tmp_status == OK) {
		hperh->error_code = ADC_ERROR_NONE;
		hperh->state 	 |= ADC_STATE_READY;
		hperh->state 	 &= ~(ADC_STATE_ERROR | ADC_STATE_REG_BUSY
				      | ADC_STATE_INJ_BUSY | ADC_STATE_BUSY_INTERNAL);
	}

	ADC_ENABLE_IT(hperh,ADC_IT_OVR);
	return tmp_status;
}

/**
  * @brief  Deinitialize the ADC peripheral registers to their default reset
  *         values.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_init_default(adc_handle_t *hperh)
{
	if (hperh == NULL)
		return ERROR;

	assert_param(IS_ADC_TYPE(hperh->perh));

	ADC_DISABLE(hperh);

	ADC_CLEAR_FLAG(hperh,ADC_FLAG_AWD | ADC_FLAG_REOC | ADC_FLAG_JEOC
	               | ADC_FLAG_OVR | ADC_FLAG_JSTRT | ADC_FLAG_RSTRT);

	hperh->perh->CR1.Word     = 0x0;
	hperh->perh->CR2.Word     = 0x0;
	hperh->perh->CCR.Word     = 0x0;
	hperh->perh->HTR.Word     = 0xFFF;
	hperh->perh->LTR.Word     = 0x0;
	hperh->perh->JDR[0].Word  = 0x0;
	hperh->perh->JDR[1].Word  = 0x0;
	hperh->perh->JDR[2].Word  = 0x0;
	hperh->perh->JDR[3].Word  = 0x0;
	hperh->perh->JOFR[0].Word = 0x0;
	hperh->perh->JOFR[1].Word = 0x0;
	hperh->perh->JOFR[2].Word = 0x0;
	hperh->perh->JOFR[3].Word = 0x0;
	hperh->perh->JSQR.Word    = 0x0;
	hperh->perh->RDR.Word     = 0x0;
	hperh->perh->RSQR1.Word   = 0x0;
	hperh->perh->RSQR2.Word   = 0x0;
	hperh->perh->RSQR3.Word   = 0x0;
	hperh->perh->RSQR4.Word   = 0x0;
	hperh->perh->SMPR1.Word   = 0x0;
	hperh->perh->SMPR2.Word   = 0x0;
	hperh->perh->SQLR.Word    = 0x0;

	if (hperh->regular_trig_conv_mode != ADC_TRIG_CONV_SOFT)
		pis_destroy(&hperh->regular_pis_handle);

	if (hperh->injected_trig_conv_mode != ADC_TRIG_CONV_SOFT)
		pis_destroy(&hperh->injected_pis_handle);

	if (hperh->adc_de_init_cbk != NULL)
		hperh->adc_de_init_cbk(hperh);

	hperh->state      = ADC_STATE_RESET;
	hperh->error_code = ADC_ERROR_NONE;
	return OK;
}
/**
  * @}
  */

/** @defgroup ADC_Public_Functions_Group2 IO operation functions
 *  @brief    Input and Output operation functions
 *  @{
 */

/**
  * @brief  Enables ADC, starts conversion of regular group.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_regular_start(adc_handle_t* hperh)
{
	if (hperh == NULL)
		return ERROR;

	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->regular_trig_conv_mode));
	assert_param(IS_ADC_TYPE(hperh->perh));

	__LOCK(hperh);
	ADC_ENABLE(hperh);
	hperh->state &= ~(ADC_STATE_READY | ADC_STATE_REG_EOC);
	hperh->state |= ADC_STATE_REG_BUSY;
	__UNLOCK(hperh);
	ADC_CLEAR_FLAG(hperh,ADC_FLAG_REOC);

	if (hperh->regular_trig_conv_mode == ADC_TRIG_CONV_SOFT) {
		hperh->perh->CR2.RTRIG = 1;
	}

	return OK;
}

/**
  * @brief  Stop ADC conversion of regular group (and injected channels in
  *         case of auto_injection mode), disable ADC peripheral.
  * @note:  ADC peripheral disable is forcing stop of potential
  *         conversion on injected group. If injected group is under use, it
  *         should be preliminarily stopped using adc_injected_stop function.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_regular_stop(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->regular_trig_conv_mode));
	assert_param(IS_ADC_TYPE(hperh->perh));

	__LOCK(hperh);

	ADC_DISABLE(hperh);
	hperh->state &= ~(ADC_STATE_REG_BUSY | ADC_STATE_REG_EOC);
	hperh->state |=  ADC_STATE_READY;

	__UNLOCK(hperh);
	return OK;
}
/**
  * @brief  Wait for regular group conversion to be completed.
  * @note   This function cannot be used in a particular setup: ADC configured  in DMA mode.
  *         In this case, DMA resets the flag EOC and polling cannot be performed on each conversion.
  * @note   When use this function,you should be pay attention to the hperh->init.reocs_mode,
  *         if it is ADC_REOCS_MODE_ALL, it means the function will wait all regular rank conversion  finished.
  *         if it is ADC_REOCS_MODE_ONE, it means the funcion will wait every regular rank conversion finished.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  timeout: Timeout value in millisecond.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_regular_poll_for_conversion(adc_handle_t* hperh, uint32_t timeout)
{
	uint32_t tickstart = 0;

	assert_param(IS_ADC_TYPE(hperh->perh));

	tickstart = __get_tick();

	if (hperh->perh->CR2.ADC_DMA == SET) {
		hperh->state |= ADC_STATE_ERROR;
		__UNLOCK(hperh);
		return ERROR;
	}

	while (hperh->perh->SR.REOC == RESET) {
		if (timeout != HAL_MAX_DELAY ) {
			if ((timeout == 0) || ((__get_tick() - tickstart) > timeout)) {
				hperh->state |= ADC_STATE_TIMEOUT;
				__UNLOCK(hperh);
				return TIMEOUT;
			}
		}
	}

	ADC_CLEAR_FLAG(hperh,ADC_FLAG_RSTRT | ADC_FLAG_REOC);
	hperh->state |= ADC_STATE_REG_EOC;

	if ((hperh->regular_trig_conv_mode == ADC_TRIG_CONV_SOFT)
		       && (hperh->init.continuous_conv_mode == DISABLE)
	               && (hperh->init.scan_conv_mode == ADC_SCAN_DISABLE)) {
		hperh->state &= ~ADC_STATE_REG_BUSY;

		if ((hperh->state & ADC_STATE_INJ_BUSY) == 0)
			hperh->state |= ADC_STATE_READY;
	}
	return OK;
}

/**
  * @brief  Poll for conversion event.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  event_type: the ADC event type.
  *          This parameter can be one of the following values:
  *            ADC_awd_event: ADC Analog watchdog event.
  * @param  timeout: Timeout value in millisecond.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_poll_for_event(adc_handle_t* hperh, adc_event_type_t event_type,uint32_t timeout)
{
	uint32_t tickstart = 0;

	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_EVENT_TYPE(event_type));

	tickstart = __get_tick();

	while ( ADC_GET_FLAG(hperh,event_type) == RESET) {
		if (timeout != HAL_MAX_DELAY ) {
			if ((timeout == 0) || ((__get_tick() - tickstart) > timeout)) {
				hperh->state |= ADC_STATE_TIMEOUT;
				__UNLOCK(hperh);
				return TIMEOUT;
			}
		}
	}

	hperh->state |= ADC_STATE_AWD;
	return OK;
}

/**
  * @brief  Enables ADC, starts conversion of regular group with interruption.
  *         Interruptions enabled in this function:
  *          - REOC (end of conversion of regular group)
  *         Each of these interruptions has its dedicated callback function.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_regular_start_it(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));

	__LOCK(hperh);
	ADC_ENABLE(hperh);
	hperh->state 	 &= ~(ADC_STATE_READY | ADC_STATE_REG_EOC);
	hperh->state 	 |= ADC_STATE_REG_BUSY;
	hperh->error_code = ADC_ERROR_NONE;

	if (hperh->perh->CR1.JAUTO == ENABLE) {
		hperh->state &= ~(ADC_STATE_INJ_EOC);
		hperh->state |= ADC_STATE_INJ_BUSY;
	}

	__UNLOCK(hperh);
	ADC_CLEAR_FLAG(hperh,ADC_FLAG_REOC);
	ADC_ENABLE_IT(hperh,ADC_IT_REOC);

	if (hperh->regular_trig_conv_mode == ADC_TRIG_CONV_SOFT)
		hperh->perh->CR2.RTRIG = 1;

	return OK;
}

/**
  * @brief  Stop ADC conversion of regular group (and injected group in
  *         case of auto_injection mode), disable interrution of
  *         end-of-conversion, disable ADC peripheral.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_regular_stop_it(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));

	__LOCK(hperh);
	ADC_DISABLE(hperh);
	ADC_DISABLE_IT(hperh,ADC_IT_REOC);
	hperh->state |= ADC_STATE_READY;
	hperh->state &= ~(ADC_STATE_REG_BUSY | ADC_STATE_INJ_BUSY);

	__UNLOCK(hperh);
	return OK;
}

#ifdef HAL_DMA
/**
  * @brief  Enables ADC, starts conversion of regular group and transfers result
  *         through DMA.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  buf: The destination Buffer address.
  * @param  size: The length of data to be transferred from ADC peripheral to memory.
  * @param  channel: The DMA channel
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_start_dma(adc_handle_t* hperh, uint16_t *buf, uint16_t size, uint8_t channel)
{
	if ((hperh == NULL) || (buf == NULL) || (size == 0) || (channel > 5))
		return ERROR;

	assert_param(IS_ADC_TYPE(hperh->perh));

	__LOCK(hperh);
	ADC_ENABLE(hperh);
	hperh->state &= ~(ADC_STATE_READY | ADC_STATE_REG_EOC);
	hperh->state |= ADC_STATE_REG_BUSY;

	if (hperh->perh->CR1.JAUTO != 0) {
		hperh->state &= ~(ADC_STATE_INJ_EOC);
		hperh->state |= ADC_STATE_INJ_BUSY;
	}

	if ((hperh->state & ADC_STATE_INJ_BUSY) != 0) {
		hperh->state      &= ~(ADC_STATE_ERROR);
		hperh->error_code &= ~(ADC_ERROR_OVR | ADC_ERROR_DMA);
	}
	else {
		hperh->state     &= ~(ADC_STATE_ERROR);
		hperh->error_code = ADC_ERROR_NONE;
	}
	__UNLOCK(hperh);

	if (hperh->hdma.perh == NULL)
		hperh->hdma.perh = DMA0;

	hperh->hdma.cplt_cbk = adc_dma_reg_conv_cplt;
	hperh->hdma.cplt_arg = hperh;
	hperh->hdma.err_cbk  = adc_dma_error;
	hperh->hdma.err_arg  = hperh;

	dma_config_struct(&hperh->hdma.config);
	hperh->hdma.config.src        = (void *)&hperh->perh->RDR;
	hperh->hdma.config.dst        = (void *)buf;
	hperh->hdma.config.size       = size;
	hperh->hdma.config.data_width = DMA_DATA_SIZE_HALFWORD;
	hperh->hdma.config.src_inc    = DMA_DATA_INC_NONE;
	hperh->hdma.config.dst_inc    = DMA_DATA_INC_HALFWORD;
	hperh->hdma.config.msel       = DMA_MSEL_ADC;
	hperh->hdma.config.msigsel    = DMA_MSIGSEL_ADC;
	hperh->hdma.config.channel    = channel;
	dma_config_basic(&hperh->hdma);
 	pis_create(&hperh->adc_dma_pis_handle);
	hperh->perh->CR2.ADC_DMA = ENABLE;

	if (hperh->regular_trig_conv_mode == ADC_TRIG_CONV_SOFT) {
		hperh->perh->CR2.RTRIG = 1;
	}

	return OK;
}

/**
  * @brief  Stop ADC conversion of regular group (and injected group in
  *         case of auto_injection mode), disable ADC DMA transfer, disable
  *         ADC peripheral.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_stop_dma(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));
	__LOCK(hperh);

	ADC_DISABLE(hperh);
	hperh->perh->CR2.ADC_DMA = DISABLE;
	pis_destroy(&hperh->adc_dma_pis_handle);
	hperh->state            &= ~(ADC_STATE_REG_BUSY | ADC_STATE_INJ_BUSY);
	hperh->state            |= ADC_STATE_READY;
	hperh->perh->CR2.ADC_DMA = DISABLE;

	__UNLOCK(hperh);
	return OK;
}
#endif

/**
  * @brief  Get ADC regular group conversion result.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval ADC group regular conversion data
  */
uint32_t adc_regular_get_value(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));

	hperh->state &= ~ADC_STATE_REG_EOC;
	return hperh->perh->RDR.RDATA;
}

/**
  * @brief  Enables ADC, starts conversion of injected group.
  *         Interruptions enabled in this function: None.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_injected_start(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->injected_trig_conv_mode));

	__LOCK(hperh);
	ADC_ENABLE(hperh);
	hperh->state &= ~(ADC_STATE_READY | ADC_STATE_INJ_EOC);
	hperh->state |= ADC_STATE_INJ_BUSY;

	if ((hperh->state & ADC_STATE_REG_BUSY) == 0)
		hperh->error_code = ADC_ERROR_NONE;

	__UNLOCK(hperh);
	ADC_CLEAR_FLAG(hperh,ADC_FLAG_JEOC);

	if (hperh->perh->CR1.JAUTO == 0){
		if (hperh->injected_trig_conv_mode == ADC_TRIG_CONV_SOFT)
			hperh->perh->CR2.JTRIG = 1;
	}

	return OK;
}

/**
  * @brief  Stop conversion of injected channels. Disable ADC peripheral if
  *         no regular conversion is on going.
  * @note   If ADC must be disabled and if conversion is on going on
  *         regular group, function adc_regular_stop must be used to stop both
  *         injected and regular groups, and disable the ADC.
  * @note   If injected group mode auto-injection is enabled,
  *         function adc_regular_stop must be used.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_injected_stop(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->injected_trig_conv_mode));

	__LOCK(hperh);

	if (((hperh->state & ADC_STATE_REG_BUSY) == 0) && (hperh->perh->CR1.JAUTO == 0)) {
		ADC_DISABLE(hperh);
		hperh->state &= ~(ADC_STATE_REG_BUSY | ADC_STATE_INJ_BUSY | ADC_STATE_INJ_EOC);
		hperh->state |= ADC_STATE_READY;
	}
	else {
		hperh->state |= ADC_STATE_ERROR;
		__UNLOCK(hperh);
		return ERROR;
	}

	__UNLOCK(hperh);
	return OK;
}

/**
  * @brief  Wait for injected group conversion to be completed.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  timeout: Timeout value in millisecond.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_injected_poll_for_conversion(adc_handle_t* hperh, uint32_t timeout)
{
	uint32_t tickstart;

	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->injected_trig_conv_mode));

	tickstart = __get_tick();

	while (hperh->perh->SR.JEOC == 0) {
		if (timeout != HAL_MAX_DELAY) {
			if ((timeout == 0) || ((__get_tick()-tickstart) > timeout)) {
				hperh->state |= ADC_STATE_TIMEOUT;
				__UNLOCK(hperh);
				return TIMEOUT;
			}
		}
	}

	ADC_CLEAR_FLAG(hperh,ADC_FLAG_JSTRT | ADC_FLAG_JEOC | ADC_FLAG_REOC);
	hperh->state |= ADC_STATE_INJ_EOC;

	if (hperh->injected_trig_conv_mode == ADC_TRIG_CONV_SOFT ) {
		hperh->state &= ~(ADC_STATE_INJ_BUSY);
		if ((hperh->state & ADC_STATE_REG_BUSY) == 0)
			hperh->state |= ADC_STATE_READY;
	}

	hperh->state &= ~(ADC_STATE_TIMEOUT);
	__UNLOCK(hperh);
	return OK;
}

/**
  * @brief  Enables ADC, starts conversion of injected group with interruption.
  *          - JEOC (end of conversion of injected group)
  *         Each of these interruptions has its dedicated callback function.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval Status, see @ref hal_status_t..
  */
hal_status_t adc_injected_start_it(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->injected_trig_conv_mode));

	__LOCK(hperh);
	ADC_ENABLE(hperh);
	hperh->state &= ~(ADC_STATE_READY | ADC_STATE_INJ_EOC);
	hperh->state |= ADC_STATE_INJ_BUSY;

	if ((hperh->state & ADC_STATE_REG_BUSY) == 0)
		hperh->error_code = ADC_ERROR_NONE;

	__UNLOCK(hperh);
	ADC_CLEAR_FLAG(hperh,ADC_FLAG_JEOC);
	ADC_ENABLE_IT(hperh,ADC_IT_JEOC);

	if (hperh->perh->CR1.JAUTO == 0) {
		if (hperh->injected_trig_conv_mode == ADC_TRIG_CONV_SOFT)
			hperh->perh->CR2.JTRIG = 1;
	}
	return OK;
}

/**
  * @brief  Stop conversion of injected channels, disable interruption of
  *         end-of-conversion. Disable ADC peripheral if no regular conversion
  *         is on going.
  * @note   If ADC must be disabled and if conversion is on going on
  *         regular group, function adc_regular_stop must be used to stop both
  *         injected and regular groups, and disable the ADC.
  * @note   If injected group mode auto-injection is enabled,
  *         function adc_regular_stop must be used.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval None
  */
hal_status_t adc_injected_stop_it(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->injected_trig_conv_mode));

	__LOCK(hperh);

	if (((hperh->state & ADC_STATE_REG_BUSY) == 0) && (hperh->perh->CR1.JAUTO == 0)) {
		ADC_DISABLE(hperh);
		ADC_DISABLE_IT(hperh,ADC_IT_JEOC);
		hperh->state &= ~(ADC_STATE_REG_BUSY | ADC_STATE_INJ_BUSY);
		hperh->state |= ADC_STATE_READY;
	}
	else {
		ADC_DISABLE_IT(hperh,ADC_IT_JEOC);
		hperh->state |= ADC_STATE_ERROR;
		__UNLOCK(hperh);
		return ERROR;
	}

	__UNLOCK(hperh);
	return OK;
}

/**
  * @brief  Get ADC injected group conversion result.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  injected_rank: the converted ADC injected rank.
  *          This parameter can be one of the following values:
  *            @arg ADC_INJECTED_RANK_1: Injected Channel1 selected
  *            @arg ADC_INJECTED_RANK_2: Injected Channel2 selected
  *            @arg ADC_INJECTED_RANK_3: Injected Channel3 selected
  *            @arg ADC_INJECTED_RANK_4: Injected Channel4 selected
  * @retval ADC group injected conversion data
  */
uint32_t adc_injected_get_value(adc_handle_t* hperh, adc_injected_rank_t injected_rank)
{
	uint32_t tmp_jdr = 0;

	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_INJECTED_RANK_TYPE(injected_rank));

	switch (injected_rank) {
	case ADC_INJECTED_RANK_1:
		tmp_jdr = hperh->perh->JDR[0].JDATA;
		break;
	case ADC_INJECTED_RANK_2:
		tmp_jdr = hperh->perh->JDR[1].JDATA;
		break;
	case ADC_INJECTED_RANK_3:
		tmp_jdr = hperh->perh->JDR[2].JDATA;
		break;
	case ADC_INJECTED_RANK_4:
		tmp_jdr = hperh->perh->JDR[3].JDATA;
		break;
	default:
		break;
	}

	return tmp_jdr;
}

/**
  * @brief  Handles ADC interrupt request
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval None
  */
void adc_irq_handler(adc_handle_t* hperh)
{
	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->injected_trig_conv_mode));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->regular_trig_conv_mode));

	if (ADC_GET_IT_SOURCE(hperh,ADC_IT_REOC) && ADC_GET_FLAG(hperh,ADC_FLAG_REOC)) {
		if ((hperh->state & ADC_STATE_ERROR) == 0)
			hperh->state |= ADC_STATE_REG_EOC;

		if ((hperh->regular_trig_conv_mode == ADC_TRIG_CONV_SOFT)
			         && (hperh->init.continuous_conv_mode == DISABLE)) {
			ADC_DISABLE_IT(hperh,ADC_IT_REOC);
			hperh->state &= ~(ADC_STATE_REG_BUSY);

			if ((hperh->state & ADC_STATE_INJ_BUSY) == 0)
				hperh->state |= ADC_STATE_READY;
		}

		if (hperh->adc_reg_cplt_cbk != NULL)
			hperh->adc_reg_cplt_cbk(hperh);

		ADC_CLEAR_FLAG(hperh,ADC_FLAG_RSTRT | ADC_FLAG_REOC);
	}

	if (ADC_GET_IT_SOURCE(hperh,ADC_IT_JEOC) && ADC_GET_FLAG(hperh,ADC_FLAG_JEOC)) {
		if ((hperh->state & ADC_STATE_ERROR) == 0)
			hperh->state |= ADC_STATE_INJ_EOC;

		if ((hperh->injected_trig_conv_mode == ADC_TRIG_CONV_SOFT)
			   || ((hperh->perh->CR1.JAUTO == 0)
		           && (hperh->regular_trig_conv_mode == ADC_TRIG_CONV_SOFT)
			   && (hperh->init.continuous_conv_mode == DISABLE))) {
			ADC_DISABLE_IT(hperh,ADC_IT_JEOC);
			hperh->state &= ~(ADC_STATE_INJ_BUSY);

			if ((hperh->state & ADC_STATE_REG_BUSY) == 0)
				hperh->state |= ADC_STATE_READY;
		}
		if (hperh->adc_inj_cplt_cbk != NULL)
			hperh->adc_inj_cplt_cbk(hperh);

		ADC_CLEAR_FLAG(hperh,(ADC_FLAG_JSTRT | ADC_FLAG_JEOC));
	}

	if (ADC_GET_IT_SOURCE(hperh,ADC_IT_AWD) && ADC_GET_FLAG(hperh,ADC_FLAG_AWD)) {
		hperh->state |= ADC_STATE_AWD;

		if (hperh->adc_out_of_win_cbk !=NULL)
			hperh->adc_out_of_win_cbk(hperh);

		ADC_CLEAR_FLAG(hperh,ADC_FLAG_AWD);
	}

	if (ADC_GET_IT_SOURCE(hperh,ADC_IT_OVR) && ADC_GET_FLAG(hperh,ADC_FLAG_OVR)) {
		ADC_CLEAR_FLAG(hperh,ADC_FLAG_OVR);
		hperh->error_code |= ADC_ERROR_OVR;
		hperh->state |= ADC_STATE_ERROR;

		if (hperh->adc_ovr_cbk != NULL)
			hperh->adc_ovr_cbk(hperh);
	}
}

/**
  * @}
  */

/** @defgroup ADC_Exported_Functions_Group3 Peripheral Control functions
 *  @brief    Peripheral Control functions
 *  @{
 */

/**
  * @brief  Configures the the selected channel to be linked to the regular
  *         group.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  config: Structure of ADC channel for regular group.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_regular_config_channel(adc_handle_t* hperh, adc_channel_conf_t* config)
{
	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_CHANNELS_TYPE(config->channel));
	assert_param(IS_ADC_REGULAR_RANK_TYPE(config->rank));
	assert_param(IS_ADC_SAMPLING_TIMES_TYPE(config->sampling_time));

	__LOCK(hperh);

	if (config->rank <=ADC_REGULAR_RANK_4 ) {
		hperh->perh->RSQR1.Word &= ~(0x1f <<  ((config->rank - 1) << 3));
		hperh->perh->RSQR1.Word |= (config->channel << ((config->rank - 1) << 3));
	}
	else if (config->rank <= ADC_REGULAR_RANK_8) {
		hperh->perh->RSQR2.Word &= ~(0x1f <<  ((config->rank - 5) << 3));
		hperh->perh->RSQR2.Word |= (config->channel << ((config->rank - 5) << 3));
	}
	else if (config->rank <= ADC_REGULAR_RANK_12) {
		hperh->perh->RSQR3.Word &= ~(0x1f <<  ((config->rank - 9) << 3));
		hperh->perh->RSQR3.Word |= (config->channel << ((config->rank - 9) << 3));
	}
	else {
		hperh->perh->RSQR4.Word &= ~(0x1f <<  ((config->rank - 13) << 3));
		hperh->perh->RSQR4.Word |= (config->channel << ((config->rank - 13) << 3));
	}

	if (config->channel <= 15) {
		hperh->perh->SMPR1.Word &=  ~(0x03 << (config->channel << 1));
		hperh->perh->SMPR1.Word |= config->sampling_time << (config->channel << 1);
	}
	else {
		hperh->perh->SMPR2.Word &=  ~(0x03 << ((config->channel - 16) << 1));
		hperh->perh->SMPR2.Word |= config->sampling_time << ((config->channel - 16) << 1);
	}

	__UNLOCK(hperh);
	return OK;
}

/**
  * @brief  Configures the the selected channel to be linked to the injected
  *         group.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  config: Structure of ADC channel for injected group.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t adc_injected_config_channel(adc_handle_t* hperh, adc_injection_conf_t* config)
{
	hal_status_t tmp_status = OK;

	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_CHANNELS_TYPE(config->injected_channel));
	assert_param(IS_ADC_INJECTED_RANK_TYPE(config->injected_rank));
	assert_param(IS_ADC_SAMPLING_TIMES_TYPE(config->injected_sampling_time));
	assert_param(IS_ADC_INJECTED_OFFSET_TYPE(config->injected_offset));
	assert_param(IS_ADC_NBR_OF_INJECTED_TYPE(config->injected_nbr_of_conv));
	assert_param(IS_FUNC_STATE(config->injected_discontinuous_conv_mode));
	assert_param(IS_FUNC_STATE(config->auto_injected_conv));
	assert_param(IS_ADC_TRIG_CONV_MODE_TYPE(hperh->injected_trig_conv_mode));

	__LOCK(hperh);

	if (hperh->init.scan_conv_mode == ADC_SCAN_DISABLE) {
		switch (config->injected_rank) {
		case ADC_INJECTED_RANK_1:
			hperh->perh->JSQR.JSQ1 = config->injected_channel;
			break;
		case ADC_INJECTED_RANK_2:
			hperh->perh->JSQR.JSQ2 = config->injected_channel;
			break;
		case ADC_INJECTED_RANK_3:
			hperh->perh->JSQR.JSQ3 = config->injected_channel;
			break;
		case ADC_INJECTED_RANK_4:
			hperh->perh->JSQR.JSQ4 = config->injected_channel;
			break;
		default:
			hperh->state      |= ADC_STATE_ERROR;
			hperh->error_code |= ADC_ERROR_INTERNAL;
			tmp_status         = ERROR;
			break;
		}
	}
	else {
		hperh->perh->SQLR.JSQL = config->injected_nbr_of_conv;

		if (config->injected_rank <= config->injected_nbr_of_conv) {
			hperh->perh->JSQR.Word &= ~(0x1f << ((config->injected_rank - 1) << 3));
			hperh->perh->JSQR.Word |= config->injected_channel
			                                  << ((config->injected_rank - 1) << 3);
		}
		else {
			hperh->perh->JSQR.Word &= ~(0x1f << ((config->injected_rank - 1) << 3));
		}
	}

	if (config->auto_injected_conv == ENABLE) {
		if (hperh->injected_trig_conv_mode == ADC_TRIG_CONV_SOFT) {
			hperh->perh->CR1.JAUTO = ENABLE;
		}
		else {
			hperh->state      |= ADC_STATE_ERROR;
			hperh->error_code |= ADC_ERROR_INTERNAL;
			tmp_status         = ERROR;
		}
	}

	if (config->injected_discontinuous_conv_mode == ENABLE) {
		if (config->auto_injected_conv == DISABLE) {
			hperh->perh->SQLR.JSQL   = config->injected_nbr_of_conv;
			hperh->perh->CR1.JDISCEN = ENABLE;
		}
		else {
			hperh->state      |= ADC_STATE_ERROR;
			hperh->error_code |= ADC_ERROR_INTERNAL;
			tmp_status         = ERROR;
		}
	}

	if (config->injected_channel <= 15) {
		hperh->perh->SMPR1.Word &=  ~(0x03 << (config->injected_channel << 1));
		hperh->perh->SMPR1.Word |= config->injected_sampling_time
		                                   << (config->injected_channel << 1);
	}
	else {
		hperh->perh->SMPR2.Word &=  ~(0x03 << ((config->injected_channel - 16) << 1));
		hperh->perh->SMPR2.Word |= config->injected_sampling_time
		                                   << ((config->injected_channel - 16) << 1);
	}

	switch (config->injected_rank) {
	case ADC_INJECTED_RANK_1:
		hperh->perh->JOFR[0].JOFF = config->injected_offset;
		break;
	case ADC_INJECTED_RANK_2:
		hperh->perh->JOFR[1].JOFF = config->injected_offset;
		break;
	case ADC_INJECTED_RANK_3:
		hperh->perh->JOFR[2].JOFF = config->injected_offset;
		break;
	case ADC_INJECTED_RANK_4:
		hperh->perh->JOFR[3].JOFF = config->injected_offset;
		break;
	default:
		break;
	}

	if (hperh->injected_trig_conv_mode != ADC_TRIG_CONV_SOFT)
		pis_create(&hperh->injected_pis_handle);

	__UNLOCK(hperh);
	return tmp_status;
}

/**
  * @brief  Configures the analog watchdog.
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @param  analog_wdg_config: Structure of ADC analog watchdog configuration
  * @retval hal status
  */
hal_status_t adc_analog_wdg_config(adc_handle_t* hperh, adc_analog_wdg_conf_t* analog_wdg_config)
{

	assert_param(IS_ADC_TYPE(hperh->perh));
	assert_param(IS_ADC_ANALOG_WTD_MODE_TYPE(analog_wdg_config->watchdog_mode));
	assert_param(IS_FUNC_STATE(analog_wdg_config->it_mode));
	assert_param(IS_HTR_TYPE(analog_wdg_config->high_threshold));
	assert_param(IS_LTR_TYPE(analog_wdg_config->low_threshold));

	__LOCK(hperh);

	if ((analog_wdg_config->watchdog_mode == ADC_ANAWTD_SING_REG)
		|| (analog_wdg_config->watchdog_mode == ADC_ANAWTD_SING_INJEC)
	        || (analog_wdg_config->watchdog_mode == ADC_ANAWTD_SING_REGINJEC))
		assert_param(IS_ADC_CHANNELS_TYPE(analog_wdg_config->channel));

	if (analog_wdg_config->it_mode == DISABLE)
		ADC_DISABLE_IT(hperh,ADC_IT_AWD);
	else
		ADC_ENABLE_IT(hperh,ADC_IT_AWD);

	hperh->perh->CR1.JAWDEN = 0;
	hperh->perh->CR1.RAWDEN = 0;
	hperh->perh->CR1.AWDSGL = 0;
	hperh->perh->CR1.Word  |= analog_wdg_config->watchdog_mode;

	if (hperh->perh->CR1.AWDSGL == 1)
		hperh->perh->CR1.AWDCH = analog_wdg_config->channel;

	hperh->perh->LTR.LT = analog_wdg_config->low_threshold;
	hperh->perh->HTR.HT = analog_wdg_config->high_threshold;

	__UNLOCK(hperh);
	return OK;
}
/**
  * @}
  */

/** @defgroup ADC_Exported_Functions_Group4 Peripheral State functions
 *  @brief    Peripheral State functions
 *  @{
 */

/**
  * @brief  return the ADC state
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval state
  */
uint32_t adc_get_state(adc_handle_t* hperh)
{
	return hperh->state;
}

/**
  * @brief  Return the ADC error code
  * @param  hperh: Pointer to a adc_handle_t structure that contains
  *         the configuration information for the specified ADC module.
  * @retval ADC Error Code
  */
uint32_t adc_get_error(adc_handle_t *hperh)
{
	return hperh->error_code;
}

/**
  *@}
  */

/**
  *@}
  */

/** @defgroup ADC_Private_Functions ADC Private Functions
  * @{
  */

#ifdef HAL_DMA
/**
  * @brief  DMA transfer complete callback.
  * @param  arg: argument of the call back.
  * @retval None
  */
static void adc_dma_reg_conv_cplt(void *arg)
{
	adc_handle_t * hperh = (adc_handle_t *)arg;

	hperh->perh->CR2.ADC_DMA = DISABLE;

	if (hperh->adc_reg_cplt_cbk != NULL)
		hperh->adc_reg_cplt_cbk(hperh);

}

/**
  * @brief  DMA error callback
  * @param  arg: argument of the call back.
  * @retval None
  */
static void adc_dma_error(void *arg)
{
	adc_handle_t * hperh = (adc_handle_t *)arg;
	hperh->state |= ADC_STATE_ERROR;
	hperh->error_code |= ADC_ERROR_DMA;

	if (hperh->adc_error_cbk != NULL)
		hperh->adc_error_cbk(hperh);
}
#endif
/**
  *@}
  */

#endif /* HAL_ADC */

/**
  *@}
  */

/**
  *@}
  */
