summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVincent Palatin <vpalatin@chromium.org>2014-03-17 16:52:57 -0700
committerchrome-internal-fetch <chrome-internal-fetch@google.com>2014-03-20 23:51:28 +0000
commitb9b5c89c301d5c7eb68b5ad5b9e05978ce93f436 (patch)
treef175f95ccf20c4dd01b662f086ce0c2459e122b1
parent6472ce964dba986907ee4b1a0250c8e27658a492 (diff)
downloadchrome-ec-b9b5c89c301d5c7eb68b5ad5b9e05978ce93f436.tar.gz
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 <vpalatin@chromium.org> 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 <vpalatin@chromium.org> Tested-by: Vic Yang <victoryang@chromium.org> Commit-Queue: Vincent Palatin <vpalatin@chromium.org>
-rw-r--r--chip/stm32/adc-stm32f0.c268
-rw-r--r--chip/stm32/adc_chip.h3
-rw-r--r--include/adc.h11
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 */