172 lines
3.3 KiB
C
172 lines
3.3 KiB
C
|
/* Copyright 2012 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 "common.h"
|
||
|
#include "console.h"
|
||
|
#include "clock.h"
|
||
|
#include "dma.h"
|
||
|
#include "hooks.h"
|
||
|
#include "registers.h"
|
||
|
#include "task.h"
|
||
|
#include "timer.h"
|
||
|
#include "util.h"
|
||
|
|
||
|
#define ADC_SINGLE_READ_TIMEOUT 3000 /* 3 ms */
|
||
|
|
||
|
struct mutex adc_lock;
|
||
|
|
||
|
static int restore_clock;
|
||
|
|
||
|
static inline void adc_set_channel(int sample_id, int channel)
|
||
|
{
|
||
|
uint32_t mask, val;
|
||
|
volatile uint32_t *sqr_reg;
|
||
|
int reg_id;
|
||
|
|
||
|
reg_id = 5 - sample_id / 6;
|
||
|
|
||
|
mask = 0x1f << ((sample_id % 6) * 5);
|
||
|
val = channel << ((sample_id % 6) * 5);
|
||
|
sqr_reg = &STM32_ADC_SQR(reg_id);
|
||
|
|
||
|
*sqr_reg = (*sqr_reg & ~mask) | val;
|
||
|
}
|
||
|
|
||
|
static void adc_configure(int ain_id)
|
||
|
{
|
||
|
/* Set ADC channel */
|
||
|
adc_set_channel(0, ain_id);
|
||
|
|
||
|
/* Disable DMA */
|
||
|
STM32_ADC_CR2 &= ~BIT(8);
|
||
|
|
||
|
/* Disable scan mode */
|
||
|
STM32_ADC_CR1 &= ~BIT(8);
|
||
|
}
|
||
|
|
||
|
static void adc_configure_all(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
/* Set ADC channels */
|
||
|
STM32_ADC_SQR1 = (ADC_CH_COUNT - 1) << 20;
|
||
|
for (i = 0; i < ADC_CH_COUNT; ++i)
|
||
|
adc_set_channel(i, adc_channels[i].channel);
|
||
|
|
||
|
/* Enable DMA */
|
||
|
STM32_ADC_CR2 |= BIT(8);
|
||
|
|
||
|
/* Enable scan mode */
|
||
|
STM32_ADC_CR1 |= BIT(8);
|
||
|
}
|
||
|
|
||
|
static inline int adc_powered(void)
|
||
|
{
|
||
|
return STM32_ADC_SR & BIT(6); /* ADONS */
|
||
|
}
|
||
|
|
||
|
static void adc_enable_clock(void)
|
||
|
{
|
||
|
STM32_RCC_APB2ENR |= BIT(9);
|
||
|
/* ADCCLK = HSI / 2 = 8MHz*/
|
||
|
STM32_ADC_CCR |= BIT(16);
|
||
|
}
|
||
|
|
||
|
static void adc_init(void)
|
||
|
{
|
||
|
/*
|
||
|
* For STM32L, ADC clock source is HSI/2 = 8 MHz. HSI must be enabled
|
||
|
* for ADC.
|
||
|
*
|
||
|
* Note that we are not powering on ADC on EC initialization because
|
||
|
* STM32L ADC module requires HSI clock. Instead, ADC module is powered
|
||
|
* on/off in adc_prepare()/adc_release().
|
||
|
*/
|
||
|
|
||
|
/* Enable ADC clock. */
|
||
|
adc_enable_clock();
|
||
|
|
||
|
if (!adc_powered())
|
||
|
/* Power on ADC module */
|
||
|
STM32_ADC_CR2 |= BIT(0); /* ADON */
|
||
|
|
||
|
/* Set right alignment */
|
||
|
STM32_ADC_CR2 &= ~BIT(11);
|
||
|
|
||
|
/*
|
||
|
* Set sample time of all channels to 16 cycles.
|
||
|
* Conversion takes (12+16)/8M = 3.34 us.
|
||
|
*/
|
||
|
STM32_ADC_SMPR1 = 0x24924892;
|
||
|
STM32_ADC_SMPR2 = 0x24924892;
|
||
|
STM32_ADC_SMPR3 = 0x24924892;
|
||
|
}
|
||
|
|
||
|
static void adc_prepare(void)
|
||
|
{
|
||
|
if (!adc_powered()) {
|
||
|
clock_enable_module(MODULE_ADC, 1);
|
||
|
adc_init();
|
||
|
restore_clock = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void adc_release(void)
|
||
|
{
|
||
|
if (restore_clock) {
|
||
|
clock_enable_module(MODULE_ADC, 0);
|
||
|
restore_clock = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Power down the ADC. The ADC consumes a non-trivial amount of power,
|
||
|
* so it's wasteful to leave it on.
|
||
|
*/
|
||
|
if (adc_powered())
|
||
|
STM32_ADC_CR2 = 0;
|
||
|
}
|
||
|
|
||
|
static inline int adc_conversion_ended(void)
|
||
|
{
|
||
|
return STM32_ADC_SR & BIT(1);
|
||
|
}
|
||
|
|
||
|
int adc_read_channel(enum adc_channel ch)
|
||
|
{
|
||
|
const struct adc_t *adc = adc_channels + ch;
|
||
|
int value;
|
||
|
timestamp_t deadline;
|
||
|
|
||
|
mutex_lock(&adc_lock);
|
||
|
|
||
|
adc_prepare();
|
||
|
|
||
|
adc_configure(adc->channel);
|
||
|
|
||
|
/* Clear EOC bit */
|
||
|
STM32_ADC_SR &= ~BIT(1);
|
||
|
|
||
|
/* Start conversion */
|
||
|
STM32_ADC_CR2 |= BIT(30); /* SWSTART */
|
||
|
|
||
|
/* Wait for EOC bit set */
|
||
|
deadline.val = get_time().val + ADC_SINGLE_READ_TIMEOUT;
|
||
|
value = ADC_READ_ERROR;
|
||
|
do {
|
||
|
if (adc_conversion_ended()) {
|
||
|
value = STM32_ADC_DR & ADC_READ_MAX;
|
||
|
break;
|
||
|
}
|
||
|
} while (!timestamp_expired(deadline, NULL));
|
||
|
|
||
|
adc_release();
|
||
|
|
||
|
mutex_unlock(&adc_lock);
|
||
|
return (value == ADC_READ_ERROR) ? ADC_READ_ERROR :
|
||
|
value * adc->factor_mul / adc->factor_div + adc->shift;
|
||
|
}
|