diff options
Diffstat (limited to 'chip/lm4/adc.c')
-rw-r--r-- | chip/lm4/adc.c | 273 |
1 files changed, 0 insertions, 273 deletions
diff --git a/chip/lm4/adc.c b/chip/lm4/adc.c deleted file mode 100644 index 13b5ebdebd..0000000000 --- a/chip/lm4/adc.c +++ /dev/null @@ -1,273 +0,0 @@ -/* 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. - */ - -/* LM4-specific ADC module for Chrome EC */ - -#include "adc.h" -#include "atomic.h" -#include "clock.h" -#include "console.h" -#include "common.h" -#include "gpio.h" -#include "hooks.h" -#include "registers.h" -#include "task.h" -#include "timer.h" -#include "util.h" - -/* Maximum time we allow for an ADC conversion */ -#define ADC_TIMEOUT_US SECOND - -static volatile task_id_t task_waiting_on_ss[LM4_ADC_SEQ_COUNT]; - -static void configure_gpio(void) -{ - int i, port, mask; - - /* Use analog function for AIN */ - for (i = 0; i < ADC_CH_COUNT; ++i) { - if (adc_channels[i].gpio_mask) { - mask = adc_channels[i].gpio_mask; - port = adc_channels[i].gpio_port; - LM4_GPIO_DEN(port) &= ~mask; - LM4_GPIO_AMSEL(port) |= mask; - } - } -} - -/** - * Flush an ADC sequencer and initiate a read. - * - * @param seq Sequencer to read - * @return Raw ADC value. - */ -static int flush_and_read(enum lm4_adc_sequencer seq) -{ - /* - * This is currently simple because we can dedicate a sequencer to each - * ADC channel. If we have enough channels that's no longer possible, - * this code will need to become more complex. For example, we could: - * - * 1) Read them all using a timer interrupt, and then return the most - * recent value? This is lowest-latency for the caller, but won't - * return accurate data if read frequently. - * - * 2) Reserve SS3 for reading a single value, and configure it on each - * read? Needs mutex if we could have multiple callers; doesn't matter - * if just used for debugging. - * - * 3) Both? - */ - volatile uint32_t scratch __attribute__((unused)); - int event; - - /* Empty the FIFO of any previous results */ - while (!(LM4_ADC_SSFSTAT(seq) & 0x100)) - scratch = LM4_ADC_SSFIFO(seq); - - /* - * This assumes we don't have multiple tasks accessing the same - * sequencer. Add mutex lock if needed. - */ - task_waiting_on_ss[seq] = task_get_current(); - - /* Clear the interrupt status */ - LM4_ADC_ADCISC |= 0x01 << seq; - - /* Enable interrupt */ - LM4_ADC_ADCIM |= 0x01 << seq; - - /* Initiate sample sequence */ - LM4_ADC_ADCPSSI |= 0x01 << seq; - - /* Wait for interrupt */ - event = task_wait_event_mask(TASK_EVENT_ADC_DONE, ADC_TIMEOUT_US); - - /* Disable interrupt */ - LM4_ADC_ADCIM &= ~(0x01 << seq); - - task_waiting_on_ss[seq] = TASK_ID_INVALID; - - if (!(event & TASK_EVENT_ADC_DONE)) - return ADC_READ_ERROR; - - /* Read the FIFO and convert to temperature */ - return LM4_ADC_SSFIFO(seq); -} - -/** - * Configure an ADC sequencer to be dedicated for an ADC input. - * - * @param seq Sequencer to configure - * @param ain_id ADC input to use - * @param ssctl Value for sampler sequencer control register - * - */ -static void adc_configure(const struct adc_t *adc) -{ - const enum lm4_adc_sequencer seq = adc->sequencer; - - /* Configure sample sequencer */ - LM4_ADC_ADCACTSS &= ~(0x01 << seq); - - /* Trigger sequencer by processor request */ - LM4_ADC_ADCEMUX = (LM4_ADC_ADCEMUX & ~(0xf << (seq * 4))) | 0x00; - - /* Sample internal temp sensor */ - if (adc->channel == LM4_AIN_NONE) { - LM4_ADC_SSMUX(seq) = 0x00; - LM4_ADC_SSEMUX(seq) = 0x00; - } else { - LM4_ADC_SSMUX(seq) = adc->channel & 0xf; - LM4_ADC_SSEMUX(seq) = adc->channel >> 4; - } - LM4_ADC_SSCTL(seq) = adc->flag; - - /* Enable sample sequencer */ - LM4_ADC_ADCACTSS |= 0x01 << seq; -} - -int adc_read_channel(enum adc_channel ch) -{ - const struct adc_t *adc = adc_channels + ch; - static uint32_t ch_busy_mask; - static struct mutex adc_clock; - int rv; - - /* - * TODO(crbug.com/314121): Generalize ADC reads such that any task can - * trigger a read of any channel. - */ - - /* - * Enable ADC clock and set a bit in ch_busy_mask to signify that this - * channel is busy. Note, this function may be called from multiple - * tasks, but each channel may be read by only one task. If assert - * fails, then it means multiple tasks are trying to read same channel. - */ - mutex_lock(&adc_clock); - ASSERT(!(ch_busy_mask & (1UL << ch))); - clock_enable_peripheral(CGC_OFFSET_ADC, 0x1, - CGC_MODE_RUN | CGC_MODE_SLEEP); - ch_busy_mask |= (1UL << ch); - mutex_unlock(&adc_clock); - - rv = flush_and_read(adc->sequencer); - - /* - * If no ADC channels are busy, then disable ADC clock to conserve - * power. - */ - mutex_lock(&adc_clock); - ch_busy_mask &= ~(1UL << ch); - if (!ch_busy_mask) - clock_disable_peripheral(CGC_OFFSET_ADC, 0x1, - CGC_MODE_RUN | CGC_MODE_SLEEP); - mutex_unlock(&adc_clock); - - if (rv == ADC_READ_ERROR) - return ADC_READ_ERROR; - - return rv * adc->factor_mul / adc->factor_div + adc->shift; -} - -/*****************************************************************************/ -/* Interrupt handlers */ - -/** - * Handle an interrupt on the specified sample sequencer. - */ -static void handle_interrupt(int ss) -{ - int id = task_waiting_on_ss[ss]; - - /* Clear the interrupt status */ - LM4_ADC_ADCISC = (0x1 << ss); - - /* Wake up the task which was waiting on the interrupt, if any */ - if (id != TASK_ID_INVALID) - task_set_event(id, TASK_EVENT_ADC_DONE); -} - -void ss0_interrupt(void) { handle_interrupt(0); } -void ss1_interrupt(void) { handle_interrupt(1); } -void ss2_interrupt(void) { handle_interrupt(2); } -void ss3_interrupt(void) { handle_interrupt(3); } - -DECLARE_IRQ(LM4_IRQ_ADC0_SS0, ss0_interrupt, 2); -DECLARE_IRQ(LM4_IRQ_ADC0_SS1, ss1_interrupt, 2); -DECLARE_IRQ(LM4_IRQ_ADC0_SS2, ss2_interrupt, 2); -DECLARE_IRQ(LM4_IRQ_ADC0_SS3, ss3_interrupt, 2); - -/*****************************************************************************/ -/* Console commands */ - -#ifdef CONFIG_CMD_ECTEMP -static int command_ectemp(int argc, char **argv) -{ - int t = adc_read_channel(ADC_CH_EC_TEMP); - ccprintf("EC temperature is %d K = %d C\n", t, K_TO_C(t)); - return EC_SUCCESS; -} -DECLARE_CONSOLE_COMMAND(ectemp, command_ectemp, - NULL, - "Print EC temperature"); -#endif - -/*****************************************************************************/ -/* Initialization */ - -static void adc_init(void) -{ - int i; - - /* Configure GPIOs */ - configure_gpio(); - - /* - * Temporarily enable the PLL when turning on the clock to the ADC - * module, to work around chip errata (10.4). No need to notify - * other modules; the PLL isn't enabled long enough to matter. - */ - clock_enable_pll(1, 0); - - /* Enable ADC0 module in run and sleep modes. */ - clock_enable_peripheral(CGC_OFFSET_ADC, 0x1, - CGC_MODE_RUN | CGC_MODE_SLEEP); - - /* - * Use external voltage references (VREFA+, VREFA-) instead of - * VDDA and GNDA. - */ - LM4_ADC_ADCCTL = 0x01; - - /* Use internal oscillator */ - LM4_ADC_ADCCC = 0x1; - - /* Disable the PLL now that the ADC is using the internal oscillator */ - clock_enable_pll(0, 0); - - /* No tasks waiting yet */ - for (i = 0; i < LM4_ADC_SEQ_COUNT; i++) - task_waiting_on_ss[i] = TASK_ID_INVALID; - - /* Enable IRQs */ - task_enable_irq(LM4_IRQ_ADC0_SS0); - task_enable_irq(LM4_IRQ_ADC0_SS1); - task_enable_irq(LM4_IRQ_ADC0_SS2); - task_enable_irq(LM4_IRQ_ADC0_SS3); - - /* 2**6 = 64x oversampling */ - LM4_ADC_ADCSAC = 6; - - /* Initialize ADC sequencer */ - for (i = 0; i < ADC_CH_COUNT; ++i) - adc_configure(adc_channels + i); - - /* Disable ADC0 module until it is needed to conserve power. */ - clock_disable_peripheral(CGC_OFFSET_ADC, 0x1, - CGC_MODE_RUN | CGC_MODE_SLEEP); -} -DECLARE_HOOK(HOOK_INIT, adc_init, HOOK_PRIO_INIT_ADC); |