460 lines
15 KiB
C
460 lines
15 KiB
C
/*
|
|
* Copyright (c) 2016, Intel Corporation
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the Intel Corporation nor the names of its
|
|
* contributors may be used to endorse or promote products derived from this
|
|
* software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL CORPORATION OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "qm_adc.h"
|
|
#include "clk.h"
|
|
#include <string.h>
|
|
|
|
#if (QUARK_D2000)
|
|
|
|
/* FIFO_INTERRUPT_THRESHOLD is used by qm_adc_irq_convert to set the threshold
|
|
* at which the FIFO will trigger an interrupt. */
|
|
#define FIFO_INTERRUPT_THRESHOLD (16)
|
|
|
|
#define QM_ADC_CHAN_SEQ_MAX (32)
|
|
|
|
/* ADC commands. */
|
|
#define QM_ADC_CMD_START_SINGLE (0)
|
|
#define QM_ADC_CMD_START_CONT (1)
|
|
#define QM_ADC_CMD_RESET_CAL (2)
|
|
#define QM_ADC_CMD_START_CAL (3)
|
|
#define QM_ADC_CMD_LOAD_CAL (4)
|
|
#define QM_ADC_CMD_STOP_CONT (5)
|
|
|
|
static uint8_t sample_window[QM_ADC_NUM];
|
|
static qm_adc_resolution_t resolution[QM_ADC_NUM];
|
|
|
|
static qm_adc_xfer_t *irq_xfer[QM_ADC_NUM];
|
|
static uint32_t count[QM_ADC_NUM];
|
|
static bool dummy_conversion = false;
|
|
|
|
/* Callbacks for mode change and calibration. */
|
|
static void (*mode_callback[QM_ADC_NUM])(void *data, int error,
|
|
qm_adc_status_t status,
|
|
qm_adc_cb_source_t source);
|
|
static void (*cal_callback[QM_ADC_NUM])(void *data, int error,
|
|
qm_adc_status_t status,
|
|
qm_adc_cb_source_t source);
|
|
static void *mode_callback_data[QM_ADC_NUM];
|
|
static void *cal_callback_data[QM_ADC_NUM];
|
|
|
|
/* ISR handler for command/calibration complete. */
|
|
static void qm_adc_isr_handler(const qm_adc_t adc)
|
|
{
|
|
uint32_t int_status = 0;
|
|
uint32_t i, samples_to_read;
|
|
|
|
int_status = QM_ADC[adc].adc_intr_status;
|
|
|
|
/* FIFO overrun interrupt. */
|
|
if (int_status & QM_ADC_INTR_STATUS_FO) {
|
|
/* Stop the transfer. */
|
|
QM_ADC[adc].adc_cmd = QM_ADC_CMD_STOP_CONT;
|
|
/* Disable all interrupts. */
|
|
QM_ADC[adc].adc_intr_enable = 0;
|
|
/* Call the user callback. */
|
|
if (irq_xfer[adc]->callback) {
|
|
irq_xfer[adc]->callback(irq_xfer[adc]->callback_data,
|
|
-EIO, QM_ADC_OVERFLOW,
|
|
QM_ADC_TRANSFER);
|
|
}
|
|
}
|
|
|
|
/* Continuous mode command complete interrupt. */
|
|
if (int_status & QM_ADC_INTR_STATUS_CONT_CC) {
|
|
/* Clear the interrupt. */
|
|
QM_ADC[adc].adc_intr_status &= QM_ADC_INTR_STATUS_CONT_CC;
|
|
|
|
/* Calculate the number of samples to read. */
|
|
samples_to_read = QM_ADC[adc].adc_fifo_count;
|
|
if (samples_to_read >
|
|
(irq_xfer[adc]->samples_len - count[adc])) {
|
|
samples_to_read =
|
|
irq_xfer[adc]->samples_len - count[adc];
|
|
}
|
|
|
|
/* Copy data out of FIFO. The sample must be shifted right by
|
|
* 2, 4 or 6 bits for 10, 8 and 6 bit resolution respectively
|
|
* to get the correct value. */
|
|
for (i = 0; i < samples_to_read; i++) {
|
|
irq_xfer[adc]->samples[count[adc]] =
|
|
(QM_ADC[adc].adc_sample >>
|
|
(2 * (3 - resolution[adc])));
|
|
count[adc]++;
|
|
}
|
|
|
|
/* Check if we have the requested number of samples, stop the
|
|
* conversion and call the user callback function. */
|
|
if (count[adc] == irq_xfer[adc]->samples_len) {
|
|
/* Stop the transfer. */
|
|
QM_ADC[adc].adc_cmd = QM_ADC_CMD_STOP_CONT;
|
|
/* Disable all interrupts. */
|
|
QM_ADC[adc].adc_intr_enable = 0;
|
|
/* Call the user callback. */
|
|
if (irq_xfer[adc]->callback) {
|
|
irq_xfer[adc]->callback(
|
|
irq_xfer[adc]->callback_data, 0,
|
|
QM_ADC_COMPLETE, QM_ADC_TRANSFER);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The Command Complete interrupt is currently used to notify of the
|
|
* completion of a calibration command or a dummy conversion. */
|
|
if ((int_status & QM_ADC_INTR_STATUS_CC) && (!dummy_conversion)) {
|
|
/* Disable and clear the Command Complete interrupt */
|
|
QM_ADC[adc].adc_intr_enable &= ~QM_ADC_INTR_ENABLE_CC;
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
|
|
/* Call the user callback if it is set. */
|
|
if (cal_callback[adc]) {
|
|
cal_callback[adc](irq_xfer[adc]->callback_data, 0,
|
|
QM_ADC_IDLE, QM_ADC_CAL_COMPLETE);
|
|
}
|
|
}
|
|
|
|
/* This dummy conversion is needed when switching to normal mode or
|
|
* normal mode with calibration. */
|
|
if ((int_status & QM_ADC_INTR_STATUS_CC) && (dummy_conversion)) {
|
|
/* Flush the FIFO to get rid of the dummy values. */
|
|
QM_ADC[adc].adc_sample = QM_ADC_FIFO_CLEAR;
|
|
/* Disable and clear the Command Complete interrupt. */
|
|
QM_ADC[adc].adc_intr_enable &= ~QM_ADC_INTR_ENABLE_CC;
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
|
|
dummy_conversion = false;
|
|
|
|
/* Call the user callback if it is set. */
|
|
if (mode_callback[adc]) {
|
|
mode_callback[adc](irq_xfer[adc]->callback_data, 0,
|
|
QM_ADC_IDLE, QM_ADC_MODE_CHANGED);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ISR handler for mode change. */
|
|
static void qm_adc_pwr_0_isr_handler(const qm_adc_t adc)
|
|
{
|
|
/* Clear the interrupt. Note that this operates differently to the
|
|
* QM_ADC_INTR_STATUS regiseter because you have to write to the
|
|
* QM_ADC_OP_MODE register, Interrupt Enable bit to clear. */
|
|
QM_ADC[adc].adc_op_mode &= ~QM_ADC_OP_MODE_IE;
|
|
|
|
/* Perform a dummy conversion if we are transitioning to Normal Mode or
|
|
* Normal Mode With Calibration */
|
|
if ((QM_ADC[adc].adc_op_mode & QM_ADC_OP_MODE_OM_MASK) >=
|
|
QM_ADC_MODE_NORM_CAL) {
|
|
|
|
/* Set the first sequence register back to its default (ch 0) */
|
|
QM_ADC[adc].adc_seq0 = QM_ADC_CAL_SEQ_TABLE_DEFAULT;
|
|
/* Clear the command complete interrupt status field */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
|
|
dummy_conversion = true;
|
|
|
|
/* Run a dummy conversion */
|
|
QM_ADC[adc].adc_cmd = (QM_ADC_CMD_IE | QM_ADC_CMD_START_SINGLE);
|
|
} else {
|
|
/* Call the user callback function */
|
|
if (mode_callback[adc]) {
|
|
mode_callback[adc](irq_xfer[adc]->callback_data, 0,
|
|
QM_ADC_IDLE, QM_ADC_MODE_CHANGED);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ISR for ADC 0 Command/Calibration Complete. */
|
|
QM_ISR_DECLARE(qm_adc_0_cal_isr)
|
|
{
|
|
qm_adc_isr_handler(QM_ADC_0);
|
|
|
|
QM_ISR_EOI(QM_IRQ_ADC_0_CAL_INT_VECTOR);
|
|
}
|
|
|
|
/* ISR for ADC 0 Mode Change. */
|
|
QM_ISR_DECLARE(qm_adc_0_pwr_isr)
|
|
{
|
|
qm_adc_pwr_0_isr_handler(QM_ADC_0);
|
|
|
|
QM_ISR_EOI(QM_IRQ_ADC_0_PWR_0_VECTOR);
|
|
}
|
|
|
|
static void setup_seq_table(const qm_adc_t adc, qm_adc_xfer_t *xfer)
|
|
{
|
|
uint32_t i, offset = 0;
|
|
volatile uint32_t *reg_pointer = NULL;
|
|
|
|
/* Loop over all of the channels to be added. */
|
|
for (i = 0; i < xfer->ch_len; i++) {
|
|
/* Get a pointer to the correct address. */
|
|
reg_pointer = &QM_ADC[adc].adc_seq0 + (i / 4);
|
|
/* Get the offset within the register */
|
|
offset = ((i % 4) * 8);
|
|
/* Clear the Last bit from all entries we will use. */
|
|
*reg_pointer &= ~(1 << (offset + 7));
|
|
/* Place the channel numnber into the sequence table. */
|
|
*reg_pointer |= (xfer->ch[i] << offset);
|
|
}
|
|
|
|
if (reg_pointer) {
|
|
/* Set the correct Last bit. */
|
|
*reg_pointer |= (1 << (offset + 7));
|
|
}
|
|
}
|
|
|
|
int qm_adc_calibrate(const qm_adc_t adc)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
|
|
/* Clear the command complete interrupt status field. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
/* Start the calibration and wait for it to complete. */
|
|
QM_ADC[adc].adc_cmd = (QM_ADC_CMD_IE | QM_ADC_CMD_START_CAL);
|
|
while (!(QM_ADC[adc].adc_intr_status & QM_ADC_INTR_STATUS_CC))
|
|
;
|
|
/* Clear the command complete interrupt status field again. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_irq_calibrate(const qm_adc_t adc,
|
|
void (*callback)(void *data, int error,
|
|
qm_adc_status_t status,
|
|
qm_adc_cb_source_t source),
|
|
void *callback_data)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
|
|
/* Set the callback. */
|
|
cal_callback[adc] = callback;
|
|
cal_callback_data[adc] = callback_data;
|
|
|
|
/* Clear and enable the command complete interrupt. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
QM_ADC[adc].adc_intr_enable |= QM_ADC_INTR_ENABLE_CC;
|
|
|
|
/* Start the calibration */
|
|
QM_ADC[adc].adc_cmd = (QM_ADC_CMD_IE | QM_ADC_CMD_START_CAL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_set_calibration(const qm_adc_t adc, const qm_adc_calibration_t cal)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
QM_CHECK(cal < 0x3F, -EINVAL);
|
|
|
|
/* Clear the command complete interrupt status field. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
|
|
/* Set the calibration data and wait for it to complete. */
|
|
QM_ADC[adc].adc_cmd = ((cal << QM_ADC_CMD_CAL_DATA_OFFSET) |
|
|
QM_ADC_CMD_IE | QM_ADC_CMD_LOAD_CAL);
|
|
while (!(QM_ADC[adc].adc_intr_status & QM_ADC_INTR_STATUS_CC))
|
|
;
|
|
/* Clear the command complete interrupt status field again. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_get_calibration(const qm_adc_t adc, qm_adc_calibration_t *const cal)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
QM_CHECK(NULL != cal, -EINVAL);
|
|
|
|
*cal = QM_ADC[adc].adc_calibration;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_set_mode(const qm_adc_t adc, const qm_adc_mode_t mode)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
QM_CHECK(mode <= QM_ADC_MODE_NORM_NO_CAL, -EINVAL);
|
|
|
|
/* Issue mode change command and wait for it to complete. */
|
|
QM_ADC[adc].adc_op_mode = mode;
|
|
while ((QM_ADC[adc].adc_op_mode & QM_ADC_OP_MODE_OM_MASK) != mode)
|
|
;
|
|
|
|
/* Perform a dummy conversion if we are transitioning to Normal Mode. */
|
|
if ((mode >= QM_ADC_MODE_NORM_CAL)) {
|
|
/* Set the first sequence register back to its default. */
|
|
QM_ADC[adc].adc_seq0 = QM_ADC_CAL_SEQ_TABLE_DEFAULT;
|
|
|
|
/* Clear the command complete interrupt status field. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
/* Run a dummy convert and wait for it to complete. */
|
|
QM_ADC[adc].adc_cmd = (QM_ADC_CMD_IE | QM_ADC_CMD_START_SINGLE);
|
|
while (!(QM_ADC[adc].adc_intr_status & QM_ADC_INTR_STATUS_CC))
|
|
;
|
|
|
|
/* Flush the FIFO to get rid of the dummy values. */
|
|
QM_ADC[adc].adc_sample = QM_ADC_FIFO_CLEAR;
|
|
/* Clear the command complete interrupt status field. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_irq_set_mode(const qm_adc_t adc, const qm_adc_mode_t mode,
|
|
void (*callback)(void *data, int error,
|
|
qm_adc_status_t status,
|
|
qm_adc_cb_source_t source),
|
|
void *callback_data)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
QM_CHECK(mode <= QM_ADC_MODE_NORM_NO_CAL, -EINVAL);
|
|
|
|
/* Set the callback. */
|
|
mode_callback[adc] = callback;
|
|
mode_callback_data[adc] = callback_data;
|
|
|
|
/* When transitioning to Normal Mode or Normal Mode With Calibration,
|
|
* enable command complete interrupt to perform a dummy conversion. */
|
|
if ((mode >= QM_ADC_MODE_NORM_CAL)) {
|
|
QM_ADC[adc].adc_intr_enable |= QM_ADC_INTR_ENABLE_CC;
|
|
}
|
|
|
|
/* Issue mode change command. Completion if this command is notified via
|
|
* the ADC Power interrupt source, which is serviced separately to the
|
|
* Command/Calibration Complete interrupt. */
|
|
QM_ADC[adc].adc_op_mode = (QM_ADC_OP_MODE_IE | mode);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_set_config(const qm_adc_t adc, const qm_adc_config_t *const cfg)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
QM_CHECK(NULL != cfg, -EINVAL);
|
|
QM_CHECK(cfg->resolution <= QM_ADC_RES_12_BITS, -EINVAL);
|
|
/* Convert cfg->resolution to actual resolution (2x+6) and add 2 to get
|
|
* minimum value for window size. */
|
|
QM_CHECK(cfg->window >= ((cfg->resolution * 2) + 8), -EINVAL);
|
|
|
|
/* Set the sample window and resolution. */
|
|
sample_window[adc] = cfg->window;
|
|
resolution[adc] = cfg->resolution;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_convert(const qm_adc_t adc, qm_adc_xfer_t *xfer,
|
|
qm_adc_status_t *const status)
|
|
{
|
|
uint32_t i;
|
|
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
QM_CHECK(NULL != xfer, -EINVAL);
|
|
QM_CHECK(NULL != xfer->ch, -EINVAL);
|
|
QM_CHECK(NULL != xfer->samples, -EINVAL);
|
|
QM_CHECK(xfer->ch_len > 0, -EINVAL);
|
|
QM_CHECK(xfer->ch_len <= QM_ADC_CHAN_SEQ_MAX, -EINVAL);
|
|
QM_CHECK(xfer->samples_len > 0, -EINVAL);
|
|
QM_CHECK(xfer->samples_len <= QM_ADC_FIFO_LEN, -EINVAL);
|
|
|
|
/* Flush the FIFO. */
|
|
QM_ADC[adc].adc_sample = QM_ADC_FIFO_CLEAR;
|
|
|
|
/* Populate the sample sequence table. */
|
|
setup_seq_table(adc, xfer);
|
|
|
|
/* Issue cmd: window & resolution, number of samples, command. */
|
|
QM_ADC[adc].adc_cmd =
|
|
(sample_window[adc] << QM_ADC_CMD_SW_OFFSET |
|
|
resolution[adc] << QM_ADC_CMD_RESOLUTION_OFFSET |
|
|
((xfer->samples_len - 1) << QM_ADC_CMD_NS_OFFSET) |
|
|
QM_ADC_CMD_START_SINGLE);
|
|
|
|
/* Wait for fifo count to reach number of samples. */
|
|
while (QM_ADC[adc].adc_fifo_count != xfer->samples_len)
|
|
;
|
|
|
|
if (status) {
|
|
*status = QM_ADC_COMPLETE;
|
|
}
|
|
|
|
/* Read the value into the data structure. The sample must be shifted
|
|
* right by 2, 4 or 6 bits for 10, 8 and 6 bit resolution respectively
|
|
* to get the correct value. */
|
|
for (i = 0; i < xfer->samples_len; i++) {
|
|
xfer->samples[i] =
|
|
(QM_ADC[adc].adc_sample >> (2 * (3 - resolution[adc])));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qm_adc_irq_convert(const qm_adc_t adc, qm_adc_xfer_t *xfer)
|
|
{
|
|
QM_CHECK(adc < QM_ADC_NUM, -EINVAL);
|
|
QM_CHECK(NULL != xfer, -EINVAL);
|
|
QM_CHECK(NULL != xfer->ch, -EINVAL);
|
|
QM_CHECK(NULL != xfer->samples, -EINVAL);
|
|
QM_CHECK(xfer->ch_len > 0, -EINVAL);
|
|
QM_CHECK(xfer->ch_len <= QM_ADC_CHAN_SEQ_MAX, -EINVAL);
|
|
QM_CHECK(xfer->samples_len > 0, -EINVAL);
|
|
|
|
/* Reset the count and flush the FIFO. */
|
|
count[adc] = 0;
|
|
QM_ADC[adc].adc_sample = QM_ADC_FIFO_CLEAR;
|
|
|
|
/* Populate the sample sequence table. */
|
|
setup_seq_table(adc, xfer);
|
|
|
|
/* Copy the xfer struct so we can get access from the ISR. */
|
|
irq_xfer[adc] = xfer;
|
|
|
|
/* Clear all pending interrupts. */
|
|
QM_ADC[adc].adc_intr_status = QM_ADC_INTR_STATUS_CC |
|
|
QM_ADC_INTR_STATUS_FO |
|
|
QM_ADC_INTR_STATUS_CONT_CC;
|
|
/* Enable the continuous command and fifo overrun interupts. */
|
|
QM_ADC[adc].adc_intr_enable =
|
|
QM_ADC_INTR_ENABLE_FO | QM_ADC_INTR_ENABLE_CONT_CC;
|
|
|
|
/* Issue cmd: window & resolution, number of samples, interrupt enable
|
|
* and start continuous coversion command. If xfer->samples_len is less
|
|
* than FIFO_INTERRUPT_THRESHOLD extra samples will be discarded in the
|
|
* ISR. */
|
|
QM_ADC[adc].adc_cmd =
|
|
(sample_window[adc] << QM_ADC_CMD_SW_OFFSET |
|
|
resolution[adc] << QM_ADC_CMD_RESOLUTION_OFFSET |
|
|
((FIFO_INTERRUPT_THRESHOLD - 1) << QM_ADC_CMD_NS_OFFSET) |
|
|
QM_ADC_CMD_IE | QM_ADC_CMD_START_CONT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* QUARK_D2000 */
|