blob: fd9711d44ae663675fb7830a168b3966743ef821 [file] [log] [blame]
/* Copyright (c) 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;
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,
};
static const struct adc_profile_t profile = {
/* Sample all channels once using DMA */
.cfgr1_reg = STM32_ADC_CFGR1_OVRMOD,
.cfgr2_reg = 0,
.smpr_reg = STM32_ADC_SMPR_13_5_CY,
.ier_reg = 0,
.dma_option = &dma_single,
.dma_buffer_size = 1,
};
#endif
#ifdef CONFIG_ADC_PROFILE_FAST_CONTINUOUS
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 = STM32_ADC_SMPR_1_5_CY,
/* 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_configure(int ain_id)
{
/* Select channel to convert */
STM32_ADC_CHSELR = 1 << 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);
/* CONT=1 -> continuous mode on */
STM32_ADC_CFGR1 |= STM32_ADC_CFGR1_CONT;
/* 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 &= ~STM32_ADC_CFGR1_CONT;
}
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 & ~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 |= 1 << 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 |= 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 & 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);
/* 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);
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;
}
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.
*/
}
static void adc_init(void)
{
/*
* 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 & (1 << 9) && (STM32_ADC_CR & STM32_ADC_CR_ADEN))
return;
/* Enable ADC clock */
STM32_RCC_APB2ENR |= (1 << 9);
/* 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;
/* Sampling time */
STM32_ADC_SMPR = profile.smpr_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;
}
DECLARE_HOOK(HOOK_INIT, adc_init, HOOK_PRIO_INIT_ADC);