/* 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. */ /* NPCX-specific ADC module for Chrome EC */ #include "adc.h" #include "atomic.h" #include "clock.h" #include "clock_chip.h" #include "console.h" #include "common.h" #include "gpio.h" #include "hooks.h" #include "registers.h" #include "system.h" #include "task.h" #include "timer.h" #include "util.h" #define CPRINTS(format, args...) cprints(CC_SYSTEM, format, ## args) #define CPRINTF(format, args...) cprintf(CC_SYSTEM, format, ## args) /* Maximum time we allow for an ADC conversion */ #define ADC_TIMEOUT_US SECOND #define ADC_CLK 2000000 #define ADC_REGULAR_DLY 0x11 #define ADC_REGULAR_ADCCNF2 0x8B07 #define ADC_REGULAR_GENDLY 0x0100 #define ADC_REGULAR_MEAST 0x0001 /* ADC conversion mode */ enum npcx_adc_conversion_mode { ADC_CHN_CONVERSION_MODE = 0, ADC_SCAN_CONVERSION_MODE = 1 }; /* Global variables */ static volatile task_id_t task_waiting; struct mutex adc_lock; /** * Preset ADC operation clock. * * @param none * @return none * @notes changed when initial or HOOK_FREQ_CHANGE command */ void adc_freq_changed(void) { uint8_t prescaler_divider = 0; /* Set clock prescaler divider to ADC module*/ prescaler_divider = (uint8_t)(clock_get_apb1_freq() / ADC_CLK); if (prescaler_divider >= 1) prescaler_divider = prescaler_divider - 1; if (prescaler_divider > 0x3F) prescaler_divider = 0x3F; /* Set Core Clock Division Factor in order to obtain the ADC clock */ SET_FIELD(NPCX_ATCTL, NPCX_ATCTL_SCLKDIV_FIELD, prescaler_divider); } DECLARE_HOOK(HOOK_FREQ_CHANGE, adc_freq_changed, HOOK_PRIO_DEFAULT); /** * Flush an ADC sequencer and initiate a read. * * @param input_ch operation channel * @param timeout preset timeout * @return TRUE/FALSE success/fail * @notes set SW-triggered interrupt conversion and one-shot mode in npcx chip */ static int start_single_and_wait(enum npcx_adc_input_channel input_ch , int timeout) { int event; task_waiting = task_get_current(); /* Stop ADC conversion first */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_STOP); /* Set ADC conversion code to SW conversion mode */ SET_FIELD(NPCX_ADCCNF, NPCX_ADCCNF_ADCMD_FIELD, ADC_CHN_CONVERSION_MODE); /* Set conversion type to one-shot type */ CLEAR_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCRPTC); /* Update number of channel to be converted */ SET_FIELD(NPCX_ASCADD, NPCX_ASCADD_SADDR_FIELD, input_ch); /* Clear End-of-Conversion Event status */ SET_BIT(NPCX_ADCSTS, NPCX_ADCSTS_EOCEV); /* Enable ADC End-of-Conversion Interrupt if applicable */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_INTECEN); /* Start conversion */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_START); /* Wait for interrupt */ event = task_wait_event_mask(TASK_EVENT_ADC_DONE, timeout); task_waiting = TASK_ID_INVALID; return (event == TASK_EVENT_ADC_DONE); } static uint16_t repetitive_enabled; void npcx_set_adc_repetitive(enum npcx_adc_input_channel input_ch, int enable) { mutex_lock(&adc_lock); /* Stop ADC conversion */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_STOP); if (enable) { /* Forbid EC enter deep sleep during conversion. */ disable_sleep(SLEEP_MASK_ADC); /* Turn on ADC */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCEN); /* Set ADC conversion code to SW conversion mode */ SET_FIELD(NPCX_ADCCNF, NPCX_ADCCNF_ADCMD_FIELD, ADC_SCAN_CONVERSION_MODE); /* Update number of channel to be converted */ SET_BIT(NPCX_ADCCS, input_ch); /* Set conversion type to repetitive (runs continuously) */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCRPTC); repetitive_enabled |= BIT(input_ch); /* Start conversion */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_START); } else { CLEAR_BIT(NPCX_ADCCS, input_ch); repetitive_enabled &= ~BIT(input_ch); if (!repetitive_enabled) { /* Turn off ADC */ CLEAR_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCEN); /* Set ADC to one-shot mode */ CLEAR_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCRPTC); /* Allow ec enter deep sleep */ enable_sleep(SLEEP_MASK_ADC); } else { /* Start conversion again */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_START); } } mutex_unlock(&adc_lock); } /** * Return the ADC value from CHNDAT register directly. * * @param input_ch channel number * @return ADC data */ int adc_read_data(enum npcx_adc_input_channel input_ch) { const struct adc_t *adc = adc_channels + input_ch; int value; uint16_t chn_data; chn_data = NPCX_CHNDAT(adc->input_ch); value = GET_FIELD(chn_data, NPCX_CHNDAT_CHDAT_FIELD) * adc->factor_mul / adc->factor_div + adc->shift; return value; } /** * Start a single conversion and return the result * * @param ch operation channel * @return ADC converted voltage or error message */ int adc_read_channel(enum adc_channel ch) { const struct adc_t *adc = adc_channels + ch; int value; uint16_t chn_data; mutex_lock(&adc_lock); /* Forbid ec enter deep sleep during ADC conversion is proceeding. */ disable_sleep(SLEEP_MASK_ADC); /* Turn on ADC */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCEN); if (start_single_and_wait(adc->input_ch, ADC_TIMEOUT_US)) { chn_data = NPCX_CHNDAT(adc->input_ch); if ((adc->input_ch == GET_FIELD(NPCX_ASCADD, NPCX_ASCADD_SADDR_FIELD)) && (IS_BIT_SET(chn_data, NPCX_CHNDAT_NEW))) { value = GET_FIELD(chn_data, NPCX_CHNDAT_CHDAT_FIELD) * adc->factor_mul / adc->factor_div + adc->shift; } else { value = ADC_READ_ERROR; } } else { value = ADC_READ_ERROR; } if (!repetitive_enabled) { /* Turn off ADC */ CLEAR_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCEN); /* Allow ec enter deep sleep */ enable_sleep(SLEEP_MASK_ADC); } else { /* Set ADC conversion code to SW conversion mode */ SET_FIELD(NPCX_ADCCNF, NPCX_ADCCNF_ADCMD_FIELD, ADC_SCAN_CONVERSION_MODE); /* Set conversion type to repetitive (runs continuously) */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCRPTC); /* Start conversion */ SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_START); } mutex_unlock(&adc_lock); return value; } /* Board should register these callbacks with npcx_adc_cfg_thresh_int(). */ static void (*adc_thresh_irqs[NPCX_ADC_THRESH_CNT])(void); void npcx_adc_thresh_int_enable(int threshold_idx, int enable) { uint16_t thrcts; enable = !!enable; if ((threshold_idx < 1) || (threshold_idx > NPCX_ADC_THRESH_CNT)) { CPRINTS("Invalid ADC thresh index! (%d)", threshold_idx); return; } threshold_idx--; /* convert to 0-based */ /* avoid clearing other threshold status */ thrcts = NPCX_THRCTS & ~GENMASK(NPCX_ADC_THRESH_CNT - 1, 0); if (enable) { /* clear threshold status */ SET_BIT(thrcts, threshold_idx); /* set enable threshold status */ SET_BIT(thrcts, NPCX_THRCTS_THR1_IEN + threshold_idx); } else { CLEAR_BIT(thrcts, NPCX_THRCTS_THR1_IEN + threshold_idx); } NPCX_THRCTS = thrcts; } void npcx_adc_register_thresh_irq(int threshold_idx, const struct npcx_adc_thresh_t *thresh_cfg) { int npcx_adc_ch; int raw_val; int mul; int div; int shift; if ((threshold_idx < 1) || (threshold_idx > NPCX_ADC_THRESH_CNT)) { CPRINTS("Invalid ADC thresh index! (%d)", threshold_idx); return; } npcx_adc_ch = adc_channels[thresh_cfg->adc_ch].input_ch; if (!thresh_cfg->adc_thresh_cb) { CPRINTS("No callback for ADC Threshold %d!", threshold_idx); return; } /* Fill in the table */ adc_thresh_irqs[threshold_idx-1] = thresh_cfg->adc_thresh_cb; /* Select the channel */ SET_FIELD(NPCX_THRCTL(threshold_idx), NPCX_THRCTL_CHNSEL, npcx_adc_ch); if (thresh_cfg->lower_or_higher) SET_BIT(NPCX_THRCTL(threshold_idx), NPCX_THRCTL_L_H); else CLEAR_BIT(NPCX_THRCTL(threshold_idx), NPCX_THRCTL_L_H); /* Set the threshold value. */ mul = adc_channels[thresh_cfg->adc_ch].factor_mul; div = adc_channels[thresh_cfg->adc_ch].factor_div; shift = adc_channels[thresh_cfg->adc_ch].shift; raw_val = (thresh_cfg->thresh_assert - shift) * div / mul; CPRINTS("ADC THR%d: Setting THRVAL = %d, L_H: %d", threshold_idx, raw_val, thresh_cfg->lower_or_higher); SET_FIELD(NPCX_THRCTL(threshold_idx), NPCX_THRCTL_THRVAL, raw_val); #if NPCX_FAMILY_VERSION <= NPCX_FAMILY_NPCX7 /* Disable deassertion threshold function */ CLEAR_BIT(NPCX_THR_DCTL(threshold_idx), NPCX_THR_DCTL_THRD_EN); #endif /* Enable threshold detection */ SET_BIT(NPCX_THRCTL(threshold_idx), NPCX_THRCTL_THEN); } /** * ADC interrupt handler * * @param none * @return none * @notes Only handle SW-triggered conversion in npcx chip */ void adc_interrupt(void) { int i; uint16_t thrcts; if (IS_BIT_SET(NPCX_ADCCNF, NPCX_ADCCNF_INTECEN) && IS_BIT_SET(NPCX_ADCSTS, NPCX_ADCSTS_EOCEV)) { /* Disable End-of-Conversion Interrupt */ CLEAR_BIT(NPCX_ADCCNF, NPCX_ADCCNF_INTECEN); /* Stop conversion for single-shot mode */ if (!repetitive_enabled) SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_STOP); /* Clear End-of-Conversion Event status */ SET_BIT(NPCX_ADCSTS, NPCX_ADCSTS_EOCEV); /* Wake up the task which was waiting for the interrupt */ if (task_waiting != TASK_ID_INVALID) task_set_event(task_waiting, TASK_EVENT_ADC_DONE); } for (i = NPCX_THRCTS_THR1_STS; i < NPCX_ADC_THRESH_CNT; i++) { if (IS_BIT_SET(NPCX_THRCTS, NPCX_THRCTS_THR1_IEN + i) && IS_BIT_SET(NPCX_THRCTS, i)) { /* avoid clearing other threshold status */ thrcts = NPCX_THRCTS & ~GENMASK(NPCX_ADC_THRESH_CNT - 1, 0); /* Clear threshold status */ SET_BIT(thrcts, i); NPCX_THRCTS = thrcts; if (adc_thresh_irqs[i]) adc_thresh_irqs[i](); } } } DECLARE_IRQ(NPCX_IRQ_ADC, adc_interrupt, 4); /** * ADC initial. * * @param none * @return none */ static void adc_init(void) { /* Configure pins from GPIOs to ADCs */ gpio_config_module(MODULE_ADC, 1); /* Enable ADC clock (bit4 mask = 0x10) */ clock_enable_peripheral(CGC_OFFSET_ADC, CGC_ADC_MASK, CGC_MODE_RUN | CGC_MODE_SLEEP); /* Set Core Clock Division Factor in order to obtain the ADC clock */ adc_freq_changed(); /* Set regular speed */ SET_FIELD(NPCX_ATCTL, NPCX_ATCTL_DLY_FIELD, (ADC_REGULAR_DLY - 1)); /* Set the other ADC settings */ NPCX_ADCCNF2 = ADC_REGULAR_ADCCNF2; NPCX_GENDLY = ADC_REGULAR_GENDLY; NPCX_MEAST = ADC_REGULAR_MEAST; task_waiting = TASK_ID_INVALID; /* Enable IRQs */ task_enable_irq(NPCX_IRQ_ADC); } DECLARE_HOOK(HOOK_INIT, adc_init, HOOK_PRIO_INIT_ADC);