mbed library sources. Supersedes mbed-src.
Fork of mbed-dev by
Diff: targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/drivers/adc/TARGET_SAML21/adc.c
- Revision:
- 18:da299f395b9e
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/targets/hal/TARGET_Atmel/TARGET_SAM_CortexM0P/drivers/adc/TARGET_SAML21/adc.c Mon Nov 09 13:30:11 2015 +0000 @@ -0,0 +1,769 @@ +/** + * \file + * + * \brief SAM Peripheral Analog-to-Digital Converter Driver + * + * Copyright (C) 2014-2015 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 + * + */ +/* + * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a> + */ + +#include "adc.h" +#if (ADC_INST_NUM > 1) || (SAMC20) + +# define _ADC_GCLK_ID(n,unused) TPASTE3(ADC,n,_GCLK_ID), +# define _ADC_APBCMASK(n,unused) TPASTE2(MCLK_APBCMASK_ADC,n), + +# define _ADC_FUSES_BIASCOMP_ADDR(n,unused) TPASTE3(ADC,n,_FUSES_BIASCOMP_ADDR), +# define _ADC_FUSES_BIASCOMP_Pos(n,unused) TPASTE3(ADC,n,_FUSES_BIASCOMP_Pos), +# define _ADC_FUSES_BIASREFBUF_ADDR(n,unused) TPASTE3(ADC,n,_FUSES_BIASREFBUF_ADDR), +# define _ADC_FUSES_BIASREFBUF_Pos(n,unused) TPASTE3(ADC,n,_FUSES_BIASREFBUF_Pos), +# define _ADC_EXTCHANNEL_MSB(n,unused) TPASTE3(ADC,n,_EXTCHANNEL_MSB), + +# define ADC_GCLK_ID MREPEAT(ADC_INST_NUM, _ADC_GCLK_ID, 0) +# define ADC_APBCMASKS MREPEAT(ADC_INST_NUM, _ADC_APBCMASK, 0) + +# define ADC_FUSES_BIASCOMP_ADDR MREPEAT(ADC_INST_NUM, _ADC_FUSES_BIASCOMP_ADDR, 0) +# define ADC_FUSES_BIASCOMP_Pos MREPEAT(ADC_INST_NUM, _ADC_FUSES_BIASCOMP_Pos, 0) +# define ADC_FUSES_BIASREFBUF_ADDR MREPEAT(ADC_INST_NUM, _ADC_FUSES_BIASREFBUF_ADDR, 0) +# define ADC_FUSES_BIASREFBUF_Pos MREPEAT(ADC_INST_NUM, _ADC_FUSES_BIASREFBUF_Pos, 0) +# define ADC_EXTCHANNEL_MSB MREPEAT(ADC_INST_NUM, _ADC_EXTCHANNEL_MSB, 0) + +#endif + +/* List of ADC GCLK IDs */ +const uint8_t _adc_gclk_ids[ADC_INST_NUM] = { ADC_GCLK_ID }; + +/* List of ADC APB Masks */ +#if (SAML21) +const uint32_t _adc_apbcmasks[ADC_INST_NUM] = { MCLK_APBDMASK_ADC }; +#else +const uint32_t _adc_apbcmasks[ADC_INST_NUM] = { ADC_APBCMASKS }; +#endif + +/* List of Number of external channels of ADC modules. */ +const uint32_t _adc_extchannel_msb[ADC_INST_NUM] = { ADC_EXTCHANNEL_MSB }; + +/* List of address of comparator scaling of ADC modules. */ +const uint32_t _adc_biascomp_addr[ADC_INST_NUM] = { ADC_FUSES_BIASCOMP_ADDR }; + +/* List of address of bias reference buffer scaling of ADC modules. */ +const uint32_t _adc_biasrefbuf_addr[ADC_INST_NUM] = { ADC_FUSES_BIASREFBUF_ADDR }; + +/* List of offset of comparator scaling of ADC modules. */ +const uint8_t _adc_biascomp_pos[ADC_INST_NUM] = { ADC_FUSES_BIASCOMP_Pos }; + +/* List of offset of bias reference buffer scaling of ADC modules. */ +const uint8_t _adc_biasrefbuf_pos[ADC_INST_NUM] = { ADC_FUSES_BIASREFBUF_Pos }; + + +/** + * \internal Find the index of given ADC module instance. + * + * \param[in] ADC module instance pointer. + * + * \return Index of the given ADC module instance. + */ +uint8_t _adc_get_inst_index( + Adc *const hw) +{ + /* List of available ADC modules. */ + Adc *const adc_modules[ADC_INST_NUM] = ADC_INSTS; + + /* Find index for ADC instance. */ + for (uint32_t i = 0; i < ADC_INST_NUM; i++) { + if (hw == adc_modules[i]) { + return i; + } + } + + /* Invalid data given. */ + Assert(false); + return 0; +} + +/** + * \brief Initializes an ADC configuration structure to defaults. + * + * Initializes a given ADC configuration struct to a set of known default + * values. This function should be called on any new instance of the + * configuration struct before being modified by the user application. + * + * The default configuration is as follows: + * \li GCLK generator 0 (GCLK main) clock source + * \li Internal bandgap reference + * \li Div 2 clock prescaler + * \li 12-bit resolution + * \li Window monitor disabled + * \li Positive input on ADC PIN 1 + * \li Negative input on Internal ground + * \li Averaging disabled + * \li Oversampling disabled + * \li Right adjust data + * \li Single-ended mode + * \li Free running disabled + * \li All events (input and generation) disabled + * \li ADC run in standby disabled + * \li ADC On demand disabled + * \li No sampling time compensation + * \li Disable the positive input sequense + * \li No reference compensation + * \li No gain/offset correction + * \li No added sampling time + * + * \param[out] config Pointer to configuration struct to initialize to + * default values + */ +void adc_get_config_defaults(struct adc_config *const config) +{ + Assert(config); + config->clock_source = GCLK_GENERATOR_0; + config->reference = ADC_REFERENCE_INTREF; + config->clock_prescaler = ADC_CLOCK_PRESCALER_DIV2; + config->resolution = ADC_RESOLUTION_12BIT; + config->window.window_mode = ADC_WINDOW_MODE_DISABLE; + config->window.window_upper_value = 0; + config->window.window_lower_value = 0; + config->positive_input = ADC_POSITIVE_INPUT_PIN1; + config->negative_input = ADC_NEGATIVE_INPUT_GND; + config->accumulate_samples = ADC_ACCUMULATE_DISABLE; + config->divide_result = ADC_DIVIDE_RESULT_DISABLE; + config->left_adjust = false; + config->differential_mode = false; + config->freerunning = false; + config->event_action = ADC_EVENT_ACTION_DISABLED; + config->run_in_standby = false; + config->on_demand = false; + config->sampling_time_compensation_enable = false; + config->positive_input_sequence_mask_enable = 0; + config->reference_compensation_enable = false; + config->correction.correction_enable = false; + config->correction.gain_correction = ADC_GAINCORR_RESETVALUE; + config->correction.offset_correction = ADC_OFFSETCORR_RESETVALUE; + config->sample_length = 0; +} + +/** + * \brief Sets the ADC window mode. + * + * Sets the ADC window mode to a given mode and value range. + * + * \param[in] module_inst Pointer to the ADC software instance struct + * \param[in] window_mode Window monitor mode to set + * \param[in] window_lower_value Lower window monitor threshold value + * \param[in] window_upper_value Upper window monitor threshold value + */ +void adc_set_window_mode( + struct adc_module *const module_inst, + const enum adc_window_mode window_mode, + const int16_t window_lower_value, + const int16_t window_upper_value) +{ + /* Sanity check arguments */ + Assert(module_inst); + Assert(module_inst->hw); + + Adc *const adc_module = module_inst->hw; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Set window mode */ + adc_module->CTRLC.reg = window_mode; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Set lower window monitor threshold value */ + adc_module->WINLT.reg = window_lower_value; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Set upper window monitor threshold value */ + adc_module->WINUT.reg = window_upper_value; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } +} + +/** +* \internal Configure MUX settings for the analog pins. +* +* This function will set the given ADC input pins +* to the analog function in the pin mux, giving +* the ADC access to the analog signal. +* +* \param [in] index Index of the ADC module instance. +* \param [in] pin AINxx pin to configure +*/ +static inline void _adc_configure_ain_pin(uint8_t index, uint32_t pin) +{ +#define PIN_INVALID_ADC_AIN 0xFFFFUL + + /* Pinmapping table for AINxx -> GPIO pin number */ +#if (SAML21) + const uint32_t pinmapping[] = { +# if (SAML21E) + PIN_PA02B_ADC_AIN0, PIN_PA03B_ADC_AIN1, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PA04B_ADC_AIN4, PIN_PA05B_ADC_AIN5, + PIN_PA06B_ADC_AIN6, PIN_PA07B_ADC_AIN7, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PA08B_ADC_AIN16, PIN_PA09B_ADC_AIN17, + PIN_PA10B_ADC_AIN18, PIN_PA11B_ADC_AIN19, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, +# elif (SAML21G) + PIN_PA02B_ADC_AIN0, PIN_PA03B_ADC_AIN1, + PIN_PB08B_ADC_AIN2, PIN_PB09B_ADC_AIN3, + PIN_PA04B_ADC_AIN4, PIN_PA05B_ADC_AIN5, + PIN_PA06B_ADC_AIN6, PIN_PA07B_ADC_AIN7, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PB02B_ADC_AIN10, PIN_PB03B_ADC_AIN11, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PA08B_ADC_AIN16, PIN_PA09B_ADC_AIN17, + PIN_PA10B_ADC_AIN18, PIN_PA11B_ADC_AIN19, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, +# elif (SAML21J) + PIN_PA02B_ADC_AIN0, PIN_PA03B_ADC_AIN1, + PIN_PB08B_ADC_AIN2, PIN_PB09B_ADC_AIN3, + PIN_PA04B_ADC_AIN4, PIN_PA05B_ADC_AIN5, + PIN_PA06B_ADC_AIN6, PIN_PA07B_ADC_AIN7, + PIN_PB00B_ADC_AIN8, PIN_PB01B_ADC_AIN9, + PIN_PB02B_ADC_AIN10, PIN_PB03B_ADC_AIN11, + PIN_PB04B_ADC_AIN12, PIN_PB05B_ADC_AIN13, + PIN_PB06B_ADC_AIN14, PIN_PB07B_ADC_AIN15, + PIN_PA08B_ADC_AIN16, PIN_PA09B_ADC_AIN17, + PIN_PA10B_ADC_AIN18, PIN_PA11B_ADC_AIN19, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, +# else +# error ADC pin mappings are not defined for this device. +# endif + }; +#elif (SAMC20) + const uint32_t pinmapping[] = { +# if (SAMC20E) + PIN_PA02B_ADC0_AIN0, PIN_PA03B_ADC0_AIN1, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PA04B_ADC0_AIN4, PIN_PA05B_ADC0_AIN5, + PIN_PA06B_ADC0_AIN6, PIN_PA07B_ADC0_AIN7, + PIN_PA08B_ADC0_AIN8, PIN_PA09B_ADC0_AIN9, + PIN_PA10B_ADC0_AIN10, PIN_PA11B_ADC0_AIN11, +# elif (SAMC20G) + PIN_PA02B_ADC0_AIN0, PIN_PA03B_ADC0_AIN1, + PIN_PB08B_ADC0_AIN2, PIN_PB09B_ADC0_AIN3, + PIN_PA04B_ADC0_AIN4, PIN_PA05B_ADC0_AIN5, + PIN_PA06B_ADC0_AIN6, PIN_PA07B_ADC0_AIN7, + PIN_PA08B_ADC0_AIN8, PIN_PA09B_ADC0_AIN9, + PIN_PA10B_ADC0_AIN10, PIN_PA11B_ADC0_AIN11, +# elif (SAMC20J) + PIN_PA02B_ADC0_AIN0, PIN_PA03B_ADC0_AIN1, + PIN_PB08B_ADC0_AIN2, PIN_PB09B_ADC0_AIN3, + PIN_PA04B_ADC0_AIN4, PIN_PA05B_ADC0_AIN5, + PIN_PA06B_ADC0_AIN6, PIN_PA07B_ADC0_AIN7, + PIN_PA08B_ADC0_AIN8, PIN_PA09B_ADC0_AIN9, + PIN_PA10B_ADC0_AIN10, PIN_PA11B_ADC0_AIN11, +# else +# error ADC pin mappings are not defined for this device. +# endif + }; +#elif (SAMC21) + const uint32_t *pinmapping = NULL;; + const uint32_t pinmapping0[] = { +# if (SAMC21E) + PIN_PA02B_ADC0_AIN0, PIN_PA03B_ADC0_AIN1, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PA04B_ADC0_AIN4, PIN_PA05B_ADC0_AIN5, + PIN_PA06B_ADC0_AIN6, PIN_PA07B_ADC0_AIN7, + PIN_PA08B_ADC0_AIN8, PIN_PA09B_ADC0_AIN9, + PIN_PA10B_ADC0_AIN10, PIN_PA11B_ADC0_AIN11, +# elif (SAMC21G) + PIN_PA02B_ADC0_AIN0, PIN_PA03B_ADC0_AIN1, + PIN_PB08B_ADC0_AIN2, PIN_PB09B_ADC0_AIN3, + PIN_PA04B_ADC0_AIN4, PIN_PA05B_ADC0_AIN5, + PIN_PA06B_ADC0_AIN6, PIN_PA07B_ADC0_AIN7, + PIN_PA08B_ADC0_AIN8, PIN_PA09B_ADC0_AIN9, + PIN_PA10B_ADC0_AIN10, PIN_PA11B_ADC0_AIN11, +# elif (SAMC21J) + PIN_PA02B_ADC0_AIN0, PIN_PA03B_ADC0_AIN1, + PIN_PB08B_ADC0_AIN2, PIN_PB09B_ADC0_AIN3, + PIN_PA04B_ADC0_AIN4, PIN_PA05B_ADC0_AIN5, + PIN_PA06B_ADC0_AIN6, PIN_PA07B_ADC0_AIN7, + PIN_PA08B_ADC0_AIN8, PIN_PA09B_ADC0_AIN9, + PIN_PA10B_ADC0_AIN10, PIN_PA11B_ADC0_AIN11, +# else +# error ADC pin mappings are not defined for this device. +# endif + }; + const uint32_t pinmapping1[] = { +# if (SAMC21E) + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PA08B_ADC1_AIN10, PIN_PA09B_ADC1_AIN11, +# elif (SAMC21G) + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PB02B_ADC1_AIN2, PIN_PB03B_ADC1_AIN3, + PIN_PB08B_ADC1_AIN4, PIN_PB09B_ADC1_AIN5, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_INVALID_ADC_AIN, PIN_INVALID_ADC_AIN, + PIN_PA08B_ADC1_AIN10, PIN_PA09B_ADC1_AIN11, +# elif (SAMC21J) + PIN_PB00B_ADC1_AIN0, PIN_PB01B_ADC1_AIN1, + PIN_PB02B_ADC1_AIN2, PIN_PB03B_ADC1_AIN3, + PIN_PB08B_ADC1_AIN4, PIN_PB09B_ADC1_AIN5, + PIN_PB04B_ADC1_AIN6, PIN_PB05B_ADC1_AIN7, + PIN_PB06B_ADC1_AIN8, PIN_PB07B_ADC1_AIN9, + PIN_PA08B_ADC1_AIN10, PIN_PA09B_ADC1_AIN11, +# else +# error ADC pin mappings are not defined for this device. +# endif + }; + + switch(index) { + case 0: + pinmapping = pinmapping0; + break; + case 1: + pinmapping = pinmapping1; + break; + default: + break; + } + Assert(pinmapping); +#endif + + uint32_t pin_map_result = PIN_INVALID_ADC_AIN; + + if (pin <= _adc_extchannel_msb[index]) { + pin_map_result = pinmapping[pin >> ADC_INPUTCTRL_MUXPOS_Pos]; + + Assert(pin_map_result != PIN_INVALID_ADC_AIN); + + struct system_pinmux_config config; + system_pinmux_get_config_defaults(&config); + + /* Analog functions are all on MUX setting B */ + config.input_pull = SYSTEM_PINMUX_PIN_PULL_NONE; + config.mux_position = 1; + + system_pinmux_pin_set_config(pin_map_result, &config); + } +} + +/** + * \internal Writes an ADC configuration to the hardware module. + * + * Writes out a given ADC module configuration to the hardware module. + * + * \param[in] index Index of the ADC module instance + * \param[out] module_inst Pointer to the ADC software instance struct + * \param[in] config Pointer to configuration struct + * + * \return Status of the configuration procedure. + * \retval STATUS_OK The configuration was successful + * \retval STATUS_ERR_INVALID_ARG Invalid argument(s) were provided + */ +static enum status_code _adc_set_config( + uint8_t index, + struct adc_module *const module_inst, + struct adc_config *const config) +{ + uint8_t adjres = 0; + uint32_t resolution = ADC_RESOLUTION_16BIT; + enum adc_accumulate_samples accumulate = ADC_ACCUMULATE_DISABLE; + + /* Get the hardware module pointer */ + Adc *const adc_module = module_inst->hw; + + /* Configure GCLK channel and enable clock */ + struct system_gclk_chan_config gclk_chan_conf; + system_gclk_chan_get_config_defaults(&gclk_chan_conf); + gclk_chan_conf.source_generator = config->clock_source; + system_gclk_chan_set_config(_adc_gclk_ids[index], &gclk_chan_conf); + system_gclk_chan_enable(_adc_gclk_ids[index]); + + /* Setup pinmuxing for analog inputs */ + _adc_configure_ain_pin(index, config->positive_input); + _adc_configure_ain_pin(index, config->negative_input); + + /* Set pinmux for positive input sequence*/ + for(uint8_t i=0; i <= _adc_extchannel_msb[index]; i++) { + if(config->positive_input_sequence_mask_enable & (1 << i)) { + _adc_configure_ain_pin(index, i); + } + } + + /* Configure run in standby and on demand */ + adc_module->CTRLA.reg = ((config->run_in_standby << ADC_CTRLA_RUNSTDBY_Pos) + | (config->on_demand << ADC_CTRLA_ONDEMAND_Pos)) ; + + /* Configure reference */ + adc_module->REFCTRL.reg = + (config->reference_compensation_enable << ADC_REFCTRL_REFCOMP_Pos) + | (config->reference); + + /* Set adjusting result and number of samples */ + switch (config->resolution) { + + case ADC_RESOLUTION_CUSTOM: + adjres = config->divide_result; + accumulate = config->accumulate_samples; + /* 16-bit result register */ + resolution = ADC_RESOLUTION_16BIT; + break; + + case ADC_RESOLUTION_13BIT: + /* Increase resolution by 1 bit */ + adjres = ADC_DIVIDE_RESULT_2; + accumulate = ADC_ACCUMULATE_SAMPLES_4; + /* 16-bit result register */ + resolution = ADC_RESOLUTION_16BIT; + break; + + case ADC_RESOLUTION_14BIT: + /* Increase resolution by 2 bit */ + adjres = ADC_DIVIDE_RESULT_4; + accumulate = ADC_ACCUMULATE_SAMPLES_16; + /* 16-bit result register */ + resolution = ADC_RESOLUTION_16BIT; + break; + case ADC_RESOLUTION_15BIT: + /* Increase resolution by 3 bit */ + adjres = ADC_DIVIDE_RESULT_2; + accumulate = ADC_ACCUMULATE_SAMPLES_64; + /* 16-bit result register */ + resolution = ADC_RESOLUTION_16BIT; + break; + + case ADC_RESOLUTION_16BIT: + /* Increase resolution by 4 bit */ + adjres = ADC_DIVIDE_RESULT_DISABLE; + accumulate = ADC_ACCUMULATE_SAMPLES_256; + /* 16-bit result register */ + resolution = ADC_RESOLUTION_16BIT; + break; + case ADC_RESOLUTION_8BIT: + /* 8-bit result register */ + resolution = ADC_RESOLUTION_8BIT; + break; + case ADC_RESOLUTION_10BIT: + /* 10-bit result register */ + resolution = ADC_RESOLUTION_10BIT; + break; + case ADC_RESOLUTION_12BIT: + /* 12-bit result register */ + resolution = ADC_RESOLUTION_12BIT; + break; + + default: + /* Unknown. Abort. */ + return STATUS_ERR_INVALID_ARG; + } + + adc_module->AVGCTRL.reg = ADC_AVGCTRL_ADJRES(adjres) | accumulate; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Check validity of sample length value */ + if (config->sample_length > 63) { + return STATUS_ERR_INVALID_ARG; + } else { + /* Configure sample length */ + adc_module->SAMPCTRL.reg = + (config->sample_length << ADC_SAMPCTRL_SAMPLEN_Pos) + | (config->sampling_time_compensation_enable << ADC_SAMPCTRL_OFFCOMP_Pos); + } + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Configure CTRLB */ + adc_module->CTRLB.reg = + config->clock_prescaler; + adc_module->CTRLC.reg = + resolution | + (config->correction.correction_enable << ADC_CTRLC_CORREN_Pos) | + (config->freerunning << ADC_CTRLC_FREERUN_Pos) | + (config->left_adjust << ADC_CTRLC_LEFTADJ_Pos) | + (config->differential_mode << ADC_CTRLC_DIFFMODE_Pos); + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Check validity of window thresholds */ + if (config->window.window_mode != ADC_WINDOW_MODE_DISABLE) { + switch (resolution) { + case ADC_RESOLUTION_8BIT: + if (config->differential_mode && + (config->window.window_lower_value > 127 || + config->window.window_lower_value < -128 || + config->window.window_upper_value > 127 || + config->window.window_upper_value < -128)) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } else if (config->window.window_lower_value > 255 || + config->window.window_upper_value > 255) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } + break; + case ADC_RESOLUTION_10BIT: + if (config->differential_mode && + (config->window.window_lower_value > 511 || + config->window.window_lower_value < -512 || + config->window.window_upper_value > 511 || + config->window.window_upper_value < -512)) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } else if (config->window.window_lower_value > 1023 || + config->window.window_upper_value > 1023) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } + break; + case ADC_RESOLUTION_12BIT: + if (config->differential_mode && + (config->window.window_lower_value > 2047 || + config->window.window_lower_value < -2048 || + config->window.window_upper_value > 2047 || + config->window.window_upper_value < -2048)) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } else if (config->window.window_lower_value > 4095 || + config->window.window_upper_value > 4095) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } + break; + case ADC_RESOLUTION_16BIT: + if (config->differential_mode && + (config->window.window_lower_value > 32767 || + config->window.window_lower_value < -32768 || + config->window.window_upper_value > 32767 || + config->window.window_upper_value < -32768)) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } else if (config->window.window_lower_value > 65535 || + config->window.window_upper_value > 65535) { + /* Invalid value */ + return STATUS_ERR_INVALID_ARG; + } + break; + } + } + + /* Configure window mode */ + adc_module->CTRLC.reg |= config->window.window_mode; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Configure lower threshold */ + adc_module->WINLT.reg = + config->window.window_lower_value << ADC_WINLT_WINLT_Pos; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Configure lower threshold */ + adc_module->WINUT.reg = config->window.window_upper_value << + ADC_WINUT_WINUT_Pos; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Configure pin scan mode and positive and negative input pins */ + adc_module->INPUTCTRL.reg = + config->negative_input | + config->positive_input; + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Configure events */ + adc_module->EVCTRL.reg = config->event_action; + + /* Disable all interrupts */ + adc_module->INTENCLR.reg = + (1 << ADC_INTENCLR_WINMON_Pos) |(1 << ADC_INTENCLR_OVERRUN_Pos) + | (1 << ADC_INTENCLR_RESRDY_Pos); + + if (config->correction.correction_enable) { + /* Make sure gain_correction value is valid */ + if (config->correction.gain_correction > ADC_GAINCORR_GAINCORR_Msk) { + return STATUS_ERR_INVALID_ARG; + } else { + /* Set gain correction value */ + adc_module->GAINCORR.reg = config->correction.gain_correction << + ADC_GAINCORR_GAINCORR_Pos; + } + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + /* Make sure offset correction value is valid */ + if (config->correction.offset_correction > 2047 || + config->correction.offset_correction < -2048) { + return STATUS_ERR_INVALID_ARG; + } else { + /* Set offset correction value */ + adc_module->OFFSETCORR.reg = config->correction.offset_correction << + ADC_OFFSETCORR_OFFSETCORR_Pos; + } + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + } + + /* Load in the fixed device ADC calibration constants */ + adc_module->CALIB.reg = + ADC_CALIB_BIASREFBUF( + (*(uint32_t *)_adc_biasrefbuf_addr[index] >> _adc_biasrefbuf_pos[index]) + ) | + ADC_CALIB_BIASCOMP( + (*(uint32_t *)_adc_biascomp_addr[index] >> _adc_biascomp_pos[index]) + ); + + return STATUS_OK; +} + +/** + * \brief Initializes the ADC. + * + * Initializes the ADC device struct and the hardware module based on the + * given configuration struct values. + * + * \param[out] module_inst Pointer to the ADC software instance struct + * \param[in] hw Pointer to the ADC module instance + * \param[in] config Pointer to the configuration struct + * + * \return Status of the initialization procedure. + * \retval STATUS_OK The initialization was successful + * \retval STATUS_ERR_INVALID_ARG Invalid argument(s) were provided + * \retval STATUS_BUSY The module is busy with a reset operation + * \retval STATUS_ERR_DENIED The module is enabled + */ +enum status_code adc_init( + struct adc_module *const module_inst, + Adc *hw, + struct adc_config *config) +{ + /* Sanity check arguments */ + Assert(module_inst); + Assert(hw); + Assert(config); + + /* Temporary variable to hold ADC instance number */ + uint8_t instance = _adc_get_inst_index(hw); + + /* Associate the software module instance with the hardware module */ + module_inst->hw = hw; + + /* Turn on the digital interface clock */ + system_apb_clock_set_mask(SYSTEM_CLOCK_APB_APBC, _adc_apbcmasks[instance]); + + if (hw->CTRLA.reg & ADC_CTRLA_SWRST) { + /* We are in the middle of a reset. Abort. */ + return STATUS_BUSY; + } + + while (adc_is_syncing(module_inst)) { + /* Wait for synchronization */ + } + + if (hw->CTRLA.reg & ADC_CTRLA_ENABLE) { + /* Module must be disabled before initialization. Abort. */ + return STATUS_ERR_DENIED; + } + + /* Store the selected reference for later use */ + module_inst->reference = config->reference; + + /* Make sure the voltage reference is enabled if requested by the config */ + if (module_inst->reference == ADC_REFERENCE_INTREF) { + system_voltage_reference_enable(SYSTEM_VOLTAGE_REFERENCE_OUTPUT); + } + +#if ADC_CALLBACK_MODE == true + for (uint8_t i = 0; i < ADC_CALLBACK_N; i++) { + module_inst->callback[i] = NULL; + }; + + module_inst->registered_callback_mask = 0; + module_inst->enabled_callback_mask = 0; + module_inst->remaining_conversions = 0; + module_inst->job_status = STATUS_OK; + + _adc_instances[instance] = module_inst; + + if (config->event_action == ADC_EVENT_ACTION_DISABLED && + !config->freerunning) { + module_inst->software_trigger = true; + } else { + module_inst->software_trigger = false; + } +#endif + + /* Write configuration to module */ + return _adc_set_config(instance, module_inst, config); +}