From b9b5c89c301d5c7eb68b5ad5b9e05978ce93f436 Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Mon, 17 Mar 2014 16:52:57 -0700 Subject: stm32: implement ADC support for STM32F0xx Replace the stubs by an actual implementation for ADC and Analog watchdog support on STM32F0xx chips. Signed-off-by: Vincent Palatin BRANCH=none BUG=none TEST=manually read ADC values on STM32F072B discovery. TEST=read all ADC values at once. TEST=Enable watchdog and check it fires when the voltage goes out of range. TEST=read ADC value(s) while watchdog is enabled. TEST=Disable watchdog and check it's actually disabled. Change-Id: Ie6fbd1aa95a3d76394fa47803e8cfc24bf5e4562 Reviewed-on: https://chromium-review.googlesource.com/190710 Reviewed-by: Vincent Palatin Tested-by: Vic Yang Commit-Queue: Vincent Palatin --- chip/stm32/adc-stm32f0.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++- chip/stm32/adc_chip.h | 3 +- include/adc.h | 11 ++ 3 files changed, 277 insertions(+), 5 deletions(-) diff --git a/chip/stm32/adc-stm32f0.c b/chip/stm32/adc-stm32f0.c index ca9cea1248..abf8830b6c 100644 --- a/chip/stm32/adc-stm32f0.c +++ b/chip/stm32/adc-stm32f0.c @@ -5,36 +5,296 @@ #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; + +static int watchdog_ain_id; +static int watchdog_delay_ms; + +static const struct dma_option dma_adc_option = { + STM32_DMAC_ADC, (void *)&STM32_ADC_DR, + STM32_DMA_CCR_MSIZE_32_BIT | STM32_DMA_CCR_PSIZE_32_BIT, +}; + +static void adc_configure(int ain_id) +{ + /* Select channel to convert */ + STM32_ADC_CHSELR = 1 << ain_id; + + /* Disable DMA */ + STM32_ADC_CFGR1 &= ~0x1; +} + +static void adc_continuous_read(int ain_id) +{ + adc_configure(ain_id); + + /* CONT=1 -> continuous mode on */ + STM32_ADC_CFGR1 |= 1 << 13; + + /* Start continuous conversion */ + STM32_ADC_CR |= 1 << 2; /* ADSTART */ +} + +static void adc_continuous_stop(void) +{ + /* Stop on-going conversion */ + STM32_ADC_CR |= 1 << 4; /* ADSTP */ + + /* Wait for conversion to stop */ + while (STM32_ADC_CR & (1 << 4)) + ; + + /* CONT=0 -> continuous mode off */ + STM32_ADC_CFGR1 &= ~(1 << 13); +} + +static void adc_interval_read(int ain_id, int interval_ms) +{ + adc_configure(ain_id); + + /* EXTEN=01 -> hardware trigger detection on rising edge */ + STM32_ADC_CFGR1 = (STM32_ADC_CFGR1 & ~0xc00) | (1 << 10); + + /* EXTSEL=TRG3 -> Trigger on TIM3_TRGO */ + STM32_ADC_CFGR1 = (STM32_ADC_CFGR1 & ~0x1c0) | (3 << 6); + + __hw_timer_enable_clock(TIM_ADC, 1); + + /* Upcounter, counter disabled, update event only on underflow */ + STM32_TIM_CR1(TIM_ADC) = 0x0004; + + /* TRGO on update event */ + STM32_TIM_CR2(TIM_ADC) = 0x0020; + STM32_TIM_SMCR(TIM_ADC) = 0x0000; + + /* Auto-reload value */ + STM32_TIM_ARR(TIM_ADC) = interval_ms & 0xffff; + + /* Set prescaler to tick per millisecond */ + STM32_TIM_PSC(TIM_ADC) = (clock_get_freq() / MSEC) - 1; + + /* Start counting */ + STM32_TIM_CR1(TIM_ADC) |= 1; + + /* Start ADC conversion */ + STM32_ADC_CR |= 1 << 2; /* ADSTART */ +} + +static void adc_interval_stop(void) +{ + /* EXTEN=00 -> hardware trigger detection disabled */ + STM32_ADC_CFGR1 &= ~0xc00; + + /* Set ADSTP to clear ADSTART */ + STM32_ADC_CR |= 1 << 4; /* ADSTP */ + + /* Wait for conversion to stop */ + while (STM32_ADC_CR & (1 << 4)) + ; + + /* Stop the timer */ + STM32_TIM_CR1(TIM_ADC) &= ~0x1; +} + +static int adc_watchdog_enabled(void) +{ + return STM32_ADC_CFGR1 & (1 << 23); +} + +static int adc_enable_watchdog_no_lock(void) +{ + /* Select channel */ + STM32_ADC_CFGR1 = (STM32_ADC_CFGR1 & ~0x7c000000) | + (watchdog_ain_id << 26); + adc_configure(watchdog_ain_id); + + /* Clear AWD interupt flag */ + STM32_ADC_ISR = 0x80; + /* Set Watchdog enable bit on a single channel */ + STM32_ADC_CFGR1 |= (1 << 23) | (1 << 22); + /* Enable interrupt */ + STM32_ADC_IER |= 1 << 7; + + if (watchdog_delay_ms) + adc_interval_read(watchdog_ain_id, watchdog_delay_ms); + else + adc_continuous_read(watchdog_ain_id); + + return EC_SUCCESS; +} + int adc_enable_watchdog(int ain_id, int high, int low) { - return EC_ERROR_UNKNOWN; + int ret; + + mutex_lock(&adc_lock); + + watchdog_ain_id = ain_id; + + /* Set thresholds */ + STM32_ADC_TR = ((high & 0xfff) << 16) | (low & 0xfff); + + ret = adc_enable_watchdog_no_lock(); + mutex_unlock(&adc_lock); + return ret; +} + +static int adc_disable_watchdog_no_lock(void) +{ + if (watchdog_delay_ms) + adc_interval_stop(); + else + adc_continuous_stop(); + + /* Clear Watchdog enable bit */ + STM32_ADC_CFGR1 &= ~(1 << 23); + + return EC_SUCCESS; } int adc_disable_watchdog(void) { - return EC_ERROR_UNKNOWN; + int ret; + + mutex_lock(&adc_lock); + ret = adc_disable_watchdog_no_lock(); + mutex_unlock(&adc_lock); + + return ret; +} + +int adc_set_watchdog_delay(int delay_ms) +{ + int resume_watchdog = 0; + + mutex_lock(&adc_lock); + if (adc_watchdog_enabled()) { + resume_watchdog = 1; + adc_disable_watchdog_no_lock(); + } + + watchdog_delay_ms = delay_ms; + + if (resume_watchdog) + adc_enable_watchdog_no_lock(); + mutex_unlock(&adc_lock); + + return EC_SUCCESS; } int adc_read_channel(enum adc_channel ch) { - return EC_ERROR_UNKNOWN; + const struct adc_t *adc = adc_channels + ch; + int value; + int restore_watchdog = 0; + + mutex_lock(&adc_lock); + if (adc_watchdog_enabled()) { + restore_watchdog = 1; + adc_disable_watchdog_no_lock(); + } + + adc_configure(adc->channel); + + /* Clear flags */ + STM32_ADC_ISR = 0xe; + + /* Start conversion */ + STM32_ADC_CR |= 1 << 2; /* ADSTART */ + + /* Wait for end of conversion */ + while (!(STM32_ADC_ISR & (1 << 2))) + ; + /* read converted value */ + value = STM32_ADC_DR; + + if (restore_watchdog) + adc_enable_watchdog_no_lock(); + mutex_unlock(&adc_lock); + + return value * adc->factor_mul / adc->factor_div + adc->shift; } int adc_read_all_channels(int *data) { - return EC_ERROR_UNKNOWN; + int i; + uint32_t channels = 0; + uint32_t raw_data[ADC_CH_COUNT]; + const struct adc_t *adc; + int restore_watchdog = 0; + int ret = EC_SUCCESS; + + mutex_lock(&adc_lock); + + if (adc_watchdog_enabled()) { + restore_watchdog = 1; + adc_disable_watchdog_no_lock(); + } + + /* Select all used channels */ + for (i = 0; i < ADC_CH_COUNT; ++i) + channels |= 1 << adc_channels[i].channel; + STM32_ADC_CHSELR = channels; + + /* Enable DMA */ + STM32_ADC_CFGR1 |= 0x1; + + dma_start_rx(&dma_adc_option, ADC_CH_COUNT, raw_data); + + /* Clear flags */ + STM32_ADC_ISR = 0xe; + + STM32_ADC_CR |= 1 << 2; /* ADSTART */ + + if (dma_wait(STM32_DMAC_ADC)) { + ret = EC_ERROR_UNKNOWN; + goto fail; /* goto fail; goto fail; */ + } + + for (i = 0; i < ADC_CH_COUNT; ++i) { + adc = adc_channels + i; + data[i] = (raw_data[i] & 0xffff) * + adc->factor_mul / adc->factor_div + adc->shift; + } + +fail: + if (restore_watchdog) + adc_enable_watchdog_no_lock(); + mutex_unlock(&adc_lock); + return ret; } static void adc_init(void) { + /* Enable ADC clock */ + STM32_RCC_APB2ENR |= (1 << 9); + /* check HSI14 in RCC ? ON by default */ + + /* ADC calibration (done with ADEN = 0) */ + STM32_ADC_CR = 1 << 31; /* set ADCAL = 1, ADC off */ + /* wait for the end of calibration */ + while (STM32_ADC_CR & (1 << 31)) + ; + + /* ADC enabled */ + STM32_ADC_CR = 1 << 0; + + /* Single conversion, right aligned, 12-bit */ + STM32_ADC_CFGR1 = 1 << 12; /* (1 << 15) => AUTOOFF */; + /* clock is ADCCLK */ + STM32_ADC_CFGR2 = 0; + /* Sampling time : 13.5 ADC clock cycles. */ + STM32_ADC_SMPR = 2; } DECLARE_HOOK(HOOK_INIT, adc_init, HOOK_PRIO_DEFAULT); diff --git a/chip/stm32/adc_chip.h b/chip/stm32/adc_chip.h index bc493ea75b..9d28b027a8 100644 --- a/chip/stm32/adc_chip.h +++ b/chip/stm32/adc_chip.h @@ -19,7 +19,8 @@ struct adc_t { /* * Boards must provide this list of ADC channel definitions. This must match - * the enum adc_channel list provided by the board. + * the enum adc_channel list provided by the board. Also, for STM32F0, this + * must be ordered by AIN ID. */ extern const struct adc_t adc_channels[]; diff --git a/include/adc.h b/include/adc.h index 3ff88b77f9..efca057445 100644 --- a/include/adc.h +++ b/include/adc.h @@ -57,4 +57,15 @@ int adc_enable_watchdog(int ain_id, int high, int low); */ int adc_disable_watchdog(void); +/** + * Set the delay between ADC watchdog samples. This can be used as a trade-off + * of power consumption and performance. + * + * @param delay_ms The delay in milliseconds between two ADC watchdog + * samples. + * + * @return EC_SUCCESS, or non-zero if any error or not supported. + */ +int adc_set_watchdog_delay(int delay_ms); + #endif /* __CROS_EC_ADC_H */ -- cgit v1.2.1