/* ----------------------------------------------------------------------------
 *         SAM Software Package License
 * ----------------------------------------------------------------------------
 * Copyright (c) 2015, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

/** \addtogroup adc_module Working with ADC
 * \ingroup peripherals_module
 * \section Purpose
 * The ADC driver provides the interface to configure and use the ADC peripheral.
 * \n
 *
 * It converts the analog input to digital format. The converted result could be
 * 12bit.
 *
 * To Enable a ADC conversion,the user has to follow these few steps:
 * <ul>
 * <li> Select an appropriate reference voltage on ADVREF   </li>
 * <li> Configure the ADC according to its requirements and special needs,which
 * could be  broken down into several parts:
 * -#   Select the resolution by setting or clearing ADC_MR_LOWRES bit in
 *      ADC_MR (Mode Register)
 * -#   Set ADC clock by setting ADC_MR_PRESCAL bits in ADC_MR, the clock is
 *      calculated with ADCClock = MCK / ( (PRESCAL+1) * 2 )
 * -#   Set Startup Time,Tracking Clock cycles and Transfer Clock respectively
 *      in ADC_MR.
 </li>
 * <li> Start conversion by setting ADC_CR_START in ADC_CR. </li>
 * </ul>
 *
 * \section Usage
 * <ul>
 * <li> Initialize the ADC controller using adc_initialize().
 * <li> ADC clock and timing configuration using adc_set_clock() and adc_set_timing().
 * <li> For ADC trigger using adc_set_trigger(), adc_set_trigger_mode() and
 * adc_set_trigger_period().
 * <li> For ADC sequence mode using adc_set_sequence_mode(), adc_set_sequence() and
 * adc_set_sequence_by_list().
 * <li> For ADC compare mode using adc_set_compare_channel(), adc_set_compare_mode()
 * and adc_set_comparison_window().
 * <li> ADC works with touchscreen using adc_ts_calibration(), adc_set_ts_mode(),
 * adc_set_ts_debounce(), adc_set_ts_pen_detect(), adc_set_ts_average(),
 * adc_get_ts_xposition(), adc_get_ts_yposition() and adc_get_ts_pressure().
 * </li>
 * </ul>
 *
 * For more accurate information, please look at the ADC section of the
 * Datasheet.
 *
 * Related files :\n
 * \ref adc.c\n
 * \ref adc.h\n
 */
/**
 * \file
 *
 * Implementation of Analog-to-Digital Converter (ADC).
 *
 */
/*----------------------------------------------------------------------------
 *        Headers
 *----------------------------------------------------------------------------*/

#include "chip.h"
#include "peripherals/adc.h"
#include "peripherals/pmc.h"

#include "trace.h"

#include <stdio.h>

/*----------------------------------------------------------------------------
 *        Local definitions
 *----------------------------------------------------------------------------*/

#ifndef ADC_MR_TRANSFER
/* for compatibility with older peripheral versions */
#define ADC_MR_TRANSFER(x) 0
#endif

/*----------------------------------------------------------------------------
 *        Local variables
 *----------------------------------------------------------------------------*/

/** Current working clock */
static uint32_t _adc_clock = 0;

/*----------------------------------------------------------------------------
 *        Exported functions
 *----------------------------------------------------------------------------*/

uint32_t adc_num_channels(void)
{
	return ARRAY_SIZE(ADC->ADC_CDR);
}

/**
 * \brief Initialize the ADC controller
 *
 */
void adc_initialize(void)
{
	/* Enable peripheral clock */
	pmc_enable_peripheral(ID_ADC);

	/*  Reset the controller */
	ADC->ADC_CR = ADC_CR_SWRST;

	/* Reset Mode Register */
	ADC->ADC_MR = 0;

}

/**
 * \brief Set ADC clock.
 *
 * \param clk adc clock frequency
 *
 * \return ADC clock
 */
uint32_t adc_set_clock(uint32_t clk)
{
	uint32_t prescale, mode_reg;
	uint32_t mck = pmc_get_peripheral_clock(ID_ADC);
	/* Formula for PRESCAL is:
	   ADCClock = MCK / ( (PRESCAL+1) * 2 )
	   PRESCAL = (MCK / (2 * ADCCLK)) + 1
	   First, we do the division, multiplied by 10 to get higher precision
	   If the last digit is not zero, we round up to avoid generating a higher
	   than required frequency. */
	prescale = (mck * 5) / clk;
	if (prescale % 10)
		prescale = prescale / 10;
	else {
		if (prescale == 0)
			return 0;
		prescale = prescale / 10 - 1;
	}

	mode_reg = ADC_MR_PRESCAL(prescale);
	if (mode_reg == 0)
		return 0;

	mode_reg |= (ADC->ADC_MR & ~ADC_MR_PRESCAL_Msk);
	ADC->ADC_MR = mode_reg;

	_adc_clock = mck / (prescale + 1) / 2;
	//_adc_clock = _adc_clock / 1000 * 1000;
	return _adc_clock;
}

void adc_enable_it(uint32_t mask)
{
	ADC->ADC_IER |= mask;
}

void adc_disable_it(uint32_t mask)
{
	ADC->ADC_IER &= ~mask;
}

/**
 * \brief Set ADC timing.
 *
 * \param startup startup value
 * \param tracking tracking value
 * \param settling settling value
 */
void adc_set_timing(uint32_t startup, uint32_t tracking, uint32_t settling)
{
	uint32_t mode_reg;

#ifndef CONFIG_HAVE_ADC_SETTLING_TIME
	if (settling) {
		settling = 0;
		trace_warning("adc: Analog settling time not supported, IGNORED!\r\n");
	}
#endif

	mode_reg = ADC->ADC_MR;
	mode_reg &= (~ADC_MR_STARTUP_Msk) & (~ADC_MR_TRACKTIM_Msk);

	/* Formula:
	 *     Startup  Time = startup value / ADCClock
	 *     Transfer Time = (TRANSFER * 2 + 3) / ADCClock
	 *     Tracking Time = (TRACKTIM + 1) / ADCClock
	 *     Settling Time = settling value / ADCClock
	 */
	mode_reg |= ADC_MR_STARTUP(startup);
	mode_reg |= ADC_MR_TRACKTIM(tracking);
	mode_reg |= ADC_MR_TRANSFER(2);
#ifdef CONFIG_HAVE_ADC_SETTLING_TIME
	mode_reg |=  ADC_MR_SETTLING(settling);
#endif
	ADC->ADC_MR |= mode_reg;
}

void adc_set_trigger_mode(uint32_t mode)
{
	uint32_t trg_reg = ADC->ADC_TRGR & ~ADC_TRGR_TRGMOD_Msk;
	ADC->ADC_TRGR = trg_reg | mode;
}

void adc_set_sleep_mode(uint8_t enable)
{
	if (enable) {
		ADC->ADC_MR |= ADC_MR_SLEEP;
	} else {
		ADC->ADC_MR &= ~ADC_MR_SLEEP;
	}
}

/**
 * \brief Enable/Disable seqnence mode.
 *
 * \param enable Enable/Disable seqnence mode.
 */
void adc_set_sequence_mode(uint8_t enable)
{
	if (enable) {
		/* User Sequence Mode: The sequence respects what is defined in
		   ADC_SEQR1 and ADC_SEQR2 */
		ADC->ADC_MR |= ADC_MR_USEQ;
	} else {
		/* Normal Mode: The controller converts channels in a simple numeric order. */
		ADC->ADC_MR &= ~ADC_MR_USEQ;
	}
}

/**
 * \brief Set channel sequence.
 *
 * \param seq1 Sequence 1 ~ 8  channel number.
 * \param seq2 Sequence 9 ~ 16 channel number.
 */

void adc_set_sequence(uint32_t seq1, uint32_t seq2)
{
	ADC->ADC_SEQR1 = seq1;
#ifdef CONFIG_HAVE_ADC_SEQ_REG2
	ADC->ADC_SEQR2 = seq2;
#endif
}

/**
 * \brief Set channel sequence by given channel list.
 *
 * \param channel_list Channel list.
 * \param len  Number of channels in list.
 */

void adc_set_sequence_by_list(uint8_t channel_list[], uint8_t len)
{
	uint8_t i;
	uint8_t shift;

	if (len <= 8) {
		ADC->ADC_SEQR1 = 0;
		for (i = 0, shift = 0; i < len; i++, shift += 4) {
			if (i >= len) return;
			ADC->ADC_SEQR1 |= channel_list[i] << shift;
		}
	}
	else {
		ADC->ADC_SEQR1 = 0;
		for (i = 0, shift = 0; i < 8; i++, shift += 4) {
			if (i >= len) return;
			ADC->ADC_SEQR1 |= channel_list[i] << shift;
		}
#ifdef CONFIG_HAVE_ADC_SEQ_REG2
		ADC->ADC_SEQR2 = 0;
		for (i = 0, shift = 0; i < (len-8); i++, shift += 4) {
			if (i >= len) return;
			ADC->ADC_SEQR2 |= channel_list[8+i] << shift;
		}
#endif
	}
}

void adc_set_tag_enable(uint8_t enable)
{
	if (enable) {
		ADC->ADC_EMR |= ADC_EMR_TAG;
	} else {
		ADC->ADC_EMR &= ~ADC_EMR_TAG;
	}
}

/**
 * \brief Set compare channel.
 *
 * \param channel channel number to be set, xx for all channels
 */
void adc_set_compare_channel(uint32_t channel)
{
	assert(channel <= adc_num_channels());

	if (channel < adc_num_channels()) {
		ADC->ADC_EMR &= ~(ADC_EMR_CMPALL);
		ADC->ADC_EMR &= ~(ADC_EMR_CMPSEL_Msk);
		ADC->ADC_EMR |= (channel << ADC_EMR_CMPSEL_Pos);
	} else {
		ADC->ADC_EMR |= ADC_EMR_CMPALL;
	}
}

/**
 * \brief Set compare mode.
 *
 * \param mode compare mode
 */
void adc_set_compare_mode(uint32_t mode)
{
	ADC->ADC_EMR &= ~(ADC_EMR_CMPMODE_Msk);
	ADC->ADC_EMR |= (mode & ADC_EMR_CMPMODE_Msk);
}

void adc_set_comparison_window(uint32_t window)
{
	ADC->ADC_CWR = window;
}

uint8_t adc_check_configuration(void)
{
	uint32_t mode_reg;
	uint32_t prescale;
	uint32_t clock;
	uint32_t mck = pmc_get_peripheral_clock(ID_ADC);

	mode_reg = ADC->ADC_MR;

	prescale = (mode_reg & ADC_MR_PRESCAL_Msk) >> ADC_MR_PRESCAL_Pos;
	/* Formula: ADCClock = MCK / ( (PRESCAL+1) * 2 ) */
	clock = mck / ((prescale + 1) * 2);
	if (clock > ADC_CLOCK_MAX) {
		printf ("ADC clock is too high (out of specification: %d Hz)\r\n",
		     (int) ADC_CLOCK_MAX);
		return 1;
	}

	return 0;
}

uint32_t adc_get_converted_data(uint32_t channel)
{
	assert(channel < adc_num_channels());

	if (channel < adc_num_channels()) {
		return ADC->ADC_CDR[channel];
	} else {
		return 0;
	}
}

void adc_set_startup_time(uint32_t startup)
{
	uint32_t start;
	uint32_t mode_reg;

	if (_adc_clock == 0)
		return;
	/* Formula for STARTUP is:
	   STARTUP = (time x ADCCLK) / (1000000) - 1
	   Division multiplied by 10 for higher precision */

	start = (startup * _adc_clock) / (100000);
	if (start % 10)
		start /= 10;
	else {
		start /= 10;
		if (start)
			start--;
	}
	if (start > 896)
		mode_reg = ADC_MR_STARTUP_SUT960;
	else if (start > 832)
		mode_reg = ADC_MR_STARTUP_SUT896;
	else if (start > 768)
		mode_reg = ADC_MR_STARTUP_SUT832;
	else if (start > 704)
		mode_reg = ADC_MR_STARTUP_SUT768;
	else if (start > 640)
		mode_reg = ADC_MR_STARTUP_SUT704;
	else if (start > 576)
		mode_reg = ADC_MR_STARTUP_SUT640;
	else if (start > 512)
		mode_reg = ADC_MR_STARTUP_SUT576;
	else if (start > 112)
		mode_reg = ADC_MR_STARTUP_SUT512;
	else if (start > 96)
		mode_reg = ADC_MR_STARTUP_SUT112;
	else if (start > 80)
		mode_reg = ADC_MR_STARTUP_SUT96;
	else if (start > 64)
		mode_reg = ADC_MR_STARTUP_SUT80;
	else if (start > 24)
		mode_reg = ADC_MR_STARTUP_SUT64;
	else if (start > 16)
		mode_reg = ADC_MR_STARTUP_SUT24;
	else if (start > 8)
		mode_reg = ADC_MR_STARTUP_SUT16;
	else if (start > 0)
		mode_reg = ADC_MR_STARTUP_SUT8;
	else
		mode_reg = ADC_MR_STARTUP_SUT0;

	mode_reg |= ADC->ADC_MR & ~ADC_MR_STARTUP_Msk;
	ADC->ADC_MR = mode_reg;
}

#ifdef CONFIG_HAVE_ADC_INPUT_OFFSET
/**
 * \brief Enable differential input for the specified channel.
 *
 * \param channel ADC channel number.
 */
void adc_enable_channel_differential_input (uint32_t channel)
{
	/* (ADC_COR) Differential Inputs for Channel n */
	ADC->ADC_COR |= 0x01u << (16 + channel);
}

/**
 * \brief Disable differential input for the specified channel.
 *
 * \param channel ADC channel number.
 */
void adc_disable_channel_differential_input(uint32_t channel)
{
	uint32_t temp;
	temp = ADC->ADC_COR;
	ADC->ADC_COR &= 0xFFFEFFFFu << channel;
	ADC->ADC_COR |= temp;
}

/**
 * \brief Enable analog signal offset for the specified channel.
 *
 * \param channel ADC channel number.
 */
void adc_enable_channel_input_offset (uint32_t channel)
{
	ADC->ADC_COR |= 0x01u << channel;
}

/**
 * \brief Disable analog signal offset for the specified channel.
 *
 * \param channel ADC channel number.
 */
void adc_disable_channel_input_offset (uint32_t channel)
{
	uint32_t temp;
	temp = ADC->ADC_COR;
	ADC->ADC_COR &= (0xFFFFFFFEu << channel);
	ADC->ADC_COR |= temp;
}
#endif /* CONFIG_HAVE_ADC_INPUT_OFFSET */

#ifdef CONFIG_HAVE_ADC_INPUT_GAIN
/**
 * \brief Configure input gain for the specified channel.
 *
 * \param channel ADC channel number.
 * \param gain Gain value for the input.
 */
void adc_set_channel_input_gain (uint32_t channel, uint32_t gain)
{
	assert(gain < 3);
	uint32_t temp;
	temp = ADC->ADC_CGR;
	temp |= gain << (2 * channel);
	ADC->ADC_CGR = temp;
}
#endif /* CONFIG_HAVE_ADC_INPUT_GAIN */

void adc_set_tracking_time(uint32_t dwNs)
{
	uint32_t dwShtim;
	uint32_t mode_reg;

	if (_adc_clock == 0)
		return;
	/* Formula for SHTIM is:
	   SHTIM = (time x ADCCLK) / (1000000000) - 1
	   Since 1 billion is close to the maximum value for an integer, we first
	   divide ADCCLK by 1000 to avoid an overflow */
	dwShtim = (dwNs * (_adc_clock / 1000)) / 100000;
	if (dwShtim % 10)
		dwShtim /= 10;
	else {
		dwShtim /= 10;
		if (dwShtim)
			dwShtim--;
	}
	mode_reg = ADC_MR_TRACKTIM(dwShtim);
	mode_reg |= ADC->ADC_MR & ~ADC_MR_TRACKTIM_Msk;
	ADC->ADC_MR = mode_reg;
}

void adc_set_trigger_period(uint32_t period)
{
	uint32_t trg_period;
	uint32_t trg_reg;
	if (_adc_clock == 0)
		return;
	trg_period = period * (_adc_clock/1000) - 1;
	trg_reg = ADC->ADC_TRGR & ~ADC_TRGR_TRGPER_Msk;
	trg_reg |= ADC_TRGR_TRGPER(trg_period);
	ADC->ADC_TRGR = trg_reg;
}

void adc_ts_calibration(void)
{
	ADC->ADC_CR = ADC_CR_TSCALIB;
}

void adc_set_ts_mode(uint32_t mode)
{
	ADC->ADC_TSMR = (ADC->ADC_TSMR & ~ADC_TSMR_TSMODE_Msk) | mode;
}

void adc_configure_ext_mode(uint32_t mode)
{
	ADC->ADC_EMR = mode;
}

void adc_set_ts_debounce(uint32_t time)
{
	uint32_t div = 1000000000;
	uint32_t clk = _adc_clock;
	uint32_t dwPenbc = 0;
	uint32_t target, current;
	uint32_t tsmr;
	if (time == 0 || _adc_clock == 0)
		return;
	/* Divide time & ADCCLK to avoid overflows */
	while ((div > 1) && ((time % 10) == 0)) {
		time /= 10;
		div /= 10;
	}
	while ((div > 1) && ((clk & 10) == 0)) {
		clk /= 10;
		div /= 10;
	}
	/* Compute PENDBC */
	target = time * clk / div;
	current = 1;
	while (current < target) {
		dwPenbc++;
		current *= 2;
	}
	tsmr = ADC_TSMR_PENDBC(dwPenbc);
	if (tsmr == 0)
		return;
	tsmr |= ADC->ADC_TSMR & ~ADC_TSMR_PENDBC_Msk;
	ADC->ADC_TSMR = tsmr;
}

void adc_set_ts_pen_detect(uint8_t enable)
{
	if (enable)
		ADC->ADC_TSMR |= ADC_TSMR_PENDET;
	else
		ADC->ADC_TSMR &= ~ADC_TSMR_PENDET;
}

void adc_set_ts_average(uint32_t avg_2_conv)
{
	uint32_t mode_reg = ADC->ADC_TSMR & ~ADC_TSMR_TSAV_Msk;
	uint32_t ts_av = avg_2_conv >> ADC_TSMR_TSAV_Pos;
	uint32_t ts_freq = (mode_reg & ADC_TSMR_TSFREQ_Msk) >> ADC_TSMR_TSFREQ_Pos;
	if (ts_av) {
		if (ts_av > ts_freq) {
			mode_reg &= ~ADC_TSMR_TSFREQ_Msk;
			mode_reg |= ADC_TSMR_TSFREQ(ts_av);
		}
	}
	ADC->ADC_TSMR = mode_reg | avg_2_conv;
}

uint32_t adc_get_ts_xposition(void)
{
	return ADC->ADC_XPOSR;
}

uint32_t adc_get_ts_yposition(void)
{
	return ADC->ADC_YPOSR;
}

uint32_t adc_get_ts_pressure(void)
{
	return ADC->ADC_PRESSR;
}

void adc_set_trigger(uint32_t trigger)
{
	uint32_t mode_reg;

	mode_reg = ADC->ADC_MR;
	mode_reg &= ~ADC_MR_TRGSEL_Msk;
	mode_reg |= trigger;
	ADC->ADC_MR |= mode_reg;
}

#ifdef CONFIG_HAVE_ADC_LOW_RES
void adc_set_low_resolution(uint8_t enable)
{
	if (enable) {
		ADC->ADC_MR |= ADC_MR_LOWRES;
	} else {
		ADC->ADC_MR &= ~ADC_MR_LOWRES;
	}
}
#endif
