|  | /* Copyright (c) 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 "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 */ | 
|  |  | 
|  | #define SMPR1_EXPAND(v) ((v) | ((v) << 3) | ((v) << 6) | ((v) << 9) | \ | 
|  | ((v) << 12) | ((v) << 15) | ((v) << 18) | \ | 
|  | ((v) << 21)) | 
|  | #define SMPR2_EXPAND(v) (SMPR1_EXPAND(v) | ((v) << 24) | ((v) << 27)) | 
|  |  | 
|  | /* Default ADC sample time = 13.5 cycles */ | 
|  | #ifndef CONFIG_ADC_SAMPLE_TIME | 
|  | #define CONFIG_ADC_SAMPLE_TIME 2 | 
|  | #endif | 
|  |  | 
|  | struct mutex adc_lock; | 
|  |  | 
|  | static int watchdog_ain_id; | 
|  |  | 
|  | static inline void adc_set_channel(int sample_id, int channel) | 
|  | { | 
|  | uint32_t mask, val; | 
|  | volatile uint32_t *sqr_reg; | 
|  |  | 
|  | if (sample_id < 6) { | 
|  | mask = 0x1f << (sample_id * 5); | 
|  | val = channel << (sample_id * 5); | 
|  | sqr_reg = &STM32_ADC_SQR3; | 
|  | } else if (sample_id < 12) { | 
|  | mask = 0x1f << ((sample_id - 6) * 5); | 
|  | val = channel << ((sample_id - 6) * 5); | 
|  | sqr_reg = &STM32_ADC_SQR2; | 
|  | } else { | 
|  | mask = 0x1f << ((sample_id - 12) * 5); | 
|  | val = channel << ((sample_id - 12) * 5); | 
|  | sqr_reg = &STM32_ADC_SQR1; | 
|  | } | 
|  |  | 
|  | *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 &= ~(1 << 8); | 
|  |  | 
|  | /* Disable scan mode */ | 
|  | STM32_ADC_CR1 &= ~(1 << 8); | 
|  | } | 
|  |  | 
|  | static void __attribute__((unused)) 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 |= (1 << 8); | 
|  |  | 
|  | /* Enable scan mode */ | 
|  | STM32_ADC_CR1 |= (1 << 8); | 
|  | } | 
|  |  | 
|  | static inline int adc_powered(void) | 
|  | { | 
|  | return STM32_ADC_CR2 & (1 << 0); | 
|  | } | 
|  |  | 
|  | static inline int adc_conversion_ended(void) | 
|  | { | 
|  | return STM32_ADC_SR & (1 << 1); | 
|  | } | 
|  |  | 
|  | static int adc_watchdog_enabled(void) | 
|  | { | 
|  | return STM32_ADC_CR1 & (1 << 23); | 
|  | } | 
|  |  | 
|  | static int adc_enable_watchdog_no_lock(void) | 
|  | { | 
|  | /* Fail if watchdog already enabled */ | 
|  | if (adc_watchdog_enabled()) | 
|  | return EC_ERROR_UNKNOWN; | 
|  |  | 
|  | /* Set channel */ | 
|  | STM32_ADC_SQR3 = watchdog_ain_id; | 
|  | STM32_ADC_SQR1 = 0; | 
|  | STM32_ADC_CR1 = (STM32_ADC_CR1 & ~0x1f) | watchdog_ain_id; | 
|  |  | 
|  | /* Clear interrupt bit */ | 
|  | STM32_ADC_SR &= ~0x1; | 
|  |  | 
|  | /* AWDSGL=1, SCAN=1, AWDIE=1, AWDEN=1 */ | 
|  | STM32_ADC_CR1 |= (1 << 9) | (1 << 8) | (1 << 6) | (1 << 23); | 
|  |  | 
|  | /* Disable DMA */ | 
|  | STM32_ADC_CR2 &= ~(1 << 8); | 
|  |  | 
|  | /* CONT=1 */ | 
|  | STM32_ADC_CR2 |= (1 << 1); | 
|  |  | 
|  | /* Start conversion */ | 
|  | STM32_ADC_CR2 |= (1 << 0); | 
|  |  | 
|  | return EC_SUCCESS; | 
|  | } | 
|  |  | 
|  | int adc_enable_watchdog(int ain_id, int high, int low) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | if (!adc_powered()) | 
|  | return EC_ERROR_UNKNOWN; | 
|  |  | 
|  | mutex_lock(&adc_lock); | 
|  |  | 
|  | watchdog_ain_id = ain_id; | 
|  |  | 
|  | /* Set thresholds */ | 
|  | STM32_ADC_HTR = high & 0xfff; | 
|  | STM32_ADC_LTR = low & 0xfff; | 
|  |  | 
|  | ret = adc_enable_watchdog_no_lock(); | 
|  | mutex_unlock(&adc_lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int adc_disable_watchdog_no_lock(void) | 
|  | { | 
|  | /* Fail if watchdog not running */ | 
|  | if (!adc_watchdog_enabled()) | 
|  | return EC_ERROR_UNKNOWN; | 
|  |  | 
|  | /* AWDEN=0, AWDIE=0 */ | 
|  | STM32_ADC_CR1 &= ~(1 << 23) & ~(1 << 6); | 
|  |  | 
|  | /* CONT=0 */ | 
|  | STM32_ADC_CR2 &= ~(1 << 1); | 
|  |  | 
|  | return EC_SUCCESS; | 
|  | } | 
|  |  | 
|  | int adc_disable_watchdog(void) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | if (!adc_powered()) | 
|  | return EC_ERROR_UNKNOWN; | 
|  |  | 
|  | mutex_lock(&adc_lock); | 
|  | ret = adc_disable_watchdog_no_lock(); | 
|  | mutex_unlock(&adc_lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int adc_read_channel(enum adc_channel ch) | 
|  | { | 
|  | const struct adc_t *adc = adc_channels + ch; | 
|  | int value; | 
|  | int restore_watchdog = 0; | 
|  | timestamp_t deadline; | 
|  |  | 
|  | if (!adc_powered()) | 
|  | return EC_ERROR_UNKNOWN; | 
|  |  | 
|  | mutex_lock(&adc_lock); | 
|  |  | 
|  | if (adc_watchdog_enabled()) { | 
|  | restore_watchdog = 1; | 
|  | adc_disable_watchdog_no_lock(); | 
|  | } | 
|  |  | 
|  | adc_configure(adc->channel); | 
|  |  | 
|  | /* Clear EOC bit */ | 
|  | STM32_ADC_SR &= ~(1 << 1); | 
|  |  | 
|  | /* Start conversion */ | 
|  | STM32_ADC_CR2 |= (1 << 0); /* ADON */ | 
|  |  | 
|  | /* 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)); | 
|  |  | 
|  | if (restore_watchdog) | 
|  | adc_enable_watchdog_no_lock(); | 
|  |  | 
|  | mutex_unlock(&adc_lock); | 
|  | return (value == ADC_READ_ERROR) ? ADC_READ_ERROR : | 
|  | value * adc->factor_mul / adc->factor_div + adc->shift; | 
|  | } | 
|  |  | 
|  | static void adc_init(void) | 
|  | { | 
|  | /* | 
|  | * Enable ADC clock. | 
|  | * APB2 clock is 16MHz. ADC clock prescaler is /2. | 
|  | * So the ADC clock is 8MHz. | 
|  | */ | 
|  | STM32_RCC_APB2ENR |= (1 << 9); | 
|  |  | 
|  | /* | 
|  | * ADC clock is divided with respect to AHB, so no delay needed | 
|  | * here. If ADC clock is the same as AHB, a dummy read on ADC | 
|  | * register is needed here. | 
|  | */ | 
|  |  | 
|  | if (!adc_powered()) { | 
|  | /* Power on ADC module */ | 
|  | STM32_ADC_CR2 |= (1 << 0);  /* ADON */ | 
|  |  | 
|  | /* Reset calibration */ | 
|  | STM32_ADC_CR2 |= (1 << 3);  /* RSTCAL */ | 
|  | while (STM32_ADC_CR2 & (1 << 3)) | 
|  | ; | 
|  |  | 
|  | /* A/D Calibrate */ | 
|  | STM32_ADC_CR2 |= (1 << 2);  /* CAL */ | 
|  | while (STM32_ADC_CR2 & (1 << 2)) | 
|  | ; | 
|  | } | 
|  |  | 
|  | /* Set right alignment */ | 
|  | STM32_ADC_CR2 &= ~(1 << 11); | 
|  |  | 
|  | /* Set sample time of all channels */ | 
|  | STM32_ADC_SMPR1 = SMPR1_EXPAND(CONFIG_ADC_SAMPLE_TIME); | 
|  | STM32_ADC_SMPR2 = SMPR2_EXPAND(CONFIG_ADC_SAMPLE_TIME); | 
|  | } | 
|  | DECLARE_HOOK(HOOK_INIT, adc_init, HOOK_PRIO_INIT_ADC); |