348 lines
7.9 KiB
C
348 lines
7.9 KiB
C
/* Copyright 2014 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
|
|
static const struct dma_option dma_single = {
|
|
STM32_DMAC_ADC, (void *)&STM32_ADC_DR,
|
|
STM32_DMA_CCR_MSIZE_32_BIT | STM32_DMA_CCR_PSIZE_32_BIT,
|
|
};
|
|
|
|
#ifndef CONFIG_ADC_SAMPLE_TIME
|
|
#define CONFIG_ADC_SAMPLE_TIME STM32_ADC_SMPR_13_5_CY
|
|
#endif
|
|
|
|
static const struct adc_profile_t profile = {
|
|
/* Sample all channels once using DMA */
|
|
.cfgr1_reg = STM32_ADC_CFGR1_OVRMOD,
|
|
.cfgr2_reg = 0,
|
|
.smpr_reg = CONFIG_ADC_SAMPLE_TIME,
|
|
.ier_reg = 0,
|
|
.dma_option = &dma_single,
|
|
.dma_buffer_size = 1,
|
|
};
|
|
#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_APB2ENR & BIT(9) && (STM32_ADC_CR & STM32_ADC_CR_ADEN))
|
|
return;
|
|
|
|
/* Enable ADC clock */
|
|
clock_enable_module(MODULE_ADC, 1);
|
|
/* check HSI14 in RCC ? ON by default */
|
|
|
|
/* ADC calibration (done with ADEN = 0) */
|
|
STM32_ADC_CR = STM32_ADC_CR_ADCAL; /* set ADCAL = 1, ADC off */
|
|
/* wait for the end of calibration */
|
|
while (STM32_ADC_CR & STM32_ADC_CR_ADCAL)
|
|
;
|
|
|
|
/* Single conversion, right aligned, 12-bit */
|
|
STM32_ADC_CFGR1 = profile.cfgr1_reg;
|
|
/* clock is ADCCLK (ADEN must be off when writing this reg) */
|
|
STM32_ADC_CFGR2 = profile.cfgr2_reg;
|
|
|
|
/*
|
|
* ADC enable (note: takes 4 ADC clocks between end of calibration
|
|
* and setting ADEN).
|
|
*/
|
|
STM32_ADC_CR = STM32_ADC_CR_ADEN;
|
|
while (!(STM32_ADC_ISR & STM32_ADC_ISR_ADRDY))
|
|
STM32_ADC_CR = STM32_ADC_CR_ADEN;
|
|
}
|
|
|
|
static void adc_configure(int ain_id, enum stm32_adc_smpr sample_rate)
|
|
{
|
|
/* Sampling time */
|
|
if (sample_rate == STM32_ADC_SMPR_DEFAULT ||
|
|
sample_rate >= STM32_ADC_SMPR_COUNT)
|
|
STM32_ADC_SMPR = profile.smpr_reg;
|
|
else
|
|
STM32_ADC_SMPR = STM32_ADC_SMPR_SMP(sample_rate);
|
|
|
|
/* Select channel to convert */
|
|
STM32_ADC_CHSELR = BIT(ain_id);
|
|
|
|
/* Disable DMA */
|
|
STM32_ADC_CFGR1 &= ~STM32_ADC_CFGR1_DMAEN;
|
|
}
|
|
|
|
#ifdef CONFIG_ADC_WATCHDOG
|
|
|
|
static int watchdog_ain_id;
|
|
static int watchdog_delay_ms;
|
|
|
|
static void adc_continuous_read(int ain_id)
|
|
{
|
|
adc_configure(ain_id, STM32_ADC_SMPR_DEFAULT);
|
|
|
|
/* CONT=1 -> continuous mode on */
|
|
STM32_ADC_CFGR1 |= STM32_ADC_CFGR1_CONT;
|
|
|
|
/* Start continuous conversion */
|
|
STM32_ADC_CR |= BIT(2); /* ADSTART */
|
|
}
|
|
|
|
static void adc_continuous_stop(void)
|
|
{
|
|
/* Stop on-going conversion */
|
|
STM32_ADC_CR |= BIT(4); /* ADSTP */
|
|
|
|
/* Wait for conversion to stop */
|
|
while (STM32_ADC_CR & BIT(4))
|
|
;
|
|
|
|
/* CONT=0 -> continuous mode off */
|
|
STM32_ADC_CFGR1 &= ~STM32_ADC_CFGR1_CONT;
|
|
}
|
|
|
|
static void adc_interval_read(int ain_id, int interval_ms)
|
|
{
|
|
adc_configure(ain_id, STM32_ADC_SMPR_DEFAULT);
|
|
|
|
/* EXTEN=01 -> hardware trigger detection on rising edge */
|
|
STM32_ADC_CFGR1 = (STM32_ADC_CFGR1 & ~STM32_ADC_CFGR1_EXTEN_MASK)
|
|
| STM32_ADC_CFGR1_EXTEN_RISE;
|
|
|
|
/* EXTSEL=TRG3 -> Trigger on TIM3_TRGO */
|
|
STM32_ADC_CFGR1 = (STM32_ADC_CFGR1 & ~STM32_ADC_CFGR1_TRG_MASK) |
|
|
STM32_ADC_CFGR1_TRG3;
|
|
|
|
__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 |= BIT(2); /* ADSTART */
|
|
}
|
|
|
|
static void adc_interval_stop(void)
|
|
{
|
|
/* EXTEN=00 -> hardware trigger detection disabled */
|
|
STM32_ADC_CFGR1 &= ~STM32_ADC_CFGR1_EXTEN_MASK;
|
|
|
|
/* Set ADSTP to clear ADSTART */
|
|
STM32_ADC_CR |= BIT(4); /* ADSTP */
|
|
|
|
/* Wait for conversion to stop */
|
|
while (STM32_ADC_CR & BIT(4))
|
|
;
|
|
|
|
/* Stop the timer */
|
|
STM32_TIM_CR1(TIM_ADC) &= ~0x1;
|
|
}
|
|
|
|
static int adc_watchdog_enabled(void)
|
|
{
|
|
return STM32_ADC_CFGR1 & STM32_ADC_CFGR1_AWDEN;
|
|
}
|
|
|
|
static int adc_enable_watchdog_no_lock(void)
|
|
{
|
|
/* Select channel */
|
|
STM32_ADC_CFGR1 = (STM32_ADC_CFGR1 & ~STM32_ADC_CFGR1_AWDCH_MASK) |
|
|
(watchdog_ain_id << 26);
|
|
adc_configure(watchdog_ain_id, STM32_ADC_SMPR_DEFAULT);
|
|
|
|
/* Clear AWD interrupt flag */
|
|
STM32_ADC_ISR = 0x80;
|
|
/* Set Watchdog enable bit on a single channel */
|
|
STM32_ADC_CFGR1 |= STM32_ADC_CFGR1_AWDEN | STM32_ADC_CFGR1_AWDSGL;
|
|
/* Enable interrupt */
|
|
STM32_ADC_IER |= STM32_ADC_IER_AWDIE;
|
|
|
|
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)
|
|
{
|
|
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 &= ~STM32_ADC_CFGR1_AWDEN;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
int adc_disable_watchdog(void)
|
|
{
|
|
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;
|
|
}
|
|
|
|
#else /* CONFIG_ADC_WATCHDOG */
|
|
|
|
static int adc_watchdog_enabled(void) { return 0; }
|
|
static int adc_enable_watchdog_no_lock(void) { return 0; }
|
|
static int adc_disable_watchdog_no_lock(void) { return 0; }
|
|
|
|
#endif /* CONFIG_ADC_WATCHDOG */
|
|
|
|
int adc_read_channel(enum adc_channel ch)
|
|
{
|
|
const struct adc_t *adc = adc_channels + ch;
|
|
int value;
|
|
int restore_watchdog = 0;
|
|
|
|
mutex_lock(&adc_lock);
|
|
|
|
adc_init(adc);
|
|
|
|
if (adc_watchdog_enabled()) {
|
|
restore_watchdog = 1;
|
|
adc_disable_watchdog_no_lock();
|
|
}
|
|
|
|
adc_configure(adc->channel, adc->sample_rate);
|
|
|
|
/* Clear flags */
|
|
STM32_ADC_ISR = 0xe;
|
|
|
|
/* Start conversion */
|
|
STM32_ADC_CR |= BIT(2); /* ADSTART */
|
|
|
|
/* Wait for end of conversion */
|
|
while (!(STM32_ADC_ISR & BIT(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;
|
|
}
|
|
|
|
void adc_disable(void)
|
|
{
|
|
STM32_ADC_CR |= STM32_ADC_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.
|
|
*/
|
|
}
|