/**
 * \file
 *
 * \brief AVR XMEGA A/AU specific ADC driver implementation
 *
 * Copyright (C) 2012-2013 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * 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
 * EXPRESSLY AND SPECIFICALLY 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.
 *
 * \asf_license_stop
 *
 */

#include "../adc.h"

/**
 * \ingroup adc_module_group
 * @{
 */

/** \name Internal functions for driver */
/** @{ */
extern void adc_enable_clock(ADC_t * adc);
extern void adc_disable_clock(ADC_t * adc);

/** @} */

/** \name ADC interrupt callback function */
/** @{ */

#ifdef ADCA
#  ifdef CONFIG_ADC_CALLBACK_ENABLE

extern adc_callback_t adca_callback;

/**
 * \internal
 * \brief ISR for channel 0 on ADC A
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCA_CH0_vect)
{
    adca_callback(&ADCA, ADC_CH0, adc_get_result(&ADCA, ADC_CH0));
}

/**
 * \internal
 * \brief ISR for channel 1 on ADC A
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCA_CH1_vect)
{
    adca_callback(&ADCA, ADC_CH1, adc_get_result(&ADCA, ADC_CH1));
}

/**
 * \internal
 * \brief ISR for channel 2 on ADC A
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCA_CH2_vect)
{
    adca_callback(&ADCA, ADC_CH2, adc_get_result(&ADCA, ADC_CH2));
}

/**
 * \internal
 * \brief ISR for channel 3 on ADC A
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCA_CH3_vect)
{
    adca_callback(&ADCA, ADC_CH3, adc_get_result(&ADCA, ADC_CH3));
}

#  endif
#endif

#ifdef ADCB
#  ifdef CONFIG_ADC_CALLBACK_ENABLE

extern adc_callback_t adcb_callback;

/**
 * \internal
 * \brief ISR for channel 0 on ADC B
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCB_CH0_vect)
{
    adcb_callback(&ADCB, ADC_CH0, adc_get_result(&ADCB, ADC_CH0));
}

/**
 * \internal
 * \brief ISR for channel 1 on ADC B
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCB_CH1_vect)
{
    adcb_callback(&ADCB, ADC_CH1, adc_get_result(&ADCB, ADC_CH1));
}

/**
 * \internal
 * \brief ISR for channel 2 on ADC B
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCB_CH2_vect)
{
    adcb_callback(&ADCB, ADC_CH2, adc_get_result(&ADCB, ADC_CH2));
}

/**
 * \internal
 * \brief ISR for channel 3 on ADC B
 *
 * Calls the callback function that has been set for the ADC when the channel's
 * interrupt flag is set, if its interrupt has been enabled.
 */
ISR(ADCB_CH3_vect)
{
    adcb_callback(&ADCB, ADC_CH3, adc_get_result(&ADCB, ADC_CH3));
}

#  endif
#endif

/** @} */

/** \name ADC module configuration */
/** @{ */

/**
 * \brief Write configuration to ADC module
 *
 * Disables the ADC and flushes its pipeline before writing the specified
 * configuration and factory calibration value to it. If the ADC was enabled
 * upon entry of the function, it is enabled upon function return.
 *
 * \param adc Pointer to ADC module.
 * \param conf Pointer to ADC module configuration.
 */
void adc_write_configuration(ADC_t * adc,
    const struct adc_config *conf)
{
    uint16_t cal;
    uint8_t enable;
    irqflags_t flags;

#ifdef ADCA
    if ((uintptr_t) adc == (uintptr_t) & ADCA) {
        cal = adc_get_calibration_data(ADC_CAL_ADCA);
    } else
#endif

#ifdef ADCB
    if ((uintptr_t) adc == (uintptr_t) & ADCB) {
        cal = adc_get_calibration_data(ADC_CAL_ADCB);
    } else
#endif

    {
        Assert(0);
        return;
    }

    flags = cpu_irq_save();
    adc_enable_clock(adc);
    enable = adc->CTRLA & ADC_ENABLE_bm;

    adc->CTRLA = ADC_FLUSH_bm;
    adc->CAL = cal;
    adc->CMP = conf->cmp;
    adc->REFCTRL = conf->refctrl;
    adc->PRESCALER = conf->prescaler;
    adc->EVCTRL = conf->evctrl;
    adc->CTRLB = conf->ctrlb;

    adc->CTRLA = enable | conf->ctrla;

    adc_disable_clock(adc);

    cpu_irq_restore(flags);
}

/**
 * \brief Read configuration from ADC module
 *
 * Reads out the current configuration of the ADC module to the specified
 * buffer.
 *
 * \param adc Pointer to ADC module.
 * \param conf Pointer to ADC module configuration.
 */
void adc_read_configuration(ADC_t * adc,
    struct adc_config *conf)
{
    irqflags_t flags = cpu_irq_save();

    adc_enable_clock(adc);

    conf->ctrla = adc->CTRLA & ADC_DMASEL_gm;

    conf->cmp = adc->CMP;
    conf->refctrl = adc->REFCTRL;
    conf->prescaler = adc->PRESCALER;
    conf->evctrl = adc->EVCTRL;
    conf->ctrlb = adc->CTRLB;

    adc_disable_clock(adc);

    cpu_irq_restore(flags);
}

/** @} */

/** @} */

/**
 * \ingroup adc_channel_group
 * @{
 */

/** \name ADC channel configuration */
/** @{ */

/**
 * \brief Write configuration to ADC channel
 *
 * Writes the specified configuration to the ADC channel.
 *
 * \param adc Pointer to ADC module.
 * \param ch_mask Mask of ADC channel(s):
 * \arg \c ADC_CHn , where \c n specifies the channel. (Only a single channel
 * can be given in mask)
 * \param ch_conf Pointer to ADC channel configuration.
 *
 * \note The specified ADC's callback function must be set before this function
 * is called if callbacks are enabled and interrupts are enabled in the
 * channel configuration.
 */
void adcch_write_configuration(ADC_t * adc,
    uint8_t ch_mask,
    const struct adc_channel_config *ch_conf)
{
    ADC_CH_t *adc_ch;
    irqflags_t flags;

    adc_ch = adc_get_channel(adc, ch_mask);

    flags = cpu_irq_save();

#if defined(CONFIG_ADC_CALLBACK_ENABLE) && defined(_ASSERT_ENABLE_)
    if ((adc_ch->INTCTRL & ADC_CH_INTLVL_gm) != ADC_CH_INTLVL_OFF_gc) {
#  ifdef ADCA
        if ((uintptr_t) adc == (uintptr_t) & ADCA) {
            Assert(adca_callback);
        } else
#  endif

#  ifdef ADCB
        if ((uintptr_t) adc == (uintptr_t) & ADCB) {
            Assert(adcb_callback);
        } else
#  endif

        {
            Assert(0);
            return;
        }
    }
#endif

    adc_enable_clock(adc);
    adc_ch->CTRL = ch_conf->ctrl;
    adc_ch->INTCTRL = ch_conf->intctrl;
    adc_ch->MUXCTRL = ch_conf->muxctrl;
    if (ch_mask & ADC_CH0) {
        /* USB devices has channel scan available on ADC channel 0 */
        adc_ch->SCAN = ch_conf->scan;
    }
    adc_disable_clock(adc);

    cpu_irq_restore(flags);
}

/**
 * \brief Read configuration from ADC channel
 *
 * Reads out the current configuration from the ADC channel to the specified
 * buffer.
 *
 * \param adc Pointer to ADC module.
 * \param ch_mask Mask of ADC channel(s):
 * \arg \c ADC_CHn , where \c n specifies the channel. (Only a single channel
 * can be given in mask)
 * \param ch_conf Pointer to ADC channel configuration.
 */
void adcch_read_configuration(ADC_t * adc,
    uint8_t ch_mask,
    struct adc_channel_config *ch_conf)
{
    ADC_CH_t *adc_ch;
    irqflags_t flags;

    adc_ch = adc_get_channel(adc, ch_mask);

    flags = cpu_irq_save();

    adc_enable_clock(adc);
    ch_conf->ctrl = adc_ch->CTRL;
    ch_conf->intctrl = adc_ch->INTCTRL;
    ch_conf->muxctrl = adc_ch->MUXCTRL;
    if (ch_mask & ADC_CH0) {
        /* USB devices has channel scan available on ADC channel 0 */
        ch_conf->scan = adc_ch->SCAN;
    }
    adc_disable_clock(adc);

    cpu_irq_restore(flags);
}

/** @} */

/** @} */
