/*
 * Copyright (c) 2018, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  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.
 *
 * *  Neither the name of Texas Instruments Incorporated 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 COPYRIGHT OWNER 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 <stdint.h>

/* Kernel services */
#include <ti/drivers/dpl/ClockP.h>
#include <ti/drivers/dpl/DebugP.h>
#include <ti/drivers/dpl/HwiP.h>
#include <ti/drivers/dpl/SemaphoreP.h>
#include <ti/drivers/dpl/SwiP.h>

/* TI-RTOS drivers */
#include <ti/drivers/ADCBuf.h>
#include <ti/drivers/adcbuf/ADCBufCC26X2.h>
#include <ti/drivers/PIN.h>
#include <ti/drivers/pin/PINCC26XX.h>
#include <ti/drivers/dma/UDMACC26XX.h>
#include <ti/drivers/timer/GPTimerCC26XX.h>
#include <ti/drivers/Power.h>
#include <ti/drivers/power/PowerCC26X2.h>

#include <ti/devices/DeviceFamily.h>
#include DeviceFamily_constructPath(inc/hw_memmap.h)
#include DeviceFamily_constructPath(inc/hw_ints.h)
#include DeviceFamily_constructPath(inc/hw_types.h)
#include DeviceFamily_constructPath(inc/hw_aux_evctl.h)
#include DeviceFamily_constructPath(inc/hw_gpt.h)
#include DeviceFamily_constructPath(inc/hw_event.h)
#include DeviceFamily_constructPath(driverlib/aux_adc.h)
#include DeviceFamily_constructPath(driverlib/aux_smph.h)
#include DeviceFamily_constructPath(driverlib/sys_ctrl.h)
#include DeviceFamily_constructPath(driverlib/ioc.h)
#include DeviceFamily_constructPath(driverlib/aon_ioc.h)

#define AUX_EVCTL_DMACTL_SEL_FIFO_NOT_EMPTY     AUX_EVCTL_DMACTL_SEL_AUX_ADC_FIFO_NOT_EMPTY
#define AUX_EVCTL_EVTOMCUFLAGS_ADC_DONE         AUX_EVCTL_EVTOMCUFLAGS_AUX_ADC_DONE
#define AUX_EVCTL_EVTOMCUFLAGS_ADC_IRQ          AUX_EVCTL_EVTOMCUFLAGS_AUX_ADC_IRQ

/*
 * =============================================================================
 * Public Function Declarations
 * =============================================================================
 */
void ADCBufCC26X2_init(ADCBuf_Handle handle);
ADCBuf_Handle ADCBufCC26X2_open(ADCBuf_Handle handle, const ADCBuf_Params *params);
int_fast16_t ADCBufCC26X2_convert(ADCBuf_Handle handle, ADCBuf_Conversion conversions[],  uint_fast8_t channelCount);
int_fast16_t ADCBufCC26X2_convertCancel(ADCBuf_Handle handle);
void ADCBufCC26X2_close(ADCBuf_Handle handle);
uint_fast8_t ADCBufCC26X2_getResolution(ADCBuf_Handle handle);
int_fast16_t ADCBufCC26X2_adjustRawValues(ADCBuf_Handle handle, void *sampleBuffer, uint_fast16_t sampleCount, uint32_t adcChannel);
int_fast16_t ADCBufCC26X2_convertAdjustedToMicroVolts(ADCBuf_Handle handle, uint32_t  adcChannel, void *adjustedSampleBuffer, uint32_t outputMicroVoltBuffer[], uint_fast16_t sampleCount);
int_fast16_t ADCBufCC26X2_control(ADCBuf_Handle handle, uint_fast16_t cmd, void *arg);

/*
 * =============================================================================
 * Private Function Declarations
 * =============================================================================
 */
static bool ADCBufCC26X2_acquireADCSemaphore(ADCBuf_Handle handle);
static bool ADCBufCC26X2_releaseADCSemaphore(ADCBuf_Handle handle);
static void ADCBufCC26X2_configDMA(ADCBuf_Handle handle, ADCBuf_Conversion *conversion);
static void ADCBufCC26X2_hwiFxn (uintptr_t arg);
static void ADCBufCC26X2_swiFxn (uintptr_t arg0, uintptr_t arg1);
static void ADCBufCC26X2_conversionCallback(ADCBuf_Handle handle, ADCBuf_Conversion *conversion, void *completedADCBuffer, uint32_t completedChannel);
static uint32_t ADCBufCC26X2_freqToCounts(uint32_t frequency);
static void ADCBufCC26X2_cleanADC(ADCBuf_Handle handle);
static void ADCBufCC26X2_loadDMAControlTableEntry(ADCBuf_Handle handle, ADCBuf_Conversion *conversion, bool primaryEntry);

/*
 * =============================================================================
 * Constants
 * =============================================================================
 */

const ADCBuf_FxnTable ADCBufCC26X2_fxnTable = {
    /*! Function to close the specified peripheral */
    ADCBufCC26X2_close,
    /*! Function to driver implementation specific control function */
    ADCBufCC26X2_control,
    /*! Function to initialize the given data object */
    ADCBufCC26X2_init,
    /*! Function to open the specified peripheral */
    ADCBufCC26X2_open,
    /*! Function to start an ADC conversion with the specified peripheral */
    ADCBufCC26X2_convert,
    /*! Function to abort a conversion being carried out by the specified peripheral */
    ADCBufCC26X2_convertCancel,
    /*! Function to get the resolution in bits of the ADC */
    ADCBufCC26X2_getResolution,
    /*! Function to adjust raw ADC output values to values comparable between devices of the same type */
    ADCBufCC26X2_adjustRawValues,
    /*! Function to convert adjusted ADC values to microvolts */
    ADCBufCC26X2_convertAdjustedToMicroVolts
};


/*
 * =============================================================================
 * Private Global Variables
 * =============================================================================
 */

/* Allocate space for DMA control table entry */
ALLOCATE_CONTROL_TABLE_ENTRY(dmaADCPriControlTableEntry, (UDMA_CHAN_AUX_ADC + UDMA_PRI_SELECT));
ALLOCATE_CONTROL_TABLE_ENTRY(dmaADCAltControlTableEntry, (UDMA_CHAN_AUX_ADC + UDMA_ALT_SELECT));

/*
 * =============================================================================
 * Function Definitions
 * =============================================================================
 */

/*
 * ======== ADCBufCC26X2_init ========
 */
void ADCBufCC26X2_init(ADCBuf_Handle handle) {
    ADCBufCC26X2_Object        *object;

    /* Get the pointer to the object */
    object = handle->object;
    /* Mark the object as available */
    object->isOpen = false;
}


/*
 * ======== ADCBufCC26X2_open ========
 */
ADCBuf_Handle ADCBufCC26X2_open(ADCBuf_Handle handle, const ADCBuf_Params *params) {
    /* Use union to save on stack allocation */
    union {
        HwiP_Params              hwiParams;
        SwiP_Params              swiParams;
        GPTimerCC26XX_Params     timerParams;
    } paramsUnion;
    ADCBufCC26X2_Object         *object;
    ADCBufCC26X2_HWAttrs  const *hwAttrs;
    uint32_t                    key;
    uint32_t                    adcPeriodCounts;
    uint32_t                    gptConfig;

    /* Get the pointer to the object and hwAttrs */
    object  = handle->object;
    hwAttrs = handle->hwAttrs;

    /* Disable preemption while checking if the ADC is open. */
    key = HwiP_disable();

    /* Check if the ADC is open already with the base addr. */
    if (object->isOpen == true) {
        HwiP_restore(key);

        DebugP_log0("ADCBuf: already in use.");

        return (NULL);
    }

    /* Mark the handle as being used */
    object->isOpen = true;

    HwiP_restore(key);

    /* Initialise the ADC object */
    /* Initialise params section of object */
    object->conversionInProgress        = false;
    object->semaphoreTimeout            = params->blockingTimeout;
    object->samplingFrequency           = params->samplingFrequency;
    object->returnMode                  = params->returnMode;
    object->recurrenceMode              = params->recurrenceMode;
    object->keepADCSemaphore            = false;
    object->adcSemaphoreInPossession    = false;

    if (params->custom) {
        /* If CC26X2 specific params were specified, use them */
        object->samplingDuration        = ((ADCBufCC26X2_ParamsExtension *)(params->custom))->samplingDuration;
        object->refSource               = ((ADCBufCC26X2_ParamsExtension *)(params->custom))->refSource;
        object->samplingMode            = ((ADCBufCC26X2_ParamsExtension *)(params->custom))->samplingMode;
        object->inputScalingEnabled     = ((ADCBufCC26X2_ParamsExtension *)(params->custom))->inputScalingEnabled;
    }
    else {
        /* Initialise CC26X2 specific settings to defaults */
        object->inputScalingEnabled         = true;
        object->refSource                   = ADCBufCC26X2_FIXED_REFERENCE;
        object->samplingMode                = ADCBufCC26X2_SAMPING_MODE_SYNCHRONOUS;
        object->samplingDuration            = ADCBufCC26X2_SAMPLING_DURATION_2P7_US;
    }

    /* Open timer resource */
    GPTimerCC26XX_Params_init(&paramsUnion.timerParams);
    paramsUnion.timerParams.width           = GPT_CONFIG_16BIT;
    paramsUnion.timerParams.mode            = GPT_MODE_PERIODIC_UP;
    paramsUnion.timerParams.debugStallMode  = GPTimerCC26XX_DEBUG_STALL_OFF;
    object->timerHandle                     = GPTimerCC26XX_open(hwAttrs->gpTimerUnit, &paramsUnion.timerParams);

    if (object->timerHandle == NULL) {
        /* We did not manage to open the GPTimer we wanted */
        return NULL;
    }

    /* Enable capture toggle event on timeout. The ADC will trigger a
       conversion on the rising edge of the event pulse. */
    gptConfig = HWREG(object->timerHandle->hwAttrs->baseAddr + GPT_O_TAMR);
    HWREG(object->timerHandle->hwAttrs->baseAddr + GPT_O_TAMR) = (gptConfig & 0xFF) | GPT_TAMR_TCACT_TOG_ON_TO;

    /* Calculate period count for the GPT using double the sample freqency.
       The GPT need to generate two events each ADC period to create
       the trigger pulse. */
    adcPeriodCounts = ADCBufCC26X2_freqToCounts(params->samplingFrequency * 2);
    GPTimerCC26XX_setLoadValue(object->timerHandle, adcPeriodCounts);

    if (params->returnMode == ADCBuf_RETURN_MODE_BLOCKING) {
        /* Continuous trigger mode and blocking return mode is an illegal combination */
        DebugP_assert(!(params->recurrenceMode == ADCBuf_RECURRENCE_MODE_CONTINUOUS));

        /* Create a semaphore to block task execution for the duration of the ADC conversions */
        SemaphoreP_constructBinary(&(object->conversionComplete), 0);

        /* Store internal callback function */
        object->callbackFxn = ADCBufCC26X2_conversionCallback;
    }
    else {
        /* Callback mode without a callback function defined */
        DebugP_assert(params->callbackFxn);

        /* Save the callback function pointer */
        object->callbackFxn = params->callbackFxn;
    }

    /* Create the Hwi for this ADC peripheral. */
    HwiP_Params_init(&paramsUnion.hwiParams);
    paramsUnion.hwiParams.arg = (uintptr_t) handle;
    paramsUnion.hwiParams.priority = hwAttrs->intPriority;
    HwiP_construct(&(object->hwi), INT_AUX_ADC_IRQ, ADCBufCC26X2_hwiFxn, &paramsUnion.hwiParams);

    /* Create the Swi object for this ADC peripheral */
    SwiP_Params_init(&paramsUnion.swiParams);
    paramsUnion.swiParams.arg0 = (uintptr_t)handle;
    paramsUnion.swiParams.priority = hwAttrs->swiPriority;
    SwiP_construct(&(object->swi), ADCBufCC26X2_swiFxn, &(paramsUnion.swiParams));

    /* Declare the dependency on the UDMA driver */
    object->udmaHandle = UDMACC26XX_open();

    /* Return the handle after finishing initialisation of the driver */
    DebugP_log0("ADC: opened");
    return handle;
}


/*!
 *  @brief  HWI ISR of the ADC triggered when the DMA transaction is complete
 *
 *  @param  arg         An ADCBufCC26X2_Handle
 *
 */
static void ADCBufCC26X2_hwiFxn (uintptr_t arg) {
    ADCBufCC26X2_Object            *object;
    ADCBuf_Conversion              *conversion;
    uint32_t                        intStatus;

    /* Get the pointer to the object and current conversion*/
    object     = ((ADCBuf_Handle)arg)->object;
    conversion = object->currentConversion;

    /* Set activeSampleBuffer to primary as default */
    object->activeSampleBuffer = conversion->sampleBuffer;

    if (object->recurrenceMode == ADCBuf_RECURRENCE_MODE_ONE_SHOT) {
        /* Disable the ADC */
        AUXADCDisable();
        /* Disable ADC DMA if we are only doing one conversion and clear DMA done interrupt. */
        HWREG(AUX_EVCTL_BASE + AUX_EVCTL_O_DMACTL) = AUX_EVCTL_DMACTL_REQ_MODE_SINGLE | AUX_EVCTL_DMACTL_SEL_FIFO_NOT_EMPTY ;
    }
    else if (object->recurrenceMode == ADCBuf_RECURRENCE_MODE_CONTINUOUS) {
        /* Reload the finished DMA control table entry */
        if (HWREG(UDMA0_BASE + UDMA_O_SETCHNLPRIALT) & (1 << UDMA_CHAN_AUX_ADC)) {
            /* We are currently using the alternate entry -> we just finished the primary entry -> reload primary entry */
            ADCBufCC26X2_loadDMAControlTableEntry((ADCBuf_Handle)arg, conversion, true);
        }
        else {
            /* We are currently using the primary entry -> we just finished the alternate entry -> reload the alternate entry */
            ADCBufCC26X2_loadDMAControlTableEntry((ADCBuf_Handle)arg, conversion, false);
            object->activeSampleBuffer = conversion->sampleBufferTwo;
        }
    }
    /* Clear DMA interrupts */
    UDMACC26XX_clearInterrupt(object->udmaHandle, (1 << UDMA_CHAN_AUX_ADC));

    /* Get the status of the ADC_IRQ line and ADC_DONE */
    intStatus = HWREG(AUX_EVCTL_BASE + AUX_EVCTL_O_EVTOMCUFLAGS) & (AUX_EVCTL_EVTOMCUFLAGS_ADC_IRQ | AUX_EVCTL_EVTOMCUFLAGS_ADC_DONE);
    /* Clear the ADC_IRQ flag if it triggered the ISR */
    HWREG(AUX_EVCTL_BASE + AUX_EVCTL_O_EVTOMCUFLAGSCLR) = intStatus;

    /* Post SWI to handle remaining clean up and invocation of callback */
    SwiP_post(&(object->swi));
}

/*!
 *  @brief  SWI ISR of the ADC triggered when the DMA transaction is complete
 *
 *  @param  arg0        An ADCBufCC26X2_Handle
 *
 */
static void ADCBufCC26X2_swiFxn (uintptr_t arg0, uintptr_t arg1) {
    uint32_t                        key;
    ADCBuf_Conversion               *conversion;
    ADCBufCC26X2_Object             *object;
    uint16_t                        *sampleBuffer;
    uint8_t                          channel;

    /* Get the pointer to the object */
    object = ((ADCBuf_Handle)arg0)->object;

    DebugP_log0("ADC: swi interrupt context start");

    /* Disable interrupts */
    key = HwiP_disable();

    /* Use a temporary pointers in case the callback function
     * attempts to perform another ADCBuf_transfer call
     */
    conversion   = object->currentConversion;
    sampleBuffer = object->activeSampleBuffer;
    channel      = object->currentChannel;

    if (object->recurrenceMode == ADCBuf_RECURRENCE_MODE_ONE_SHOT) {
        /* Clean up ADC and DMA */
        ADCBufCC26X2_cleanADC(((ADCBuf_Handle)arg0));
        /* Indicate we are done with this transfer */
        object->currentConversion = NULL;
    }

    /* Restore interrupts */
    HwiP_restore(key);

    /* Perform callback */
    object->callbackFxn((ADCBuf_Handle)arg0, conversion, sampleBuffer, channel);

    DebugP_log0("ADC: swi interrupt context end");
}

/*!
 *  @brief  CC26X2 internal callback function that posts the semaphore in blocking mode
 *
 *  @param  handle      An ADCBufCC26X2_Handle
 *
 *  @param  conversion  A pointer to the current ADCBuf_Conversion
 *
 */
static void ADCBufCC26X2_conversionCallback(ADCBuf_Handle handle, ADCBuf_Conversion *conversion, void *completedADCBuffer, uint32_t completedChannel) {
    ADCBufCC26X2_Object        *object;

    DebugP_log0("ADC DMA: posting conversionComplete semaphore");

    /* Get the pointer to the object */
    object = handle->object;

    /* Post the semaphore */
    SemaphoreP_post(&(object->conversionComplete));
}

/*
 * ======== ADCBufCC26X2_convert ========
 */
int_fast16_t ADCBufCC26X2_convert(ADCBuf_Handle handle, ADCBuf_Conversion conversions[],  uint_fast8_t channelCount) {
    uint32_t                        key;
    ADCBufCC26X2_Object             *object;
    ADCBufCC26X2_HWAttrs const      *hwAttrs;
    PIN_Config                      adcPinTable[2];
    uint8_t                         i = 0;

    DebugP_assert(handle);

    /* Get the pointer to the object */
    object  = handle->object;
    hwAttrs = handle->hwAttrs;

    DebugP_assert(channelCount == 1);
    DebugP_assert((conversions->samplesRequestedCount <= UDMA_XFER_SIZE_MAX));
    DebugP_assert(conversions->sampleBuffer);
    DebugP_assert(!(object->recurrenceMode == (ADCBuf_RECURRENCE_MODE_CONTINUOUS && !(conversions->sampleBufferTwo))));

    /* Disable interrupts */
    key = HwiP_disable();

    /* Check if ADC is open and that no other transfer is in progress */
    if (!(object->isOpen) || object->conversionInProgress) {
        /* Restore interrupts */
        HwiP_restore(key);
        DebugP_log0("ADCBuf: conversion failed");
        return ADCBuf_STATUS_ERROR;
    }
    object->conversionInProgress = true;

    /* Restore interrupts*/
    HwiP_restore(key);

    /* Specify input in ADC module */
    AUXADCSelectInput(hwAttrs->adcChannelLut[conversions->adcChannel].compBInput);

    /* Add pin to measure on */
    adcPinTable[i++] = (hwAttrs->adcChannelLut[conversions->adcChannel].dio) |
                        PIN_NOPULL |
                        PIN_INPUT_DIS |
                        PIN_GPIO_OUTPUT_DIS |
                        PIN_IRQ_DIS |
                        PIN_DRVSTR_MIN;

    /* Terminate pin list */
    adcPinTable[i] = PIN_TERMINATE;
    object->pinHandle = PIN_open(&object->pinState, adcPinTable);
    if (!object->pinHandle) {
        object->conversionInProgress = false;
        return ADCBuf_STATUS_ERROR;
    }

    /* Save which channel we are converting on for the callbackFxn */
    object->currentChannel = conversions->adcChannel;

    /* Try to acquire the ADC semaphore if we do not already have it. */
    if (object->adcSemaphoreInPossession == false) {
         if (!AUXSMPHTryAcquire(AUX_SMPH_2)) {
            PIN_close(object->pinHandle);
            object->conversionInProgress = false;
            DebugP_log0("ADCBuf: failed to acquire semaphore");
            return ADCBuf_STATUS_ERROR;
        }
        object->adcSemaphoreInPossession = true;
    }

    /* Store location of the current conversion */
    object->currentConversion = conversions;

    /* Configure and arm the DMA and AUX DMA control */
    ADCBufCC26X2_configDMA(handle, conversions);

    /* Flush the ADC FIFO in case we have triggered prior to this call */
    AUXADCFlushFifo();

    /* If input scaling is set to disabled in the params, disable it */
    if (!object->inputScalingEnabled) {
        AUXADCDisableInputScaling();
    }

    /* Arm the ADC in preparation for incoming conversion triggers */
    if (object->samplingMode == ADCBufCC26X2_SAMPING_MODE_SYNCHRONOUS) {
        /* ADCBufCC26X2_SYNCHRONOUS sampling mode */
        AUXADCEnableSync(object->refSource, object->samplingDuration, AUXADC_TRIGGER_GPT0A_CMP);
    }
    else {
        /* ADCBufCC26X2_ASYNCHRONOUS sampling mode */
        AUXADCEnableAsync(object->refSource, AUXADC_TRIGGER_GPT0A_CMP);
    }

    /* Start the GPTimer to create ADC triggers */
    GPTimerCC26XX_start(object->timerHandle);

    /* Set constraints to guarantee operation */
    Power_setConstraint(PowerCC26XX_DISALLOW_STANDBY);

    if (object->returnMode == ADCBuf_RETURN_MODE_BLOCKING) {
        DebugP_log0("ADCBuf: transfer pending on conversionComplete "
                                "semaphore");

        if (SemaphoreP_OK != SemaphoreP_pend(&(object->conversionComplete),
                    object->semaphoreTimeout)) {
            /* Cancel the transfer if we experience a timeout */
            ADCBufCC26X2_convertCancel(handle);
            /*
             * ADCBufCC26X2_convertCancel peforms a callback which posts a
             * conversionComplete semaphore. This call consumes this extra post.
             */
            SemaphoreP_pend(&(object->conversionComplete), SemaphoreP_NO_WAIT);
            return ADCBuf_STATUS_ERROR;
        }
    }


    return ADCBuf_STATUS_SUCCESS;
}

/*
 * ======== ADCBufCC26X2_convertCancel ========
 */
int_fast16_t ADCBufCC26X2_convertCancel(ADCBuf_Handle handle) {
    ADCBufCC26X2_Object            *object;
    ADCBuf_Conversion              *conversion;

    DebugP_assert(handle);

    /* Get the pointer to the object and hwAttrs*/
    object = handle->object;

    /* Check if ADC is open and that no other transfer is in progress */
    if (!(object->conversionInProgress)) {
        DebugP_log0("ADCBuf: a conversion must be in progress to cancel one");
        return ADCBuf_STATUS_ERROR;
    }

    /* Stop triggering a conversion on trigger events */
    AUXADCDisable();

    /* Set hardware and software configuration to default and turn off driver */
    ADCBufCC26X2_cleanADC(handle);

    /* Use a temporary transaction pointer in case the callback function
     * attempts to perform another ADCBuf_convert call
     */
    conversion = object->currentConversion;

    /* Perform callback if we are in one-shot mode. In continuous mode, ADCBuf_convertCancel will probably be called from the callback functon itself. No need to call it again. */
    if (object->recurrenceMode == ADCBuf_RECURRENCE_MODE_ONE_SHOT) {
        object->callbackFxn(handle, conversion, conversion->sampleBuffer, object->currentChannel);
    }

    return ADCBuf_STATUS_SUCCESS;
}

/*
 * ======== ADCBufCC26X2_close ========
 */
void ADCBufCC26X2_close(ADCBuf_Handle handle) {
    ADCBufCC26X2_Object         *object;

    DebugP_assert(handle);

    /* Get the pointer to the object */
    object = handle->object;

    /* Check if the ADC is running and abort conversion if necessary. */
    if (object->conversionInProgress) {
       ADCBuf_convertCancel(handle);
    }

    /* Get the pointer to the object */
    object = handle->object;

    /* Release the uDMA dependency and potentially power down uDMA. */
    UDMACC26XX_close(object->udmaHandle);

    /* Destroy the Hwi */
    HwiP_destruct(&(object->hwi));

    /* Destroy the Swi */
    SwiP_destruct(&(object->swi));

    /* Close the timer */
    GPTimerCC26XX_close(object->timerHandle);

    if (object->returnMode == ADCBuf_RETURN_MODE_BLOCKING) {
        SemaphoreP_destruct(&(object->conversionComplete));
    }

    /* Mark the module as available */
    object->isOpen = false;

    DebugP_log0("ADCBuf: closed");
}

/*
 * ======== ADCBufCC26X2_getResolution ========
 */
uint_fast8_t ADCBufCC26X2_getResolution(ADCBuf_Handle handle) {
    return (ADCBufCC26X2_RESOLUTION);
}

/*
 * ======== ADCBufCC26X2_adjustRawValues ========
 */
int_fast16_t ADCBufCC26X2_adjustRawValues(ADCBuf_Handle handle, void *sampleBuffer, uint_fast16_t sampleCount, uint32_t adcChannel) {
    ADCBufCC26X2_Object *object;
    uint32_t            gain;
    uint32_t            offset;
    uint16_t            i;

    object = handle->object;

    gain = AUXADCGetAdjustmentGain(object->refSource);
    offset = AUXADCGetAdjustmentOffset(object->refSource);

    for (i = 0; i < sampleCount; i++) {
        uint16_t tmpRawADCVal = ((uint16_t *)sampleBuffer)[i];
        ((uint16_t *)sampleBuffer)[i] = AUXADCAdjustValueForGainAndOffset(tmpRawADCVal, gain, offset);
    }

    return ADCBuf_STATUS_SUCCESS;
}

/*
 * ======== ADCBufCC26X2_convertAdjustedToMicroVolts ========
 */
int_fast16_t ADCBufCC26X2_convertAdjustedToMicroVolts(ADCBuf_Handle handle, uint32_t  adcChannel, void *adjustedSampleBuffer, uint32_t outputMicroVoltBuffer[], uint_fast16_t sampleCount) {
    ADCBufCC26X2_Object *object;
    uint16_t            i;
    uint32_t            voltageRef;

    object = handle->object;

    voltageRef = (object->inputScalingEnabled) ? AUXADC_FIXED_REF_VOLTAGE_NORMAL : AUXADC_FIXED_REF_VOLTAGE_UNSCALED;

    for (i = 0; i < sampleCount; i++) {
        outputMicroVoltBuffer[i] = AUXADCValueToMicrovolts(voltageRef, ((uint16_t *)adjustedSampleBuffer)[i]);
    }

    return ADCBuf_STATUS_SUCCESS;
}

/*!
 *  @brief  Function to configure the DMA to automatically transfer ADC output data into a provided array
 *
 *  @pre    ADCBufCC26X2_open() has to be called first.
 *
 *  @pre    There must not currently be a conversion in progress
 *
 *  @param  handle An ADCBufCC26X2 handle returned from ADCBufCC26X2_open()
 *
 *  @param  conversion A pointer to an ADCBuf_Conversion
 *
 */
static void ADCBufCC26X2_configDMA(ADCBuf_Handle handle, ADCBuf_Conversion *conversion) {
    ADCBufCC26X2_Object             *object;

    /* Get the pointer to the object */
    object = handle->object;

    /* Set configure control table entry */
    ADCBufCC26X2_loadDMAControlTableEntry(handle, conversion, true);

    /* If we are operating in continous mode, load the alternate DMA control table data structure */
    if (object->recurrenceMode == ADCBuf_RECURRENCE_MODE_CONTINUOUS) {
        ADCBufCC26X2_loadDMAControlTableEntry(handle, conversion, false);
    }

    /* Enable the channels */
    UDMACC26XX_channelEnable(object->udmaHandle, 1 << UDMA_CHAN_AUX_ADC);

    /* Configure DMA settings in AUX_EVCTL */
    HWREG(AUX_EVCTL_BASE + AUX_EVCTL_O_DMACTL) = AUX_EVCTL_DMACTL_REQ_MODE_SINGLE | AUX_EVCTL_DMACTL_EN | AUX_EVCTL_DMACTL_SEL_FIFO_NOT_EMPTY;

    DebugP_log0("ADCBuf: DMA transfer enabled");
}

/*!
 *  @brief  Function to configure the adc DMA control table entry for basic or ping pong mode
 *
 *  @pre    ADCBufCC26X2_open() has to be called first.
 *
 *  @pre    There must not currently be a conversion in progress
 *
 *  @param  handle An ADCBufCC26X2 handle returned from ADCBufCC26X2_open()
 *
 *  @param  conversion A pointer to an ADCBuf_Conversion
 *
 *  @param  primaryEntry Is this supposed to modify the primary or the alternate control table entry
 *
 */
static void ADCBufCC26X2_loadDMAControlTableEntry(ADCBuf_Handle handle, ADCBuf_Conversion *conversion, bool primaryEntry) {
    ADCBufCC26X2_Object             *object;
    volatile tDMAControlTable       *dmaControlTableEntry;
    uint32_t                        numberOfBytes;

    /* Get the pointer to the object*/
    object = handle->object;

    /* Calculate the number of bytes for the transfer */
    numberOfBytes = (uint16_t)(conversion->samplesRequestedCount) * ADCBufCC26X2_BYTES_PER_SAMPLE;

    /* Set configure control table entry */
    dmaControlTableEntry = primaryEntry ? &dmaADCPriControlTableEntry : &dmaADCAltControlTableEntry;
    dmaControlTableEntry->ui32Control = ((object->recurrenceMode == ADCBuf_RECURRENCE_MODE_ONE_SHOT) ? UDMA_MODE_BASIC : UDMA_MODE_PINGPONG) |
                                        UDMA_SIZE_16  |
                                        UDMA_SRC_INC_NONE |
                                        UDMA_DST_INC_16 |
                                        UDMA_ARB_1 |
                                        UDMACC26XX_SET_TRANSFER_SIZE((uint16_t)conversion->samplesRequestedCount);
    dmaControlTableEntry->pvDstEndAddr = (void *)((uint32_t)(primaryEntry ? conversion->sampleBuffer : conversion->sampleBufferTwo) + numberOfBytes - 1);
    dmaControlTableEntry->pvSrcEndAddr = (void *)(AUX_ANAIF_BASE + AUX_ANAIF_O_ADCFIFO);
}

/*!
 *  @brief  Function to undo all configurations done by the ADC driver
 *
 *  @pre    ADCBuf_open() has to be called first.
 *
 *  @pre    ADCBuf_convert() has to be called first.
 *
 *  @param  handle An ADCBufCC26X2 handle returned from ADCBufCC26X2_open()
 *
 */
static void ADCBufCC26X2_cleanADC(ADCBuf_Handle handle) {
    ADCBufCC26X2_Object            *object;

    /* Get the pointer to the object */
    object = handle->object;

    /* Stop the timer to stop generating triggers */
    GPTimerCC26XX_stop(object->timerHandle);

    /* Set constraints to guarantee operation */
    Power_releaseConstraint(PowerCC26XX_DISALLOW_STANDBY);

    if (object->adcSemaphoreInPossession && !object->keepADCSemaphore) {
        /* Release the ADC semaphore */
        AUXSMPHRelease(AUX_SMPH_2);
        object->adcSemaphoreInPossession = false;
    }

    /* Disable the UDMA channels */
    UDMACC26XX_channelDisable(object->udmaHandle, (1 << UDMA_CHAN_AUX_ADC));

    /* Deallocate pins */
    PIN_close(object->pinHandle);

    /* Disable UDMA mode for ADC */
    HWREG(AUX_EVCTL_BASE + AUX_EVCTL_O_DMACTL) = AUX_EVCTL_DMACTL_REQ_MODE_SINGLE | AUX_EVCTL_DMACTL_SEL_FIFO_NOT_EMPTY ;

    /* Note that the driver is no longer converting */
    object->conversionInProgress = false;
}

/* Return period in timer counts */
static uint32_t ADCBufCC26X2_freqToCounts(uint32_t frequency)
{
    ClockP_FreqHz freq;
    ClockP_getCpuFreq(&freq);

    uint32_t periodCounts = (freq.lo / frequency) - 1;

    return periodCounts;
}

/*!
 *  @brief  Function to acquire the semaphore that arbitrates access to the ADC
 *          between the CM3 and the sensor controller
 *
 *  @pre    ADCBufCC26X2_open() has to be called first.
 *
 *  @pre    There must not currently be a conversion in progress
 *
 *  @param  handle An ADCBufCC26X2 handle returned from ADCBufCC26X2_open()
 *
 */
static bool ADCBufCC26X2_acquireADCSemaphore(ADCBuf_Handle handle) {
    uint32_t                    key;
    bool                        semaphoreAvailable;
    ADCBufCC26X2_Object         *object;

    /* Get the pointer to the object */
    object = handle->object;

    /* Set semaphoreAvailable false at default */
    semaphoreAvailable = false;

    /* Disable interrupts */
    key = HwiP_disable();

    /* Check if ADC is closed or a conversion is in progress */
    if (!(object->isOpen) || object->conversionInProgress) {
        DebugP_log0("ADC: driver must be open and no conversion must be in progress to disable input scaling");
    }
    /* This is a non-blocking call to acquire the ADC semaphore. */
    else if (AUXSMPHTryAcquire(AUX_SMPH_2)) {
        object->adcSemaphoreInPossession = true;
        semaphoreAvailable = true;
    }

    /* Restore interrupts */
    HwiP_restore(key);

    return semaphoreAvailable;
}

/*!
 *  @brief  This function releases the ADC semaphore
 *
 *  @pre    ADCBufCC26X2_open() has to be called first.
 *
 *  @pre    There must not currently be a conversion in progress
 *
 *  @param  handle An ADCBufCC26X2 handle returned from ADCBufCC26X2_open()
 *
 */
static bool ADCBufCC26X2_releaseADCSemaphore(ADCBuf_Handle handle) {
    uint32_t                key;
    bool                    semaphoreReleased;
    ADCBufCC26X2_Object     *object;

    /* Get the pointer to the object */
    object = handle->object;

    /* Set semaphoreReleased true at default */
    semaphoreReleased = true;

    /* Disable interrupts */
    key = HwiP_disable();

    /* Check if ADC is closed or a conversion is in progress */
    if (!(object->isOpen) || object->conversionInProgress) {
        DebugP_log0("ADC: driver must be open and no conversion must be in progress to disable input scaling");
        semaphoreReleased = false;
    }
    else {
        /* Release the ADC semaphore */
        AUXSMPHRelease(AUX_SMPH_2);
        object->adcSemaphoreInPossession = false;
    }

    /* Restore interrupts */
    HwiP_restore(key);

    return semaphoreReleased;
}

/*
 * ======== ADCBufCC26X2_control ========
 */
int_fast16_t ADCBufCC26X2_control(ADCBuf_Handle handle, uint_fast16_t cmd, void * arg) {
    ADCBufCC26X2_Object *object = handle->object;
    int status = ADCBuf_STATUS_ERROR;

    DebugP_assert(handle);

    switch (cmd) {
        case ADCBufCC26X2_CMD_ACQUIRE_ADC_SEMAPHORE:
            if (ADCBufCC26X2_acquireADCSemaphore(handle)) {
                status = ADCBuf_STATUS_SUCCESS;
            }
            break;
        case ADCBufCC26X2_CMD_KEEP_ADC_SEMAPHORE:
            object->keepADCSemaphore = true;
            status = ADCBuf_STATUS_SUCCESS;
            break;
        case ADCBufCC26X2_CMD_KEEP_ADC_SEMAPHORE_DISABLE:
            object->keepADCSemaphore = false;
            status = ADCBuf_STATUS_SUCCESS;
            break;
        case ADCBufCC26X2_CMD_RELEASE_ADC_SEMAPHORE:
            if (ADCBufCC26X2_releaseADCSemaphore(handle)) {
                status = ADCBuf_STATUS_SUCCESS;
            }
            break;
        default:
            status = ADCBuf_STATUS_UNDEFINEDCMD;
            break;
    }
    return status;
}
