Files
OpenCellular/chip/npcx/adc.c
Mulin Chao 0b6a095488 npcx: adc: Properly wait for conversion interrupt
Certain tasks (eg. chipset) may be woken directly by other tasks /
unrelated interrupts. Add an explicit wake event for ADC conversion done
so that we're not mistakenly woken.

BUG=chrome-os-partner:54971
BRANCH=None
TEST=Manual on kevin rev5, run "reboot ap-off" then "sysjump rw", verify
console isn't spammed with GPIO warning message due to ADC failure to
read board version.

Signed-off-by: Shawn Nematbakhsh <shawnn@chromium.org>

Change-Id: I5477e11c2b434e4b350d81393f4463eea1a91e7c
Reviewed-on: https://chromium-review.googlesource.com/366943
Commit-Ready: Shawn N <shawnn@chromium.org>
Tested-by: Shawn N <shawnn@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
2016-08-10 15:30:47 -07:00

201 lines
4.9 KiB
C

/* 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.
*/
/* NPCX-specific ADC module for Chrome EC */
#include "adc.h"
#include "adc_chip.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 "task.h"
#include "timer.h"
#include "util.h"
/* 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;
/**
* 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();
/* 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);
}
/**
* ADC read specific channel.
*
* @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;
static struct mutex adc_lock;
int value;
uint16_t chn_data;
mutex_lock(&adc_lock);
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;
}
mutex_unlock(&adc_lock);
return value;
}
/**
* ADC interrupt handler
*
* @param none
* @return none
* @notes Only handle SW-triggered conversion in npcx chip
*/
void adc_interrupt(void)
{
if (IS_BIT_SET(NPCX_ADCSTS, NPCX_ADCSTS_EOCEV)) {
/* Disable End-of-Conversion Interrupt */
CLEAR_BIT(NPCX_ADCCNF, NPCX_ADCCNF_INTECEN);
/* Stop conversion */
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, 0);
}
}
DECLARE_IRQ(NPCX_IRQ_ADC, adc_interrupt, 3);
/**
* 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);
/* Enable ADC */
SET_BIT(NPCX_ADCCNF, NPCX_ADCCNF_ADCEN);
/* 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);