diff --git a/boards/qn9080dk/Kconfig b/boards/qn9080dk/Kconfig index da1a0ec8eb..b1946f1da7 100644 --- a/boards/qn9080dk/Kconfig +++ b/boards/qn9080dk/Kconfig @@ -17,9 +17,10 @@ config BOARD_QN9080DK # Put defined MCU peripherals here (in alphabetical order) select BOARD_HAS_XTAL32K select BOARD_HAS_XTAL_32M + select HAS_PERIPH_ADC + select HAS_PERIPH_I2C select HAS_PERIPH_TIMER select HAS_PERIPH_UART - select HAS_PERIPH_I2C select HAS_PERIPH_UART_MODECFG source "$(RIOTBOARD)/common/qn908x/Kconfig" diff --git a/boards/qn9080dk/Makefile.features b/boards/qn9080dk/Makefile.features index 4337aa8c92..96a7fefa8e 100644 --- a/boards/qn9080dk/Makefile.features +++ b/boards/qn9080dk/Makefile.features @@ -2,6 +2,7 @@ CPU = qn908x CPU_MODEL = qn9080xhn # Put defined MCU peripherals here (in alphabetical order) +FEATURES_PROVIDED += periph_adc FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_i2c FEATURES_PROVIDED += periph_timer diff --git a/boards/qn9080dk/include/periph_conf.h b/boards/qn9080dk/include/periph_conf.h index 1d55e0be1f..d97f06f0ba 100644 --- a/boards/qn9080dk/include/periph_conf.h +++ b/boards/qn9080dk/include/periph_conf.h @@ -28,6 +28,29 @@ extern "C" { #endif +/** + * @name ADC configuration + * + * Names "An" are as described in the "Analog In" header on the PCB. All "An" + * inputs are configured referenced to 1.8V. + * @{ + */ +static const adc_conf_t adc_config[] = { + ADC_CHANNEL_ADC7_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A0 */ + ADC_CHANNEL_ADC6_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A1 */ + ADC_CHANNEL_ADC4_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A2 */ + ADC_CHANNEL_ADC5_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A3 */ + ADC_CHANNEL_ADC1_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A4 */ + ADC_CHANNEL_ADC0_VINN | ADC_VINN_AVSS | ADC_VREF_GAIN_X15, /* A5 */ + ADC_CHANNEL_ADC6_ADC7 | ADC_VREF_GAIN_X15, /* A1-A0 */ + ADC_CHANNEL_ADC4_ADC5 | ADC_VREF_GAIN_X15, /* A2-A3 */ + ADC_CHANNEL_ADC0_ADC1 | ADC_VREF_GAIN_X15, /* A5-A4 */ + ADC_CHANNEL_TEMP, /* temperature */ + ADC_CHANNEL_VCC4_VINN | ADC_VINN_AVSS, /* Vcc/4 */ +}; +#define ADC_NUMOF ARRAY_SIZE(adc_config) +/** @} */ + /** * @name I2C configuration * @{ diff --git a/cpu/qn908x/doc.txt b/cpu/qn908x/doc.txt index 993ba21524..fb9bcc2160 100644 --- a/cpu/qn908x/doc.txt +++ b/cpu/qn908x/doc.txt @@ -30,6 +30,83 @@ The GPIO driver uses the @ref GPIO_PIN(port, pin) macro to declare pins. No configuration is necessary. +@defgroup cpu_qn908x_adc NXP QN908x ADC - Analog to Digital converter +@ingroup cpu_qn908x +@brief NXP QN908x ADC driver + +This ADC is a differential sigma-delta ADC. There are 9 external signals +named ADC0 to ADC7 and ADC_VREFI that can be connected to specific external GPIO +pins. There are several combinations of measurements possible with the ADC +module using these external signals as well as some internal signals, but not +every combination is a possible input pair to the ADC. + +The ADC block runs at either 4 MHz or 32 KHz from the high speed or low speed +clock sources respective. An additional divisor is available to select some +intermediate clock values. However, this is not the sample rate, since a single +sample from @ref adc_sample() requires multiple internal samples which are then +filtered and decimated by the hardware, giving about 128 slower sample rate than +the selected clock. + +Each board-defined ADC line is configured with a single integer value which is +the logic or of the following values: + - The differential pair of signals among the options in @ref + qn908x_adc_channel_t, + - For those channels that use the "Vinn" signal, a value selecting + the Vinn signal in @ref qn908x_adc_vinn_t, + - The reference voltage "Vref" that the ADC will use for its full range + selected with @ref qn908x_adc_vref_t, + - The ADC input gain as selected by @ref qn908x_adc_gain_t, which will + multiply the differential input by a factor between 0.5 and 2, and + - An optional gain flag @ref ADC_VREF_GAIN_X15. + - An optional PGA enabled flag @ref ADC_PGA_ENABLE. + +The hardware resolution of the ADC data is always 23-bits signed, but smaller +resolutions can be requested which will result in a smaller output value. + +An internal temperature sensor is available and connected to the ADC when +selecting @ref ADC_CHANNEL_TEMP as the channel. In this case the returned +value is still a number that represents the temperature dependent voltage level +of the internal signal which then needs to be converted to a temperature by the +application using calibration parameters. When using the internal temperature +sensor, the 1.2V bandgap Vref is recommended with a Vinn of 1/2 Vref since the +measured voltage is about 800 mV at room temperature. + +A special microvolts (ADC_RES_UV) resolution value for @ref adc_res_t is +supported when using the internal 1.2 V bandgap as the reference voltage, in +which case @ref adc_sample will return the measured value in microvolts as a +signed integer, with a max range of +/- 1.8 V when using the x1.5 Vref +multiplier. +This special resolution mode takes into account the factory calibration of +the internal reference voltage for more accurate readings. In any other case, +the return value is a signed integer with as many bits as resolution +requested not including the sign bit. Note that the return value may be a +negative when measuring a negative differential voltage between the plus and +minus side of the input. + +For example, if 8-bit resolution is requested for an ADC line where the channel +connects the - side to Vinn configured as Vss, a maximum value of 255 can be +returned when the + side level is as high as the Vref signal. However, a +negative value of -255 is also possible if Vinn is configured as Vref and +the + side level is as low as Vss. + +### ADC configuration example (for periph_conf.h) ### + +@code + static const adc_conf_t adc_config[] = { + /* Pin A11 to Vss, 1.8v Vref. */ + ADC_CHANNEL_ADC7_VINN | ADC_VREF_GAIN_X15, + /* Pin A10 to A11, 1.2V Vref. */ + ADC_CHANNEL_ADC6_ADC7, + /* Temperature (in V) over to 0.6 V, 1.2 V Vref. */ + ADC_CHANNEL_TEMP | ADC_VINN_VREF_2, + /* Internal "battery monitor", Vcc/4 to Vss, 1.2V Vref. */ + ADC_CHANNEL_VCC4_VINN | ADC_VINN_AVSS, + }; + #define ADC_NUMOF ARRAY_SIZE(adc_config) + + #define QN908X_ADC_CLOCK ADC_CLOCK_500K +@endcode + @defgroup cpu_qn908x_i2c NXP QN908x I2C @ingroup cpu_qn908x diff --git a/cpu/qn908x/include/periph_cpu.h b/cpu/qn908x/include/periph_cpu.h index fc0d77d853..6b416c4595 100644 --- a/cpu/qn908x/include/periph_cpu.h +++ b/cpu/qn908x/include/periph_cpu.h @@ -141,6 +141,196 @@ enum { GPIO_PORTS_NUMOF /**< overall number of available ports */ }; +/** + * @name ADC CPU configuration + * @{ + */ +#if DOXYGEN +/** + * @brief Define if ADC external capacitor is connected to PA06 pin. + * + * The ADC block can use an external capacitor to better stabilize the reference + * voltage. This capacitor is optional, but if it is present on the board this + * macro should be defined by the board to make the ADC block use it. + */ +#define BOARD_HAS_ADC_PA06_CAP +#endif + +#ifndef DOXYGEN +/** + * @name ADC resolution values + * @{ + */ +#define HAVE_ADC_RES_T +typedef enum { + ADC_RES_6BIT = 6u, /**< ADC resolution: 6 bit + sign */ + ADC_RES_8BIT = 8u, /**< ADC resolution: 8 bit + sign */ + ADC_RES_10BIT = 10u, /**< ADC resolution: 10 bit + sign */ + ADC_RES_12BIT = 12u, /**< ADC resolution: 12 bit + sign */ + ADC_RES_14BIT = 14u, /**< ADC resolution: 14 bit + sign */ + ADC_RES_16BIT = 16u, /**< ADC resolution: 16 bit + sign */ + /* Extra modes supported by this CPU. */ + ADC_RES_MAX = 22u, /**< Full ADC resolution: 22 bit + sign */ + ADC_RES_UV = 23u, /**< ADC resolution: signed int in uV */ +} adc_res_t; +/** @} */ +#endif /* ifndef DOXYGEN */ + +/** + * @brief ADC oversample clock configuration + * + * The ADC runs at a given ADC clock frequency which is derived from either the + * high frequency clock (16 or 32 MHz) or the low frequency one (32 or + * 32.768 KHz). Running the ADC from the 32 KHz source can be useful in low + * power applications where the high speed clock is not running. + * + * The ADC sample rate for adc_sample() will be about 128 times slower than the + * ADC clock, due to the decimation filter, meaning that the maximum sampling + * rate is 31.25 KHz. + */ +typedef enum { + ADC_CLOCK_4M, /**< 4 MHz from the high speed clock. */ + ADC_CLOCK_2M, /**< 2 MHz from the high speed clock. */ + ADC_CLOCK_1M, /**< 1 MHz from the high speed clock. */ + ADC_CLOCK_500K, /**< 500 KHz from the high speed clock. */ + ADC_CLOCK_250K, /**< 250 KHz from the high speed clock. */ + ADC_CLOCK_125K, /**< 125 KHz from the high speed clock. */ + ADC_CLOCK_62K5, /**< 62.5 KHz from the high speed clock. */ + ADC_CLOCK_31K25, /**< 31.25 KHz from the high speed clock. */ + ADC_CLOCK_32K, /**< 32 KHz or 32.768 KHz from the low speed clock. */ +} qn908x_adc_clock_t; + +#ifdef DOXYGEN +/** @brief Selected ADC oversample clock. + * + * Define to one of the qn908x_adc_clock_t values. + */ +#define QN908X_ADC_CLOCK +/** @} */ +#endif /* ifdef DOXYGEN */ + + +/** + * @brief ADC channel pair configuration + * + * The following are the possible combinations of + and - inputs to the ADC + * sigma delta. Some of these combinations reference the "Vinn" signal which can + * be independently selected, see @ref qn908x_adc_vinn_t for details. + * + * The first signal is connected to the positive side while the second one is + * connected to the negative side. For example, ADC_CHANNEL_ADC0_ADC1 will read + * a positive value if ADC0 voltage is higher than ADC1. + * + * The @ref ADC_CHANNEL_TEMP uses the internal temperature signal and + * @ref ADC_CHANNEL_VCC4_VINN connects the + side to Vcc/4, which is useful to + * measure the battery level when Vcc is directly connected to a battery. + */ +typedef enum { + ADC_CHANNEL_ADC0_ADC1 = 0u << 9u, /**< Sample ADC0 / ADC1 */ + ADC_CHANNEL_ADC2_ADC3 = 1u << 9u, /**< Sample ADC2 / ADC3 */ + ADC_CHANNEL_ADC4_ADC5 = 2u << 9u, /**< Sample ADC4 / ADC5 */ + ADC_CHANNEL_ADC6_ADC7 = 3u << 9u, /**< Sample ADC6 / ADC7 */ + ADC_CHANNEL_ADC0_VINN = 4u << 9u, /**< Sample ADC0 / Vinn */ + ADC_CHANNEL_ADC1_VINN = 5u << 9u, /**< Sample ADC1 / Vinn */ + ADC_CHANNEL_ADC2_VINN = 6u << 9u, /**< Sample ADC2 / Vinn */ + ADC_CHANNEL_ADC3_VINN = 7u << 9u, /**< Sample ADC3 / Vinn */ + ADC_CHANNEL_ADC4_VINN = 8u << 9u, /**< Sample ADC4 / Vinn */ + ADC_CHANNEL_ADC5_VINN = 9u << 9u, /**< Sample ADC5 / Vinn */ + ADC_CHANNEL_ADC6_VINN = 10u << 9u, /**< Sample ADC6 / Vinn */ + ADC_CHANNEL_ADC7_VINN = 11u << 9u, /**< Sample ADC7 / Vinn */ + ADC_CHANNEL_TEMP = 13u << 9u, /**< Sample internal temperature */ + ADC_CHANNEL_VCC4_VINN = 14u << 9u, /**< Sample 1/4 Vcc / Vinn */ + ADC_CHANNEL_VINN_VINN = 15u << 9u, /**< Sample Vinn / Vinn */ + ADC_CHANNEL_VINN_VSS = 20u << 9u, /**< Sample Vinn / Vss */ +} qn908x_adc_channel_t; + +/** + * @brief ADC Vref configuration + * + * This value affects the reference voltage used by the ADC as the full range. + * It is also used in some cases to generate the Vinn signal are is only relevant for the channels that reference Vinn when it was + * set by @ref qn908x_adc_vinn_t to use Vref. The actual values match the field + * VREF_SEL in ADC CTRL register. + */ +typedef enum { + ADC_VREF_1V2 = 0x0000u, /**< Vref := internal 1.2V. */ + ADC_VREF_VREF = 0x4000u, /**< Vref := external ADC_VREFI pin */ + ADC_VREF_VEXT = 0x8000u, /**< Vref := external ADC_VREFI with the driver */ + ADC_VREF_VCC = 0xC000u, /**< Vref := Vcc */ +} qn908x_adc_vref_t; + +/** + * @brief ADC Vref x1.5 multiplier flag + * + * Note, this is the same value as ADC_CFG_VREF_GAIN_MASK. When enabled the + * Vref voltage will be multiplied by 1.5x. + */ +#define ADC_VREF_GAIN_X15 (0x100u) + +/** + * @brief ADC PGA Enabled flag + * + * Flag to enable the Programmable Gain Amplifier (PGA) with a gain of 1x. This + * is only useful if the source signal doesn't have any driving capability since + * the gain is set to 1x. The hardware supports other gain combinations but + * those are not supported by the driver. + * + * Note: this value is defined as the inverse of ADC_CFG_PGA_BP_MASK which is + * defined if the PGA is bypassed. + */ +#define ADC_PGA_ENABLE (0x08u) + +/** + * @brief ADC Vinn configuration + * + * This value is only relevant for the channels that reference Vinn. The value + * is the same as the PGA_VINN in ADC CFG register with a logic xor 0x30u to + * make the default AVSS (analog Vss pad). + */ +typedef enum { + ADC_VINN_VREF = 0x30u, /**< Use Vinn := Vref */ + ADC_VINN_VREF_3_4 = 0x20u, /**< Use Vinn := 3/4 * Vref */ + ADC_VINN_VREF_2 = 0x10u, /**< Use Vinn := 1/2 * Vref */ + ADC_VINN_AVSS = 0x00u, /**< Use Vinn := Vss */ +} qn908x_adc_vinn_t; + +/** + * @brief ADC SD Gain configuration + * + * This multiplies the sampled value (difference between +/- signals) by the + * given value. + * + * Note: these values logic xor 0x40 match the values for ADC_CFG_ADC_GAIN + * field. This is selected so that omitting this flag in the config field + * defaults to x1.0 gain but it can still be converted to the ADC_GAIN field + * with a simple logic xor. + */ +typedef enum { + ADC_GAIN_X05 = 0x40u, /**< Use gain := 0.5 */ + ADC_GAIN_X1 = 0x00u, /**< Use gain := 1 */ + ADC_GAIN_X15 = 0xC0u, /**< Use gain := 1.5 */ + ADC_GAIN_X20 = 0x80u, /**< Use gain := 2 */ +} qn908x_adc_gain_t; + +/** + * @brief CPU specific ADC configuration + * + * ADC Channel, Vinn, Vref and gain configuration. + * + * This value should be set to the logic or between the following values: + * * bit 3: the optional flag @ref ADC_PGA_ENABLE, + * * bits 4-5: a @ref qn908x_adc_vinn_t value defining Vinn if needed, + * * bits 6-7: a @ref qn908x_adc_gain_t optional gain value, + * * bit 8: the optional flag @ref ADC_VREF_GAIN_X15, + * * bits 9-13: the selected @ref qn908x_adc_channel_t, and + * * bits 14-15: the @ref qn908x_adc_vref_t value defining Vref. + * + * The same channels with different settings can be configured as different ADC + * lines in the board, just using different adc_conf_t entries. + */ +typedef uint16_t adc_conf_t; +/** @} */ + /** * @brief CPU specific timer Counter/Timers (CTIMER) configuration * @{ diff --git a/cpu/qn908x/periph/adc.c b/cpu/qn908x/periph/adc.c new file mode 100644 index 0000000000..afc38170a7 --- /dev/null +++ b/cpu/qn908x/periph/adc.c @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2020 iosabi + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup cpu_qn908x + * @ingroup drivers_periph_adc + * + * @{ + * + * @file + * @brief Low-level ADC driver implementation + * + * @author iosabi + * + * This driver supports most of the ADC capabilities of the ADC block in this + * CPU, but there are some functions left out of this driver: + * + * * The software decimation is fixed to 32 samples. This could be configured + * per line instead with many more filtering options. + * + * * There's no amplification support (PGA) other than 1x. The main issue with + * supporting this configuration even at build time only is that it also + * requires to adjust the signal's common mode voltage level so that it + * matches roughly Vcc/2, leaving enough dynamic range in the ADC. This is + * maybe beyond the scope of this interface, but it could be configured from + * the board if needed. See "PGA output VCM" section in the user manual for + * details. + * + * @} + */ + +#include +#include +#include + +#include "bitarithm.h" +#include "cpu.h" +#include "mutex.h" +#include "xtimer.h" + +#include "gpio_mux.h" +#include "periph/adc.h" + +#include "vendor/drivers/fsl_clock.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#ifndef QN908X_ADC_CLOCK +#define QN908X_ADC_CLOCK ADC_CLOCK_500K +#endif + +/* Value of the ADC_CTRL_CLKSEL field which is actually CLKSEL in the lower two + * bits (0 for 4 MHz source and 1 for 32 KHz source) and the divisor exponent + * in the bits 2 to 4 (0 for 128 divisor and 7 for 1 divisor). */ +static const uint8_t adc_ctrl_clksel[] = { + 28, /* 4 MHz*/ + 24, /* 2 MHz*/ + 20, /* 1 MHz*/ + 16, /* 500 KHz*/ + 12, /* 250 KHz*/ + 8, /* 125 KHz*/ + 4, /* 62.5 KHz*/ + 0, /* 31.25 KHz*/ + 1, /* 32 KHz or 32.768 KHz from the low speed source. */ +}; + +/* ADC oversample clock cycle in microseconds (used for delay). */ +static const uint8_t adc_clock_cycle_us[] = { + 1, /* 4 MHz*/ + 1, /* 2 MHz*/ + 1, /* 1 MHz*/ + 2, /* 500 KHz*/ + 4, /* 250 KHz*/ + 8, /* 125 KHz*/ + 16, /* 62.5 KHz*/ + 32, /* 31.25 KHz*/ + 32, /* 32 KHz or 32.768 KHz from the low speed source. */ +}; + +/* Mutex for the public interface. This guards against two threads calling the + * ADC at the same time. */ +static mutex_t adc_lock = MUTEX_INIT; + +/* Flag telling whether the ADC block was initialized. We only initialize the + * ADC block once, although some pin initialization is done per ADC line. */ +static bool adc_init_done = false; + +/* Mutex to lock the @ref adc_sample function waiting for the interrupt to + * retrieve the value. */ +static mutex_t adc_sample_isr_lock = MUTEX_INIT_LOCKED; + +/* Pointer to the variable that should hold the pending ADC result. This is + * used by the ISR to store the value. */ +static volatile int32_t* adc_data_ptr = NULL; + +/* Factory calibration actual voltage of the internal 1.2V bandgap source. */ +static uint32_t adc_bandgap_calib_mv; + +/** + * @brief Get the 1.2V bandgap reference voltage in mV. + */ +static uint32_t _adc_bandgap_calib_mv(void) { + uint32_t calib = *(uint32_t*)FSL_FEATURE_FLASH_ADDR_OF_BANDGAP_VOL; + DEBUG("[adc] bandgap calib = %" PRIu32 " mV\n", calib); + /* Check that the bandgap calibration value stored in the Flash Information + * Page (Section 7.3.1.1 in the User Manual) makes sense and use a + * reasonable default otherwise. */ + if (calib < 1214 || calib > 1228) { + calib = 1222; + } + return calib; +} + +/* Extract the channel number from the adc_conf_t. */ +#define ADC_CONF_T_CHANNEL_NUM(conf) (((conf) >> 9u) & 0x1fu) + +/* Mask to and against the adc_conf_t to get the channel enum value. */ +#define ADC_CONF_T_CHANNEL_MASK (0x1fu << 9u) + + +int adc_init(adc_t line) +{ + if (line >= ADC_NUMOF) { + return -1; + } + mutex_lock(&adc_lock); + if (!adc_init_done) { + DEBUG("[adc] ADC block init\n"); + adc_bandgap_calib_mv = _adc_bandgap_calib_mv(); + + /* Power ON the ADC by clearing the disable (DIS) bits. */ + SYSCON->PMU_CTRL1 &= ~(SYSCON_PMU_CTRL1_ADC_BUF_DIS_MASK | /* PGA */ + SYSCON_PMU_CTRL1_ADC_BG_DIS_MASK | + SYSCON_PMU_CTRL1_ADC_DIS_MASK | + SYSCON_PMU_CTRL1_ADC_VCM_DIS_MASK | /* VINN */ + SYSCON_PMU_CTRL1_ADC_VREF_DIS_MASK); + /* Need to wait 100 us before the ADC can be used for sampling. We could + * in theory avoid this wait since it is only needed before adc_sample() + * is called but it is short enough that it is safer to include it. */ + xtimer_usleep(100u); + + /* Enable the ADC clock so we can use the ADC. */ + CLOCK_EnableClock(kCLOCK_Adc); + /* Reset the ADC. */ + SYSCON->RST_SW_SET = SYSCON_RST_SW_SET_SET_ADC_RST_MASK; + SYSCON->RST_SW_CLR = SYSCON_RST_SW_CLR_CLR_ADC_RST_MASK; + + /* Enabled the ADC module: single conversion mode, scan disabled, window + * mode disabled, 32-bit signed data format, software trigger. + */ + ADC->CTRL = ADC_CTRL_CLKSEL(adc_ctrl_clksel[QN908X_ADC_CLOCK]) | + ADC_CTRL_CONV_MODE(1) | ADC_CTRL_SCAN_EN(0) | + ADC_CTRL_CH_IDX_EN(0) | + ADC_CTRL_DATA_FORMAT(1) | /* DATA is sign extended. */ + ADC_CTRL_SIG_INV_EN(0) | +#ifdef CONFIG_BOARD_HAS_ADC_PA06_CAP + ADC_CTRL_VREFO_EN(1) | +#endif /* CONFIG_BOARD_HAS_ADC_PA06_CAP */ + ADC_CTRL_TRIGGER(35 /* software trigger */); + +#ifdef CONFIG_BOARD_HAS_ADC_PA06_CAP + /* Use PA06 as the ADC_EX_CAP signal. */ + gpio_init_mux(GPIO_PIN(PORT_A, 6), 1); +#endif /* CONFIG_BOARD_HAS_ADC_PA06_CAP */ + + /* Always use CFG[0] to sample all channels. */ + ADC->CH_CFG = 0u; + + /* Enable the PGA chopper. */ + ADC->BG_BF |= ADC_BG_BF_PGA_CHOP_EN_MASK; + + /* Enable interrupts. */ + ADC->INTEN = ADC_INTEN_DAT_RDY_INTEN_MASK | ADC_INTEN_ADC_INTEN_MASK; + + NVIC_EnableIRQ(ADC_IRQn); + + adc_init_done = true; + } + const adc_conf_t conf = adc_config[line]; + DEBUG("[adc] ADC line %u init: ch=%u\n", line, ADC_CONF_T_CHANNEL_NUM(conf)); + + uint32_t func1_pins = 0; + /* ADC pins are all function 1. */ + switch (conf & ADC_CONF_T_CHANNEL_MASK) { + case ADC_CHANNEL_ADC0_ADC1: + func1_pins = (1u << 0) | (1u << 1); + break; + case ADC_CHANNEL_ADC2_ADC3: + func1_pins = (1u << 4) | (1u << 5); + break; + case ADC_CHANNEL_ADC4_ADC5: + func1_pins = (1u << 8) | (1u << 9); + break; + case ADC_CHANNEL_ADC6_ADC7: + func1_pins = (1u << 10) | (1u << 11); + break; + case ADC_CHANNEL_ADC0_VINN: + func1_pins = 1u << 0; + break; + case ADC_CHANNEL_ADC1_VINN: + func1_pins = 1u << 1; + break; + case ADC_CHANNEL_ADC2_VINN: + func1_pins = 1u << 4; + break; + case ADC_CHANNEL_ADC3_VINN: + func1_pins = 1u << 5; + break; + case ADC_CHANNEL_ADC4_VINN: + func1_pins = 1u << 8; + break; + case ADC_CHANNEL_ADC5_VINN: + func1_pins = 1u << 9; + break; + case ADC_CHANNEL_ADC6_VINN: + func1_pins = 1u << 10; + break; + case ADC_CHANNEL_ADC7_VINN: + func1_pins = 1u << 11; + break; + case ADC_CHANNEL_TEMP: + /* Enable the internal temperature source. */ + ADC->BG_BF |= ADC_BG_BF_TEMP_EN_MASK; + break; + case ADC_CHANNEL_VCC4_VINN: + /* Enable the Vcc/4 source. */ + SYSCON->ANA_EN |= SYSCON_ANA_EN_BAT_MON_EN_MASK; + break; + default: + break; + } + + const uint32_t vref_sel = conf & ADC_CTRL_VREF_SEL_MASK; + if (ADC_CTRL_VREF_SEL(1) == vref_sel || ADC_CTRL_VREF_SEL(2) == vref_sel) { + /* ADC_VREFI (A7 pin) used as Vref input. */ + gpio_init_mux(GPIO_PIN(PORT_A, 7), 1); + } + while (func1_pins) { + uint8_t pin; + func1_pins = bitarithm_test_and_clear(func1_pins, &pin); + gpio_init_mux(GPIO_PIN(PORT_A, pin), 1); + } + mutex_unlock(&adc_lock); + return 0; +} + +int32_t adc_sample(adc_t line, adc_res_t res) { + if (line >= ADC_NUMOF) { + return -1; + } + mutex_lock(&adc_lock); + const adc_conf_t conf = adc_config[line]; + + /* Enable the ADC and set the Vref selection. */ + ADC->CTRL = (ADC->CTRL & ~ADC_CTRL_VREF_SEL_MASK) | + ADC_CTRL_ENABLE_MASK | (conf & ADC_CTRL_VREF_SEL_MASK) ; + /* The ADC needs one ADC clock cycle before it can be triggered after we + * enabled it. */ + + /* Configure the channel parameters. We always use CFG[0]. */ + ADC->CH_SEL = 1u << ADC_CONF_T_CHANNEL_NUM(conf); + ADC->CFG[0] = + ADC_CFG_PGA_GAIN(0) | /* PGA gain = x1 */ + /* PGA_VINN, ADC_GAIN and VREF_GAIN directly from the config, flipping + * 0x40 to default ADC_GAIN to x1.0, flipping 0x30 to default Vinn to + * Vss and flipping 0x08 to default to PGA disabled (bypass enabled). */ + ((conf ^ 0x78) & (ADC_CFG_PGA_BP_MASK | ADC_CFG_PGA_VINN_MASK | + ADC_CFG_ADC_GAIN_MASK)) | + ADC_CFG_PGA_VCM_EN(0) | /* Vcm control disabled. */ + /* TODO: Allow the board to configure the decimation. */ + ADC_CFG_DOWN_SAMPLE_RATE(1) | /* down sample 32 */ + ADC_CFG_DS_DATA_STABLE(7) | + ADC_CFG_SCAN_INTV(4); /* Switching ADC source every 32 clock cycles. */ + + /* Need to wait for one ADC cycle before it can be started. */ + xtimer_usleep(adc_clock_cycle_us[QN908X_ADC_CLOCK]); + + /* Configure the destination of the ADC value read from the interrupt. */ + volatile int32_t adc_data = 0; + adc_data_ptr = &adc_data; + + /* Triggers the sample event. When done the interrupt will fire and release + * the mutex. */ + ADC->CTRL |= ADC_CTRL_SW_START_MASK; + /* Wait for the interrupt to return a value in adc_data. */ + mutex_lock(&adc_sample_isr_lock); + mutex_unlock(&adc_lock); + + int32_t ret = -1; + /* adc_data has 23-bit signed number, with the sign extended to an int32_t. + */ + if (res <= ADC_RES_MAX) { + ret = adc_data >> (22 - res); + } else if (res == ADC_RES_UV && + (conf & ADC_CTRL_VREF_SEL_MASK) == ADC_VREF_1V2) { + /* Returning in uV is only supported when using Vref as the internal + * 1.2v. */ + uint32_t vref = adc_bandgap_calib_mv; + if (conf & ADC_VREF_GAIN_X15) { + vref = vref + vref / 2; + } + /* adc_data is a signed 23 bit number and the Vref is at most 1.8v, so + * an unsigned 11 bit number. To make this fit in signed 32-bit + * arithmetic we need to drop some bits from the adc_data. Then we + * convert the result to uV dividing by (1 << 20) which is now the full + * scale of (adc_data >> 2) and multiplying by 1000. The last two + * operations can be done as a single division still in 32-bit integer + * space. */ + ret = ((adc_data >> 2) * (int32_t)vref) / ((1 << 20) / 1000); + } + DEBUG("[adc] sample line %u: ch=%u res=%u conf=0x%.4" PRIx16 + " adc=0x%.8" PRIx32 " ret=%" PRIi32 "\n", + line, ADC_CONF_T_CHANNEL_NUM(conf), (unsigned)res, conf, adc_data, + ret); + + return ret; +} + +void isr_adc(void) +{ + if (ADC->INT & ADC_INT_DAT_RDY_INT_MASK) { + uint32_t data = ADC->DATA; + /* The DAT_RDY_INT bit clears automatically when reading the data. */ + if (adc_data_ptr) { + *adc_data_ptr = data; + } + adc_data_ptr = NULL; + mutex_unlock(&adc_sample_isr_lock); + } + + cortexm_isr_end(); +}