diff options
Diffstat (limited to 'chip/stm32')
-rw-r--r-- | chip/stm32/adc-stm32l4.c | 235 | ||||
-rw-r--r-- | chip/stm32/adc_chip.h | 24 |
2 files changed, 258 insertions, 1 deletions
diff --git a/chip/stm32/adc-stm32l4.c b/chip/stm32/adc-stm32l4.c new file mode 100644 index 0000000000..8c6e737588 --- /dev/null +++ b/chip/stm32/adc-stm32l4.c @@ -0,0 +1,235 @@ +/* Copyright 2021 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "adc.h" +#include "adc_chip.h" +#include "clock.h" +#include "common.h" +#include "console.h" +#include "dma.h" +#include "hooks.h" +#include "hwtimer.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +struct mutex adc_lock; + +struct adc_profile_t { + /* Register values. */ + uint32_t cfgr1_reg; + uint32_t cfgr2_reg; + uint32_t smpr_reg; /* Default Sampling Rate */ + uint32_t ier_reg; + /* DMA config. */ + const struct dma_option *dma_option; + /* Size of DMA buffer, in units of ADC_CH_COUNT. */ + int dma_buffer_size; +}; + +#ifdef CONFIG_ADC_PROFILE_SINGLE +#ifndef CONFIG_ADC_SAMPLE_TIME +#define CONFIG_ADC_SAMPLE_TIME STM32_ADC_SMPR_12_5_CY +#endif +#endif + +#if defined(CHIP_FAMILY_STM32L4) +#define ADC_CALIBRATION_TIMEOUT_US 100000U +#define ADC_ENABLE_TIMEOUT_US 200000U +#define ADC_CONVERSION_TIMEOUT_US 200000U + +#define NUMBER_OF_ADC_CHANNEL 2 +uint8_t adc1_initialized; +#endif + +#ifdef CONFIG_ADC_PROFILE_FAST_CONTINUOUS + +#ifndef CONFIG_ADC_SAMPLE_TIME +#define CONFIG_ADC_SAMPLE_TIME STM32_ADC_SMPR_1_5_CY +#endif + +static const struct dma_option dma_continuous = { + STM32_DMAC_ADC, (void *)&STM32_ADC_DR, + STM32_DMA_CCR_MSIZE_32_BIT | STM32_DMA_CCR_PSIZE_32_BIT | + STM32_DMA_CCR_CIRC, +}; + +static const struct adc_profile_t profile = { + /* Sample all channels continuously using DMA */ + .cfgr1_reg = STM32_ADC_CFGR1_OVRMOD | + STM32_ADC_CFGR1_CONT | + STM32_ADC_CFGR1_DMACFG, + .cfgr2_reg = 0, + .smpr_reg = CONFIG_ADC_SAMPLE_TIME, + /* Fire interrupt at end of sequence. */ + .ier_reg = STM32_ADC_IER_EOSEQIE, + .dma_option = &dma_continuous, + /* Double-buffer our samples. */ + .dma_buffer_size = 2, +}; +#endif + +static void adc_init(const struct adc_t *adc) +{ + /* + * If clock is already enabled, and ADC module is enabled + * then this is a warm reboot and ADC is already initialized. + */ + + if (STM32_RCC_AHB2ENR & STM32_RCC_AHB2ENR_ADCEN && + (STM32_ADC1_CR & STM32_ADC1_CR_ADEN)) + return; + + /* Enable ADC clock */ + clock_enable_module(MODULE_ADC, 1); + + /* set ADC clock to 20MHz */ + STM32_ADC1_CCR &= ~0x003C0000; + STM32_ADC1_CCR |= 0x00080000; + + STM32_RCC_AHB2ENR |= STM32_RCC_HB2_GPIOA; + STM32_RCC_AHB2ENR |= STM32_RCC_HB2_GPIOB; + + /* Set ADC data resolution */ + STM32_ADC1_CFGR &= ~STM32_ADC1_CFGR_CONT; + /* Set ADC conversion data alignment */ + STM32_ADC1_CFGR &= ~STM32_ADC1_CFGR_ALIGN; + /* Set ADC delayed conversion mode */ + STM32_ADC1_CFGR &= ~STM32_ADC1_CFGR_AUTDLY; +} + +static void adc_configure(int ain_id, int ain_rank, + enum stm32_adc_smpr sample_rate) +{ + /* Select Sampling time and channel to convert */ + if (ain_id <= 10) { + STM32_ADC1_SMPR1 &= ~(7 << ((ain_id - 1) * 3)); + STM32_ADC1_SMPR1 |= (sample_rate << ((ain_id - 1) * 3)); + } else { + STM32_ADC1_SMPR2 &= ~(7 << ((ain_id - 11) * 3)); + STM32_ADC1_SMPR2 |= (sample_rate << ((ain_id - 11) * 3)); + } + + /* Setup Rank */ + STM32_ADC1_JSQR &= ~(0x03); + STM32_ADC1_JSQR |= NUMBER_OF_ADC_CHANNEL - 1; + + STM32_ADC1_JSQR &= ~(0x1F << (((ain_rank - 1) * 6) + 8)); + STM32_ADC1_JSQR |= (ain_id << (((ain_rank - 1) * 6) + 8)); + + /* Disable DMA */ + STM32_ADC1_CFGR &= ~STM32_ADC1_CFGR_DMAEN; +} + +int adc_read_channel(enum adc_channel ch) +{ + const struct adc_t *adc = adc_channels + ch; + + int value = 0; + uint32_t wait_loop_index; + + mutex_lock(&adc_lock); + + if (adc1_initialized == 0) { + adc_init(adc); + + /* Configure Injected Channel N */ + for (uint8_t i = 0; i < NUMBER_OF_ADC_CHANNEL; i++) { + const struct adc_t *adc = adc_channels + i; + + adc_configure(adc->channel, adc->rank, + adc->sample_rate); + } + + if ((STM32_ADC1_CR & STM32_ADC1_CR_ADEN) != + STM32_ADC1_CR_ADEN) { + /* Disable ADC deep power down (enabled by default after + * reset state) + */ + STM32_ADC1_CR &= ~STM32_ADC1_CR_DEEPPWD; + /* Enable ADC internal voltage regulator */ + STM32_ADC1_CR |= STM32_ADC1_CR_ADVREGEN; + } + + /* + * Delay for ADC internal voltage regulator stabilization. + * Compute number of CPU cycles to wait for, from delay in us. + * + * Note: Variable divided by 2 to compensate partially + * CPU processing cycles (depends on compilation optimization). + * + * Note: If system core clock frequency is below 200kHz, wait + * time is only a few CPU processing cycles. + */ + wait_loop_index = ((20 * (80000000 / (100000 * 2))) / 10); + while (wait_loop_index-- != 0) + ; + + /* Run ADC self calibration */ + STM32_ADC1_CR |= STM32_ADC1_CR_ADCAL; + + /* wait for the end of calibration */ + wait_loop_index = ((ADC_CALIBRATION_TIMEOUT_US * + (CPU_CLOCK / (100000 * 2))) / 10); + while (STM32_ADC1_CR & STM32_ADC1_CR_ADCAL) { + if (wait_loop_index-- == 0) + break; + } + + /* Enable ADC */ + STM32_ADC1_ISR |= STM32_ADC1_ISR_ADRDY; + STM32_ADC1_CR |= STM32_ADC1_CR_ADEN; + wait_loop_index = ((ADC_ENABLE_TIMEOUT_US * + (CPU_CLOCK / (100000 * 2))) / 10); + while (!(STM32_ADC1_ISR & STM32_ADC1_ISR_ADRDY)) { + wait_loop_index--; + if (wait_loop_index == 0) + break; + } + + adc1_initialized = 1; + } + + /* Start injected conversion */ + STM32_ADC1_CR |= BIT(3); /* JADSTART */ + + /* Wait for end of injected conversion */ + wait_loop_index = ((ADC_CONVERSION_TIMEOUT_US * + (CPU_CLOCK / (100000 * 2))) / 10); + while (!(STM32_ADC1_ISR & BIT(6))) { + if (wait_loop_index-- == 0) + break; + } + + /* Clear JEOS bit */ + STM32_ADC1_ISR |= BIT(6); + + /* read converted value */ + if (adc->rank == 1) + value = STM32_ADC1_JDR1; + if (adc->rank == 2) + value = STM32_ADC1_JDR2; + + mutex_unlock(&adc_lock); + + return value * adc->factor_mul / adc->factor_div + adc->shift; +} + +void adc_disable(void) +{ + /* Disable ADC */ + /* Do not Set ADDIS when ADC is disabled */ + adc1_initialized = 0; + + if (STM32_ADC1_CR & STM32_ADC1_CR_ADEN) + STM32_ADC1_CR |= STM32_ADC1_CR_ADDIS; + + /* + * Note that the ADC is not in OFF state immediately. + * Once the ADC is effectively put into OFF state, + * STM32_ADC_CR_ADDIS bit will be cleared by hardware. + */ +} diff --git a/chip/stm32/adc_chip.h b/chip/stm32/adc_chip.h index 413653e4d7..0d6ae7fe51 100644 --- a/chip/stm32/adc_chip.h +++ b/chip/stm32/adc_chip.h @@ -10,6 +10,20 @@ #include "stdint.h" +#ifdef CHIP_FAMILY_STM32L4 +enum stm32_adc_smpr { + STM32_ADC_SMPR_DEFAULT = 0, + STM32_ADC_SMPR_2_5_CY, + STM32_ADC_SMPR_6_5_CY, + STM32_ADC_SMPR_12_5_CY, + STM32_ADC_SMPR_24_5_CY, + STM32_ADC_SMPR_47_5_CY, + STM32_ADC_SMPR_92_5_CY, + STM32_ADC_SMPR_247_5_CY, + STM32_ADC_SMPR_640_5_CY, + STM32_ADC_SMPR_COUNT, +}; +#else enum stm32_adc_smpr { STM32_ADC_SMPR_DEFAULT = 0, STM32_ADC_SMPR_1_5_CY, @@ -22,6 +36,7 @@ enum stm32_adc_smpr { STM32_ADC_SMPR_239_5_CY, STM32_ADC_SMPR_COUNT, }; +#endif /* Data structure to define ADC channels. */ struct adc_t { @@ -30,7 +45,11 @@ struct adc_t { int factor_div; int shift; int channel; -#ifdef CHIP_FAMILY_STM32F0 +#ifdef CHIP_FAMILY_STM32L4 + int rank; +#endif + +#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32L4) enum stm32_adc_smpr sample_rate; /* Sampling Rate of the channel */ #endif }; @@ -52,4 +71,7 @@ void adc_disable(void); /* Just plain id mapping for code readability */ #define STM32_AIN(x) (x) +/* Add for ADCs with RANK */ +#define STM32_RANK(x) (x) + #endif /* __CROS_EC_ADC_CHIP_H */ |