blob: f8eb7b302da281d95506518b13d5b7560a2e4bd5 [file] [log] [blame]
/*
* Copyright (c) 2017, 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_ss_adc.h"
#include <string.h>
#include "clk.h"
/* FIFO_INTERRUPT_THRESHOLD is used by qm_ss_adc_irq_convert to set the
* threshold at which the FIFO will trigger an interrupt. It is also used the
* ISR handler to determine the number of samples to read from the FIFO. */
#define FIFO_INTERRUPT_THRESHOLD (16)
#define QM_SS_ADC_CHAN_SEQ_MAX (32)
#define ADC_SAMPLE_SHIFT (11)
/* SS ADC commands. */
#define QM_SS_ADC_CMD_START_CAL (3)
#define QM_SS_ADC_CMD_LOAD_CAL (4)
/* Mode change delay is (clock speed * 5). */
#define CALCULATE_DELAY() (clk_sys_get_ticks_per_us() * 5)
static uint32_t adc_base[QM_SS_ADC_NUM] = {QM_SS_ADC_BASE};
static qm_ss_adc_xfer_t *irq_xfer[QM_SS_ADC_NUM];
static uint8_t sample_window[QM_SS_ADC_NUM];
static qm_ss_adc_resolution_t resolution[QM_SS_ADC_NUM];
static uint32_t count[QM_SS_ADC_NUM];
static void (*mode_callback[QM_SS_ADC_NUM])(void *data, int error,
qm_ss_adc_status_t status,
qm_ss_adc_cb_source_t source);
static void (*cal_callback[QM_SS_ADC_NUM])(void *data, int error,
qm_ss_adc_status_t status,
qm_ss_adc_cb_source_t source);
static void *mode_callback_data[QM_SS_ADC_NUM];
static void *cal_callback_data[QM_SS_ADC_NUM];
static void dummy_conversion(uint32_t controller);
/* As the mode change interrupt is always asserted when the requested mode
* matches the current mode, an interrupt will be triggered whenever it is
* unmasked, which may need to be suppressed. */
static volatile bool ignore_spurious_interrupt[QM_SS_ADC_NUM] = {true};
static qm_ss_adc_mode_t requested_mode[QM_SS_ADC_NUM];
static void enable_adc(void)
{
QM_SS_REG_AUX_OR(QM_SS_ADC_BASE + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_ADC_ENA);
}
static void disable_adc(void)
{
QM_SS_REG_AUX_NAND(QM_SS_ADC_BASE + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_ADC_ENA);
}
static void qm_ss_adc_isr_handler(const qm_ss_adc_t adc)
{
uint32_t i, samples_to_read;
uint32_t controller = adc_base[adc];
/* Calculate the number of samples to read. */
samples_to_read = FIFO_INTERRUPT_THRESHOLD;
if (samples_to_read > (irq_xfer[adc]->samples_len - count[adc])) {
samples_to_read = irq_xfer[adc]->samples_len - count[adc];
}
/* Read the samples into the array. */
for (i = 0; i < samples_to_read; i++) {
/* Pop one sample into the sample register. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_SET,
QM_SS_ADC_SET_POP_RX);
/* Read the sample in the array. */
irq_xfer[adc]->samples[count[adc]] =
(__builtin_arc_lr(controller + QM_SS_ADC_SAMPLE) >>
(ADC_SAMPLE_SHIFT - resolution[adc]));
count[adc]++;
}
/* Clear the data available status register. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_CLR_DATA_A);
if (count[adc] == irq_xfer[adc]->samples_len) {
/* Stop the sequencer. */
QM_SS_REG_AUX_NAND(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_SEQ_START);
/* Mask all interrupts. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_MSK_ALL_INT);
/* Call the user callback. */
if (irq_xfer[adc]->callback) {
irq_xfer[adc]->callback(irq_xfer[adc]->callback_data, 0,
QM_SS_ADC_COMPLETE,
QM_SS_ADC_TRANSFER);
}
/* Disable the ADC. */
disable_adc();
return;
}
}
static void qm_ss_adc_isr_err_handler(const qm_ss_adc_t adc)
{
uint32_t controller = adc_base[adc];
uint32_t intstat = __builtin_arc_lr(controller + QM_SS_ADC_INTSTAT);
/* Stop the sequencer. */
QM_SS_REG_AUX_NAND(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_SEQ_START);
/* Mask all interrupts. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_MSK_ALL_INT);
/* Call the user callback and pass it the status code. */
if (intstat & QM_SS_ADC_INTSTAT_OVERFLOW) {
if (irq_xfer[adc]->callback) {
irq_xfer[adc]->callback(irq_xfer[adc]->callback_data,
-EIO, QM_SS_ADC_OVERFLOW,
QM_SS_ADC_TRANSFER);
}
}
if (intstat & QM_SS_ADC_INTSTAT_UNDERFLOW) {
if (irq_xfer[adc]->callback) {
irq_xfer[adc]->callback(irq_xfer[adc]->callback_data,
-EIO, QM_SS_ADC_UNDERFLOW,
QM_SS_ADC_TRANSFER);
}
}
if (intstat & QM_SS_ADC_INTSTAT_SEQERROR) {
if (irq_xfer[adc]->callback) {
irq_xfer[adc]->callback(irq_xfer[adc]->callback_data,
-EIO, QM_SS_ADC_SEQERROR,
QM_SS_ADC_TRANSFER);
}
}
/* Clear all error interrupts. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
(QM_SS_ADC_CTRL_CLR_SEQERROR |
QM_SS_ADC_CTRL_CLR_OVERFLOW |
QM_SS_ADC_CTRL_CLR_UNDERFLOW));
/* Disable the ADC. */
disable_adc();
}
static void qm_ss_adc_isr_pwr_handler(const qm_ss_adc_t adc)
{
uint32_t controller = adc_base[adc];
/* The IRQ associated with the mode change fires an interrupt as soon
* as it is enabled so it is necessary to ignore it the first time the
* ISR runs. */
if (ignore_spurious_interrupt[adc]) {
ignore_spurious_interrupt[adc] = false;
return;
}
/* Perform a dummy conversion if we are transitioning to Normal Mode. */
if ((requested_mode[adc] >= QM_SS_ADC_MODE_NORM_CAL)) {
dummy_conversion(controller);
}
/* Call the user callback if it is set. */
if (mode_callback[adc]) {
mode_callback[adc](mode_callback_data[adc], 0, QM_SS_ADC_IDLE,
QM_SS_ADC_MODE_CHANGED);
}
}
static void qm_ss_adc_isr_cal_handler(const qm_ss_adc_t adc)
{
/* Clear the calibration request reg. */
QM_SS_REG_AUX_NAND(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL,
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
/* Call the user callback if it is set. */
if (cal_callback[adc]) {
cal_callback[adc](cal_callback_data[adc], 0, QM_SS_ADC_IDLE,
QM_SS_ADC_CAL_COMPLETE);
}
/* Disable the ADC. */
disable_adc();
}
/* ISR for SS ADC 0 Data available. */
QM_ISR_DECLARE(qm_ss_adc_0_isr)
{
qm_ss_adc_isr_handler(QM_SS_ADC_0);
}
/* ISR for SS ADC 0 Error. */
QM_ISR_DECLARE(qm_ss_adc_0_error_isr)
{
qm_ss_adc_isr_err_handler(QM_SS_ADC_0);
}
/* ISR for SS ADC 0 Mode change. */
QM_ISR_DECLARE(qm_ss_adc_0_pwr_isr)
{
qm_ss_adc_isr_pwr_handler(QM_SS_ADC_0);
}
/* ISR for SS ADC 0 Calibration. */
QM_ISR_DECLARE(qm_ss_adc_0_cal_isr)
{
qm_ss_adc_isr_cal_handler(QM_SS_ADC_0);
}
static void setup_seq_table(const qm_ss_adc_t adc, qm_ss_adc_xfer_t *xfer,
bool single_run)
{
uint32_t i, reg, ch_odd, ch_even, seq_entry = 0;
uint32_t num_channels, controller = adc_base[adc];
/* The sample window is the time in cycles between the start of one
* sample and the start of the next. Resolution is indexed from 0 so we
* need to add 1 and a further 2 for the time it takes to process. */
int delay = sample_window[adc] - (resolution[adc] + 3);
delay = delay < 0 ? 0 : delay; /* clamp underflows */
/* Reset the sequence table and sequence pointer. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_SEQ_TABLE_RST);
/* If a single run is requested and the number of channels in ch is less
* than the number of samples requested we need to insert multiple
* channels into the sequence table. */
num_channels = single_run ? xfer->samples_len : xfer->ch_len;
/* The sequence table has to be populated with pairs of entries so there
* are sample_len/2 pairs of entries. These entries are read from the
* ch array in pairs. The same delay is used between all entries. */
for (i = 0; i < (num_channels - 1); i += 2) {
ch_odd = xfer->ch[(i + 1) % xfer->ch_len];
ch_even = xfer->ch[i % xfer->ch_len];
seq_entry =
((delay << QM_SS_ADC_SEQ_DELAYODD_OFFSET) |
(ch_odd << QM_SS_ADC_SEQ_MUXODD_OFFSET) |
(delay << QM_SS_ADC_SEQ_DELAYEVEN_OFFSET) | ch_even);
__builtin_arc_sr(seq_entry, controller + QM_SS_ADC_SEQ);
}
/* If there is an uneven number of entries we need to create a final
* pair with a singly entry. */
if (num_channels % 2) {
ch_even = xfer->ch[i % xfer->ch_len];
seq_entry =
((delay << QM_SS_ADC_SEQ_DELAYEVEN_OFFSET) | (ch_even));
__builtin_arc_sr(seq_entry, controller + QM_SS_ADC_SEQ);
}
/* Reset the sequence pointer back to 0. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_SEQ_PTR_RST);
/* Set the number of entries in the sequencer. */
reg = __builtin_arc_lr(controller + QM_SS_ADC_SET);
reg &= ~QM_SS_ADC_SET_SEQ_ENTRIES_MASK;
reg |= ((num_channels - 1) << QM_SS_ADC_SET_SEQ_ENTRIES_OFFSET);
__builtin_arc_sr(reg, controller + QM_SS_ADC_SET);
}
static void dummy_conversion(uint32_t controller)
{
uint32_t reg;
int res;
/* Flush the FIFO. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_SET, QM_SS_ADC_SET_FLUSH_RX);
/* Set up sequence table. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_SEQ_TABLE_RST);
/* Populate the seq table. */
__builtin_arc_sr(QM_SS_ADC_SEQ_DUMMY, controller + QM_SS_ADC_SEQ);
/* Reset the sequence pointer back to 0. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_SEQ_PTR_RST);
/* Set the number of entries in the sequencer. */
reg = __builtin_arc_lr(controller + QM_SS_ADC_SET);
reg &= ~QM_SS_ADC_SET_SEQ_ENTRIES_MASK;
reg |= (0 << QM_SS_ADC_SET_SEQ_ENTRIES_OFFSET);
__builtin_arc_sr(reg, controller + QM_SS_ADC_SET);
/* Set the threshold. */
reg = __builtin_arc_lr(controller + QM_SS_ADC_SET);
reg &= ~QM_SS_ADC_SET_THRESHOLD_MASK;
reg |= (0 << QM_SS_ADC_SET_THRESHOLD_OFFSET);
__builtin_arc_sr(reg, controller + QM_SS_ADC_SET);
/* Set the sequence mode to single run. */
QM_SS_REG_AUX_NAND(controller + QM_SS_ADC_SET, QM_SS_ADC_SET_SEQ_MODE);
/* Clear all interrupts. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_CLR_ALL_INT);
/* Mask all interrupts. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_MSK_ALL_INT);
/* Enable the ADC. */
enable_adc();
/* Start the sequencer. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL, QM_SS_ADC_CTRL_SEQ_START);
/* Wait for the sequence to finish. */
while (!(res = __builtin_arc_lr(controller + QM_SS_ADC_INTSTAT))) {
}
/* Flush the FIFO. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_SET, QM_SS_ADC_SET_FLUSH_RX);
/* Clear the data available status register. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_CLR_DATA_A);
/* Unmask all interrupts. */
QM_SS_REG_AUX_NAND(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_MSK_ALL_INT);
/* Disable the ADC. */
disable_adc();
}
int qm_ss_adc_set_config(const qm_ss_adc_t adc,
const qm_ss_adc_config_t *const cfg)
{
uint32_t reg;
uint32_t controller = adc_base[adc];
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
QM_CHECK(NULL != cfg, -EINVAL);
QM_CHECK(cfg->resolution <= QM_SS_ADC_RES_12_BITS, -EINVAL);
/* The window must be 2 greater than the resolution but since this is
* indexed from 0 we need to add a further 1. */
QM_CHECK(cfg->window >= (cfg->resolution + 3), -EINVAL);
/* Set the sample window and resolution. */
sample_window[adc] = cfg->window;
resolution[adc] = cfg->resolution;
/* Set the resolution. */
reg = __builtin_arc_lr(controller + QM_SS_ADC_SET);
reg &= ~QM_SS_ADC_SET_SAMPLE_WIDTH_MASK;
reg |= resolution[adc];
__builtin_arc_sr(reg, controller + QM_SS_ADC_SET);
return 0;
}
int qm_ss_adc_set_mode(const qm_ss_adc_t adc, const qm_ss_adc_mode_t mode)
{
uint32_t creg, delay, intstat;
uint32_t controller = adc_base[adc];
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
QM_CHECK(mode <= QM_SS_ADC_MODE_NORM_NO_CAL, -EINVAL);
/* Save the state of the mode interrupt mask. */
intstat = QM_IR_GET_MASK(QM_INTERRUPT_ROUTER->adc_0_pwr_int_mask);
/* Mask the ADC mode change interrupt. */
QM_IR_MASK_INTERRUPTS(QM_INTERRUPT_ROUTER->adc_0_pwr_int_mask);
/* Calculate the delay. */
delay = CALCULATE_DELAY();
/* Issue mode change command and wait for it to complete. */
creg = __builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
creg &= ~((QM_SS_IO_CREG_MST0_CTRL_ADC_DELAY_MASK
<< QM_SS_IO_CREG_MST0_CTRL_ADC_DELAY_OFFSET) |
QM_SS_IO_CREG_MST0_CTRL_ADC_PWR_MODE_MASK);
creg |= ((delay << QM_SS_IO_CREG_MST0_CTRL_ADC_DELAY_OFFSET) | mode);
__builtin_arc_sr(creg, QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
/* Wait for the mode change to complete. */
while (!(__builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_SLV0_OBSR) &
QM_SS_IO_CREG_SLV0_OBSR_ADC_PWR_MODE_STS)) {
}
/* Restore the state of the mode change interrupt mask if necessary. */
if (!intstat) {
ignore_spurious_interrupt[adc] = true;
QM_IR_UNMASK_INTERRUPTS(
QM_INTERRUPT_ROUTER->adc_0_pwr_int_mask);
}
/* Perform a dummy conversion if transitioning to Normal Mode. */
if ((mode >= QM_SS_ADC_MODE_NORM_CAL)) {
dummy_conversion(controller);
}
return 0;
}
int qm_ss_adc_irq_set_mode(const qm_ss_adc_t adc, const qm_ss_adc_mode_t mode,
void (*callback)(void *data, int error,
qm_ss_adc_status_t status,
qm_ss_adc_cb_source_t source),
void *callback_data)
{
uint32_t creg, delay;
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
QM_CHECK(mode <= QM_SS_ADC_MODE_NORM_NO_CAL, -EINVAL);
mode_callback[adc] = callback;
mode_callback_data[adc] = callback_data;
requested_mode[adc] = mode;
/* Calculate the delay. */
delay = CALCULATE_DELAY();
/* Issue mode change command and wait for it to complete. */
creg = __builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
creg &= ~((QM_SS_IO_CREG_MST0_CTRL_ADC_DELAY_MASK
<< QM_SS_IO_CREG_MST0_CTRL_ADC_DELAY_OFFSET) |
QM_SS_IO_CREG_MST0_CTRL_ADC_PWR_MODE_MASK);
creg |= ((delay << QM_SS_IO_CREG_MST0_CTRL_ADC_DELAY_OFFSET) | mode);
__builtin_arc_sr(creg, QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
return 0;
}
int qm_ss_adc_calibrate(const qm_ss_adc_t adc __attribute__((unused)))
{
uint32_t creg, intstat;
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
/* Save the state of the calibration interrupt mask. */
intstat = QM_IR_GET_MASK(QM_INTERRUPT_ROUTER->adc_0_cal_int_mask);
/* Mask the ADC calibration interrupt. */
QM_IR_MASK_INTERRUPTS(QM_INTERRUPT_ROUTER->adc_0_cal_int_mask);
/* Enable the ADC. */
enable_adc();
/* Issue the start calibrate command. */
creg = __builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
creg &= ~(QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_CMD_MASK |
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
creg |= ((QM_SS_ADC_CMD_START_CAL
<< QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_CMD_OFFSET) |
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
__builtin_arc_sr(creg, QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
/* Wait for the calibrate command to complete. */
while (!(__builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_SLV0_OBSR) &
QM_SS_IO_CREG_SLV0_OBSR_ADC_CAL_ACK)) {
}
/* Clear the calibration request reg and wait for it to complete. */
QM_SS_REG_AUX_NAND(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL,
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
while (__builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_SLV0_OBSR) &
QM_SS_IO_CREG_SLV0_OBSR_ADC_CAL_ACK) {
}
/* Disable the ADC. */
disable_adc();
/* Restore the state of the calibration interrupt mask if necessary. */
if (!intstat) {
QM_IR_UNMASK_INTERRUPTS(
QM_INTERRUPT_ROUTER->adc_0_cal_int_mask);
}
return 0;
}
int qm_ss_adc_irq_calibrate(const qm_ss_adc_t adc,
void (*callback)(void *data, int error,
qm_ss_adc_status_t status,
qm_ss_adc_cb_source_t source),
void *callback_data)
{
uint32_t creg;
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
cal_callback[adc] = callback;
cal_callback_data[adc] = callback_data;
/* Enable the ADC. */
enable_adc();
/* Issue the start calibrate command. */
creg = __builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
creg &= ~(QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_CMD_MASK |
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
creg |= ((QM_SS_ADC_CMD_START_CAL
<< QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_CMD_OFFSET) |
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
__builtin_arc_sr(creg, QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
return 0;
}
int qm_ss_adc_set_calibration(const qm_ss_adc_t adc __attribute__((unused)),
const qm_ss_adc_calibration_t cal_data)
{
uint32_t creg, intstat;
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
QM_CHECK(cal_data <= QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_VAL_MAX, -EINVAL);
/* Save the state of the calibration interrupt mask. */
intstat = QM_IR_GET_MASK(QM_INTERRUPT_ROUTER->adc_0_cal_int_mask);
/* Mask the ADC calibration interrupt. */
QM_IR_MASK_INTERRUPTS(QM_INTERRUPT_ROUTER->adc_0_cal_int_mask);
/* Issue the load calibrate command. */
creg = __builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
creg &= ~(QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_VAL_MASK |
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_CMD_MASK |
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
creg |= ((cal_data << QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_VAL_OFFSET) |
(QM_SS_ADC_CMD_LOAD_CAL
<< QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_CMD_OFFSET) |
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
__builtin_arc_sr(creg, QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL);
/* Wait for the calibrate command to complete. */
while (!(__builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_SLV0_OBSR) &
QM_SS_IO_CREG_SLV0_OBSR_ADC_CAL_ACK)) {
}
/* Clear the calibration request reg and wait for it to complete. */
QM_SS_REG_AUX_NAND(QM_SS_CREG_BASE + QM_SS_IO_CREG_MST0_CTRL,
QM_SS_IO_CREG_MST0_CTRL_ADC_CAL_REQ);
while (__builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_SLV0_OBSR) &
QM_SS_IO_CREG_SLV0_OBSR_ADC_CAL_ACK) {
}
/* Restore the state of the calibration interrupt mask if necessary. */
if (!intstat) {
QM_IR_UNMASK_INTERRUPTS(
QM_INTERRUPT_ROUTER->adc_0_cal_int_mask);
}
return 0;
}
int qm_ss_adc_get_calibration(const qm_ss_adc_t adc __attribute__((unused)),
qm_ss_adc_calibration_t *const cal)
{
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
QM_CHECK(NULL != cal, -EINVAL);
*cal = ((__builtin_arc_lr(QM_SS_CREG_BASE + QM_SS_IO_CREG_SLV0_OBSR) &
QM_SS_IO_CREG_SLV0_OBSR_ADC_CAL_VAL_MASK) >>
QM_SS_IO_CREG_SLV0_OBSR_ADC_CAL_VAL_OFFSET);
return 0;
}
int qm_ss_adc_convert(const qm_ss_adc_t adc, qm_ss_adc_xfer_t *xfer,
qm_ss_adc_status_t *const status)
{
uint32_t reg, i;
uint32_t controller = adc_base[adc];
int res;
QM_CHECK(adc < QM_SS_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_SS_ADC_CHAN_SEQ_MAX, -EINVAL);
QM_CHECK(xfer->samples_len > 0, -EINVAL);
QM_CHECK(xfer->samples_len <= QM_SS_ADC_FIFO_LEN, -EINVAL);
/* Flush the FIFO. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_SET, QM_SS_ADC_SET_FLUSH_RX);
/* Populate the sequence table. */
setup_seq_table(adc, xfer, true);
/* Set the threshold. */
reg = __builtin_arc_lr(controller + QM_SS_ADC_SET);
reg &= ~QM_SS_ADC_SET_THRESHOLD_MASK;
reg |= ((xfer->samples_len - 1) << QM_SS_ADC_SET_THRESHOLD_OFFSET);
__builtin_arc_sr(reg, controller + QM_SS_ADC_SET);
/* Set the sequence mode to single run. */
QM_SS_REG_AUX_NAND(controller + QM_SS_ADC_SET, QM_SS_ADC_SET_SEQ_MODE);
/* Mask all interrupts. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_MSK_ALL_INT);
/* Enable the ADC. */
enable_adc();
/* Start the sequencer. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL, QM_SS_ADC_CTRL_SEQ_START);
/* Wait for the sequence to finish. */
while (!(res = __builtin_arc_lr(controller + QM_SS_ADC_INTSTAT))) {
}
/* Return if we get an error (UNDERFLOW, OVERFLOW, SEQ_ERROR). */
if (res > 1) {
if (status) {
*status = res;
}
return -EIO;
}
/* Read the samples into the array. */
for (i = 0; i < xfer->samples_len; i++) {
/* Pop one sample into the sample register. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_SET,
QM_SS_ADC_SET_POP_RX);
/* Read the sample in the array. */
xfer->samples[i] =
(__builtin_arc_lr(controller + QM_SS_ADC_SAMPLE) >>
(ADC_SAMPLE_SHIFT - resolution[adc]));
}
/* Clear the data available status register. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL,
QM_SS_ADC_CTRL_CLR_DATA_A);
/* Disable the ADC. */
disable_adc();
return 0;
}
int qm_ss_adc_irq_convert(const qm_ss_adc_t adc, qm_ss_adc_xfer_t *xfer)
{
uint32_t reg;
uint32_t controller = adc_base[adc];
QM_CHECK(adc < QM_SS_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->samples_len > 0, -EINVAL);
QM_CHECK(xfer->ch_len <= QM_SS_ADC_CHAN_SEQ_MAX, -EINVAL);
/* Flush the FIFO. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_SET, QM_SS_ADC_SET_FLUSH_RX);
/* Populate the sequence table. */
setup_seq_table(adc, xfer, false);
/* Copy the xfer struct so we can get access from the ISR. */
irq_xfer[adc] = xfer;
/* Set count back to 0. */
count[adc] = 0;
/* Set the threshold. */
reg = __builtin_arc_lr(controller + QM_SS_ADC_SET);
reg &= ~QM_SS_ADC_SET_THRESHOLD_MASK;
reg |= (FIFO_INTERRUPT_THRESHOLD - 1) << QM_SS_ADC_SET_THRESHOLD_OFFSET;
__builtin_arc_sr(reg, controller + QM_SS_ADC_SET);
/* Set the sequence mode to repetitive. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_SET, QM_SS_ADC_SET_SEQ_MODE);
/* Enable all interrupts. */
QM_SS_REG_AUX_NAND(controller + QM_SS_ADC_CTRL, 0x1F00);
/* Enable the ADC. */
enable_adc();
/* Start the sequencer. */
QM_SS_REG_AUX_OR(controller + QM_SS_ADC_CTRL, QM_SS_ADC_CTRL_SEQ_START);
return 0;
}
#if (ENABLE_RESTORE_CONTEXT)
int qm_ss_adc_save_context(const qm_ss_adc_t adc,
qm_ss_adc_context_t *const ctx)
{
const uint32_t controller = adc_base[adc];
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
QM_CHECK(NULL != ctx, -EINVAL);
ctx->adc_set = __builtin_arc_lr(controller + QM_SS_ADC_SET);
ctx->adc_divseqstat =
__builtin_arc_lr(controller + QM_SS_ADC_DIVSEQSTAT);
ctx->adc_seq = __builtin_arc_lr(controller + QM_SS_ADC_SEQ);
/* Restore control register with ADC enable bit cleared. */
ctx->adc_ctrl = __builtin_arc_lr(controller + QM_SS_ADC_CTRL) &
~QM_SS_ADC_CTRL_ADC_ENA;
/*
* The IRQ associated with the mode change fires an interrupt as soon
* as it is enabled so it is necessary to ignore it the first time the
* ISR runs. This is set in the save function in case interrupts are
* enabled before the restore function runs.
*/
ignore_spurious_interrupt[adc] = true;
return 0;
}
int qm_ss_adc_restore_context(const qm_ss_adc_t adc,
const qm_ss_adc_context_t *const ctx)
{
const uint32_t controller = adc_base[adc];
QM_CHECK(adc < QM_SS_ADC_NUM, -EINVAL);
QM_CHECK(NULL != ctx, -EINVAL);
__builtin_arc_sr(ctx->adc_set, controller + QM_SS_ADC_SET);
__builtin_arc_sr(ctx->adc_divseqstat,
controller + QM_SS_ADC_DIVSEQSTAT);
__builtin_arc_sr(ctx->adc_seq, controller + QM_SS_ADC_SEQ);
__builtin_arc_sr(ctx->adc_ctrl, controller + QM_SS_ADC_CTRL);
return 0;
}
#else
int qm_ss_adc_save_context(const qm_ss_adc_t adc,
qm_ss_adc_context_t *const ctx)
{
(void)adc;
(void)ctx;
return 0;
}
int qm_ss_adc_restore_context(const qm_ss_adc_t adc,
const qm_ss_adc_context_t *const ctx)
{
(void)adc;
(void)ctx;
return 0;
}
#endif /* ENABLE_RESTORE_CONTEXT */