| /* |
| * 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_isr) |
| { |
| qm_adc_isr_handler(QM_ADC_0); |
| |
| QM_ISR_EOI(QM_IRQ_ADC_0_VECTOR); |
| } |
| |
| /* ISR for ADC 0 Mode Change. */ |
| QM_ISR_DECLARE(qm_adc_pwr_0_isr) |
| { |
| qm_adc_pwr_0_isr_handler(QM_ADC_0); |
| |
| QM_ISR_EOI(QM_IRQ_ADC_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 */ |