/*
 * Copyright (c) 2014-2017, 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>
#include <stdbool.h>
#include <ti/drivers/Power.h>
#include <ti/drivers/power/PowerCC32XX.h>

/*
 * By default disable both asserts and log for this module.
 * This must be done before DebugP.h is included.
 */
#ifndef DebugP_ASSERT_ENABLED
#define DebugP_ASSERT_ENABLED 0
#endif
#ifndef DebugP_LOG_ENABLED
#define DebugP_LOG_ENABLED 0
#endif

#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/uart/UARTCC32XX.h>

/* driverlib header files */
#include <ti/devices/cc32xx/inc/hw_memmap.h>
#include <ti/devices/cc32xx/inc/hw_ocp_shared.h>
#include <ti/devices/cc32xx/inc/hw_ints.h>
#include <ti/devices/cc32xx/inc/hw_types.h>
#include <ti/devices/cc32xx/driverlib/rom.h>
#include <ti/devices/cc32xx/driverlib/rom_map.h>
#include <ti/devices/cc32xx/driverlib/uart.h>
#include <ti/devices/cc32xx/driverlib/pin.h>

/* Pad configuration defines */
#define PAD_CONFIG_BASE (OCP_SHARED_BASE + OCP_SHARED_O_GPIO_PAD_CONFIG_0)
#define PAD_RESET_STATE 0xC61

/* UARTCC32XX functions */
void         UARTCC32XX_close(UART_Handle handle);
int_fast16_t UARTCC32XX_control(UART_Handle handle, uint_fast16_t cmd,
                void *arg);
void         UARTCC32XX_init(UART_Handle handle);
UART_Handle  UARTCC32XX_open(UART_Handle handle, UART_Params *params);
int_fast32_t UARTCC32XX_read(UART_Handle handle, void *buffer, size_t size);
void         UARTCC32XX_readCancel(UART_Handle handle);
int_fast32_t UARTCC32XX_readPolling(UART_Handle handle, void *buffer,
                size_t size);
int_fast32_t UARTCC32XX_write(UART_Handle handle, const void *buffer,
                size_t size);
void         UARTCC32XX_writeCancel(UART_Handle handle);
int_fast32_t UARTCC32XX_writePolling(UART_Handle handle, const void *buffer,
                size_t size);

/* Static functions */
static void errorCallback(UART_Handle handle, uintptr_t error);
static unsigned int getPowerMgrId(unsigned int baseAddr);
static void initHw(UART_Handle handle);
static int  postNotifyFxn(unsigned int eventType, uintptr_t eventArg,
    uintptr_t clientArg);
static void readBlockingTimeout(uintptr_t arg);
static bool readIsrBinaryBlocking(UART_Handle handle);
static bool readIsrBinaryCallback(UART_Handle handle);
static bool readIsrTextBlocking(UART_Handle handle);
static bool readIsrTextCallback(UART_Handle handle);
static void readSemCallback(UART_Handle handle, void *buffer, size_t count);
static int readTaskBlocking(UART_Handle handle);
static int readTaskCallback(UART_Handle handle);
static void releasePowerConstraint(UART_Handle handle);
static void writeData(UART_Handle handle, bool inISR);
static void writeSemCallback(UART_Handle handle, void *buffer, size_t count);

/*
 * Function for checking whether flow control is enabled.
 */
static inline bool isFlowControlEnabled(UARTCC32XX_HWAttrsV1 const  *hwAttrs) {
    return ((hwAttrs->flowControl == UARTCC32XX_FLOWCTRL_HARDWARE) &&
        (hwAttrs->ctsPin != UARTCC32XX_PIN_UNASSIGNED) &&
            (hwAttrs->rtsPin != UARTCC32XX_PIN_UNASSIGNED));
}

/* UART function table for UARTCC32XX implementation */
const UART_FxnTable UARTCC32XX_fxnTable = {
    UARTCC32XX_close,
    UARTCC32XX_control,
    UARTCC32XX_init,
    UARTCC32XX_open,
    UARTCC32XX_read,
    UARTCC32XX_readPolling,
    UARTCC32XX_readCancel,
    UARTCC32XX_write,
    UARTCC32XX_writePolling,
    UARTCC32XX_writeCancel
};

static const uint32_t dataLength[] = {
    UART_CONFIG_WLEN_5,     /* UART_LEN_5 */
    UART_CONFIG_WLEN_6,     /* UART_LEN_6 */
    UART_CONFIG_WLEN_7,     /* UART_LEN_7 */
    UART_CONFIG_WLEN_8      /* UART_LEN_8 */
};

static const uint32_t stopBits[] = {
    UART_CONFIG_STOP_ONE,   /* UART_STOP_ONE */
    UART_CONFIG_STOP_TWO    /* UART_STOP_TWO */
};

static const uint32_t parityType[] = {
    UART_CONFIG_PAR_NONE,   /* UART_PAR_NONE */
    UART_CONFIG_PAR_EVEN,   /* UART_PAR_EVEN */
    UART_CONFIG_PAR_ODD,    /* UART_PAR_ODD */
    UART_CONFIG_PAR_ZERO,   /* UART_PAR_ZERO */
    UART_CONFIG_PAR_ONE     /* UART_PAR_ONE */
};

/*
 *  ======== staticFxnTable ========
 *  This is a function lookup table to simplify the UART driver modes.
 */
static const UARTCC32XX_FxnSet staticFxnTable[2][2] = {
    {/* UART_MODE_BLOCKING */
        {/* UART_DATA_BINARY */
            .readIsrFxn  = readIsrBinaryBlocking,
            .readTaskFxn = readTaskBlocking
        },
        {/* UART_DATA_TEXT */
            .readIsrFxn  = readIsrTextBlocking,
            .readTaskFxn = readTaskBlocking
        }
    },
    {/* UART_MODE_CALLBACK */
        {/* UART_DATA_BINARY */
            .readIsrFxn  = readIsrBinaryCallback,
            .readTaskFxn = readTaskCallback

        },
        {/* UART_DATA_TEXT */
            .readIsrFxn  = readIsrTextCallback,
            .readTaskFxn = readTaskCallback,
        }
    }
};

/*
 *  ======== UARTCC32XX_close ========
 */
void UARTCC32XX_close(UART_Handle handle)
{
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    uint_fast16_t               retVal;
    uint32_t                    padRegister;

    /* Disable UART and interrupts. */
    MAP_UARTIntDisable(hwAttrs->baseAddr, UART_INT_TX | UART_INT_RX |
        UART_INT_RT | UART_INT_OE | UART_INT_BE | UART_INT_PE | UART_INT_FE);
    MAP_UARTDisable(hwAttrs->baseAddr);

    if (object->hwiHandle) {
        HwiP_delete(object->hwiHandle);
    }
    if (object->writeSem) {
        SemaphoreP_delete(object->writeSem);
    }
    if (object->readSem) {
        SemaphoreP_delete(object->readSem);
    }
    if (object->timeoutClk) {
        ClockP_delete(object->timeoutClk);
    }

    if (object->state.txEnabled) {
        retVal = Power_releaseConstraint(PowerCC32XX_DISALLOW_LPDS);
        DebugP_assert(retVal == Power_SOK);
        object->state.txEnabled = false;
    }

    Power_unregisterNotify(&object->postNotify);
    if (object->state.rxEnabled) {
        retVal = Power_releaseConstraint(PowerCC32XX_DISALLOW_LPDS);
        DebugP_assert(retVal == Power_SOK);
        object->state.rxEnabled = false;

        DebugP_log1("UART:(%p) UART_close released read power constraint",
            hwAttrs->baseAddr);
    }
    Power_releaseDependency(object->powerMgrId);

    if (object->txPin != (uint16_t)-1) {
        PowerCC32XX_restoreParkState((PowerCC32XX_Pin)object->txPin,
            object->prevParkTX);
        object->txPin = (uint16_t)-1;
    }

    if (object->rtsPin != (uint16_t)-1) {
        PowerCC32XX_restoreParkState((PowerCC32XX_Pin)object->rtsPin,
            object->prevParkRTS);
        object->rtsPin = (uint16_t)-1;
    }

    /* Restore pin pads to their reset states */
    padRegister = (PinToPadGet((hwAttrs->rxPin) & 0xff)<<2) + PAD_CONFIG_BASE;
    HWREG(padRegister) = PAD_RESET_STATE;
    padRegister = (PinToPadGet((hwAttrs->txPin) & 0xff)<<2) + PAD_CONFIG_BASE;
    HWREG(padRegister) = PAD_RESET_STATE;
    if (isFlowControlEnabled(hwAttrs)) {
        padRegister = (PinToPadGet((hwAttrs->ctsPin) & 0xff)<<2)
            + PAD_CONFIG_BASE;
        HWREG(padRegister) = PAD_RESET_STATE;
        padRegister = (PinToPadGet((hwAttrs->rtsPin) & 0xff)<<2)
            + PAD_CONFIG_BASE;
        HWREG(padRegister) = PAD_RESET_STATE;
    }

    object->state.opened = false;

    DebugP_log1("UART:(%p) closed", hwAttrs->baseAddr);

    (void)retVal;
}

/*
 *  ======== UARTCC32XX_control ========
 *  @pre    Function assumes that the handle is not NULL
 */
int_fast16_t UARTCC32XX_control(UART_Handle handle, uint_fast16_t cmd,
        void *arg)
{
    UARTCC32XX_Object          *object = handle->object;
    UARTCC32XX_HWAttrsV1 const *hwAttrs = handle->hwAttrs;
    unsigned char               data;
    int                         bufferCount;
    uint_fast16_t               retVal;

    bufferCount = RingBuf_peek(&object->ringBuffer, &data);

    switch (cmd) {
        /* Common UART CMDs */
        case (UART_CMD_PEEK):
            *(int *)arg = (bufferCount) ? data : UART_ERROR;
            DebugP_log2("UART:(%p) UART_CMD_PEEK: %d", hwAttrs->baseAddr,
                *(uintptr_t*)arg);
            return (UART_STATUS_SUCCESS);

        case (UART_CMD_ISAVAILABLE):
            *(bool *)arg = (bufferCount != 0);
            DebugP_log2("UART:(%p) UART_CMD_ISAVAILABLE: %d", hwAttrs->baseAddr,
                *(uintptr_t*)arg);
            return (UART_STATUS_SUCCESS);

        case (UART_CMD_GETRXCOUNT):
            *(int *)arg = bufferCount;
            DebugP_log2("UART:(%p) UART_CMD_GETRXCOUNT: %d", hwAttrs->baseAddr,
                *(uintptr_t*)arg);
            return (UART_STATUS_SUCCESS);

        case (UART_CMD_RXENABLE):
            if (!object->state.rxEnabled) {
                Power_setConstraint(PowerCC32XX_DISALLOW_LPDS);
                MAP_UARTIntEnable(hwAttrs->baseAddr, UART_INT_RX | UART_INT_RT);
                object->state.rxEnabled = true;
                DebugP_log1("UART:(%p) UART_CMD_RXENABLE: Enabled",
                    hwAttrs->baseAddr);
                DebugP_log1("UART:(%p) UART_control set read power constraint",
                    hwAttrs->baseAddr);
                return (UART_STATUS_SUCCESS);
            }
            DebugP_log1("UART:(%p) UART_CMD_RXENABLE: Already enabled",
                hwAttrs->baseAddr);
            return (UART_STATUS_ERROR);

        case (UART_CMD_RXDISABLE):
            if (object->state.rxEnabled) {
                MAP_UARTIntDisable(hwAttrs->baseAddr, UART_INT_RX | UART_INT_RT);

                retVal = Power_releaseConstraint(PowerCC32XX_DISALLOW_LPDS);

                DebugP_assert(retVal == Power_SOK);
                object->state.rxEnabled = false;
                DebugP_log1("UART:(%p) UART_CMD_RXDISABLE: Disabled",
                    hwAttrs->baseAddr);
                DebugP_log1("UART:(%p) UART_control released read power "
                    "constraint", hwAttrs->baseAddr);

                (void)retVal;
                return (UART_STATUS_SUCCESS);
            }
            DebugP_log1("UART:(%p) UART_CMD_RXDISABLE: Already disabled",
                hwAttrs->baseAddr);
            return (UART_STATUS_ERROR);

        /* Specific UART CMDs */
        case (UARTCC32XX_CMD_IS_BUSY):
            *(bool *)arg = MAP_UARTBusy(hwAttrs->baseAddr);
            return (UART_STATUS_SUCCESS);

        case (UARTCC32XX_CMD_IS_RX_DATA_AVAILABLE):
            *(bool *)arg = MAP_UARTCharsAvail(hwAttrs->baseAddr);
            return (UART_STATUS_SUCCESS);

        case (UARTCC32XX_CMD_IS_TX_SPACE_AVAILABLE):
            *(bool *)arg = MAP_UARTSpaceAvail(hwAttrs->baseAddr);
            return (UART_STATUS_SUCCESS);

        default:
            DebugP_log2("UART:(%p) UART CMD undefined: %d",
                hwAttrs->baseAddr, cmd);
            return (UART_STATUS_UNDEFINEDCMD);
    }
}

/*
 *  ======== UARTCC32XX_hwiIntFxn ========
 *  Hwi function that processes UART interrupts.
 *
 *  @param(arg)         The UART_Handle for this Hwi.
 */
static void UARTCC32XX_hwiIntFxn(uintptr_t arg)
{
    uint32_t                     status;
    UARTCC32XX_Object           *object = ((UART_Handle)arg)->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = ((UART_Handle)arg)->hwAttrs;
    uint32_t                     rxErrors;

    /* Clear interrupts */
    status = MAP_UARTIntStatus(hwAttrs->baseAddr, true);
    MAP_UARTIntClear(hwAttrs->baseAddr, status);

    if (status & (UART_INT_RX | UART_INT_RT | UART_INT_OE | UART_INT_BE |
            UART_INT_PE | UART_INT_FE)) {
        object->readFxns.readIsrFxn((UART_Handle)arg);
    }

    /* Reading the data from the FIFO doesn't mean we caught an overrrun! */
    rxErrors = MAP_UARTRxErrorGet(hwAttrs->baseAddr);
    if (rxErrors) {
        MAP_UARTRxErrorClear(hwAttrs->baseAddr);
        errorCallback((UART_Handle)arg, rxErrors);
    }

    if (status & UART_INT_TX) {
        writeData((UART_Handle)arg, true);
    }
}

/*
 *  ======== UARTCC32XX_init ========
 */
void UARTCC32XX_init(UART_Handle handle)
{
}

/*
 *  ======== UARTCC32XX_open ========
 */
UART_Handle UARTCC32XX_open(UART_Handle handle, UART_Params *params)
{
    uintptr_t                    key;
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    SemaphoreP_Params            semParams;
    HwiP_Params                  hwiParams;
    ClockP_Params                clockParams;
    uint16_t                     pin;
    uint16_t                     mode;

    /* Check for callback when in UART_MODE_CALLBACK */
    DebugP_assert((params->readMode != UART_MODE_CALLBACK) ||
                  (params->readCallback != NULL));
    DebugP_assert((params->writeMode != UART_MODE_CALLBACK) ||
                  (params->writeCallback != NULL));

    key = HwiP_disable();

    if (object->state.opened == true) {
        HwiP_restore(key);

        DebugP_log1("UART:(%p) already in use.", hwAttrs->baseAddr);
        return (NULL);
    }
    object->state.opened = true;

    HwiP_restore(key);

    object->state.readMode       = params->readMode;
    object->state.writeMode      = params->writeMode;
    object->state.readReturnMode = params->readReturnMode;
    object->state.readDataMode   = params->readDataMode;
    object->state.writeDataMode  = params->writeDataMode;
    object->state.readEcho       = params->readEcho;
    object->readTimeout          = params->readTimeout;
    object->writeTimeout         = params->writeTimeout;
    object->readCallback         = params->readCallback;
    object->writeCallback        = params->writeCallback;
    object->baudRate             = params->baudRate;
    object->stopBits             = params->stopBits;
    object->dataLength           = params->dataLength;
    object->parityType           = params->parityType;
    object->readFxns =
        staticFxnTable[object->state.readMode][object->state.readDataMode];

    /* Set UART variables to defaults. */
    object->writeBuf             = NULL;
    object->readBuf              = NULL;
    object->writeCount           = 0;
    object->readCount            = 0;
    object->writeSize            = 0;
    object->readSize             = 0;
    object->state.txEnabled      = false;
    object->txPin                = (uint16_t)-1;
    object->rtsPin               = (uint16_t)-1;

    /*
     *  Compute appropriate wait time for FIFO to empty out
     *       Total bits
     *          - 16 + 1: TX FIFO size + 1
     *          - 1 bit for start bit
     *          - 5+(object->dataLength) for total data length (5,6,7,or 8 bits)
     *          - 1+(object->stopBits) for total stop bit count (1 or 2 bits)
     *       mutiplied by 1000000 (bits / second)
     *       divided by object->baudRate (bits / second)
     *       divided by ClockP_getSystemTickPeriod() (bits (us) / SystemTick)
     *       In case of high baud rates, this tick count may be less than 1
     *          system ticks. Add 2 system ticks so that we can guarantee at
     *          least 1 system tick.
     */
    object->writeEmptyClkTimeout = ((16 + 1) * (1 + 5 + object->dataLength +
        1 + object->stopBits) * 1000000) / object->baudRate;
    object->writeEmptyClkTimeout /= ClockP_getSystemTickPeriod();
    object->writeEmptyClkTimeout += 2;

    RingBuf_construct(&object->ringBuffer, hwAttrs->ringBufPtr,
        hwAttrs->ringBufSize);

    /* Get the Power resource Id from the base address */
    object->powerMgrId = getPowerMgrId(hwAttrs->baseAddr);
    if (object->powerMgrId == (unsigned int)-1) {
        DebugP_log1("UART:(%p) Failed to determine Power resource id",
                hwAttrs->baseAddr);
        return (NULL);
    }

    /*
     *  Register power dependency. Keeps the clock running in SLP
     *  and DSLP modes.
     */
    Power_setDependency(object->powerMgrId);

    pin = (hwAttrs->rxPin) & 0xff;
    mode = (hwAttrs->rxPin >> 8) & 0xff;

    MAP_PinTypeUART((unsigned long)pin, (unsigned long)mode);

    pin = (hwAttrs->txPin) & 0xff;
    mode = (hwAttrs->txPin >> 8) & 0xff;

    MAP_PinTypeUART((unsigned long)pin, (unsigned long)mode);

    /*
     * Read and save TX pin park state; set to "don't park" while UART is
     * open as device default is logic '1' during LPDS
     */
    object->prevParkTX =
        (PowerCC32XX_ParkState) PowerCC32XX_getParkState((PowerCC32XX_Pin)pin);
    PowerCC32XX_setParkState((PowerCC32XX_Pin)pin, ~1);
    object->txPin = pin;

    if (isFlowControlEnabled(hwAttrs)) {
        pin = (hwAttrs->ctsPin) & 0xff;
        mode = (hwAttrs->ctsPin >> 8) & 0xff;
        MAP_PinTypeUART((unsigned long)pin, (unsigned long)mode);

        pin = (hwAttrs->rtsPin) & 0xff;
        mode = (hwAttrs->rtsPin >> 8) & 0xff;
        MAP_PinTypeUART((unsigned long)pin, (unsigned long)mode);

        /*
         * Read and save RTS pin park state; set to "don't park" while UART is
         * open as device default is logic '1' during LPDS
         */
        object->prevParkRTS = (PowerCC32XX_ParkState)PowerCC32XX_getParkState(
            (PowerCC32XX_Pin)pin);
        PowerCC32XX_setParkState((PowerCC32XX_Pin)pin, ~1);
        object->rtsPin = pin;

        /* Flow control will be enabled in initHw() */
    }

    Power_registerNotify(&object->postNotify, PowerCC32XX_AWAKE_LPDS,
        postNotifyFxn, (uintptr_t)handle);

    Power_setConstraint(PowerCC32XX_DISALLOW_LPDS);
    object->state.rxEnabled = true;

    DebugP_log1("UART:(%p) UART_open set read power constraint",
        hwAttrs->baseAddr);

    HwiP_Params_init(&hwiParams);

    hwiParams.arg = (uintptr_t)handle;
    hwiParams.priority = hwAttrs->intPriority;
    object->hwiHandle = HwiP_create(hwAttrs->intNum, UARTCC32XX_hwiIntFxn,
        &hwiParams);
    if (object->hwiHandle == NULL) {
        DebugP_log1("UART:(%p) HwiP_create() failed", hwAttrs->baseAddr);
        UARTCC32XX_close(handle);
        return (NULL);
    }

    SemaphoreP_Params_init(&semParams);
    semParams.mode = SemaphoreP_Mode_BINARY;

    /* If write mode is blocking create a semaphore and set callback. */
    if (object->state.writeMode == UART_MODE_BLOCKING) {
        if ((object->writeSem = SemaphoreP_create(0, &semParams)) == NULL) {
            DebugP_log1("UART:(%p) SemaphoreP_create() failed.",
                hwAttrs->baseAddr);
            UARTCC32XX_close(handle);
            return (NULL);
        }
        object->writeCallback = &writeSemCallback;
    }

    ClockP_Params_init(&clockParams);
    clockParams.arg = (uintptr_t)handle;

    /* If read mode is blocking create a semaphore and set callback. */
    if (object->state.readMode == UART_MODE_BLOCKING) {
        object->readSem = SemaphoreP_create(0, &semParams);
        if (object->readSem == NULL) {
            DebugP_log1("UART:(%p) SemaphoreP_create() failed.",
                hwAttrs->baseAddr);
            UARTCC32XX_close(handle);
            return (NULL);
        }
        object->readCallback = &readSemCallback;
        object->timeoutClk =
            ClockP_create((ClockP_Fxn)&readBlockingTimeout,
                    0 /* timeout */, &(clockParams));
        if (object->timeoutClk == NULL) {
            DebugP_log1("UART:(%p) ClockP_create() failed.",
                hwAttrs->baseAddr);
            UARTCC32XX_close(handle);
            return (NULL);
        }
    }
    else {
        object->state.drainByISR = false;
    }

    /* Initialize the hardware */
    initHw(handle);

    DebugP_log1("UART:(%p) opened", hwAttrs->baseAddr);

    /* Return the handle */
    return (handle);
}

/*
 *  ======== UARTCC32XX_read ========
 */
int_fast32_t UARTCC32XX_read(UART_Handle handle, void *buffer, size_t size)
{
    uintptr_t           key;
    UARTCC32XX_Object  *object = handle->object;

    key = HwiP_disable();

    if ((object->state.readMode == UART_MODE_CALLBACK) && object->readSize) {
        HwiP_restore(key);

        DebugP_log1("UART:(%p) Could not read data, uart in use.",
                ((UARTCC32XX_HWAttrsV1 const *)(handle->hwAttrs))->baseAddr);
        return (UART_ERROR);
    }

    /* Save the data to be read and restore interrupts. */
    object->readBuf = buffer;
    object->readSize = size;
    object->readCount = size;

    HwiP_restore(key);

    return (object->readFxns.readTaskFxn(handle));
}

/*
 *  ======== UARTCC32XX_readCancel ========
 */
void UARTCC32XX_readCancel(UART_Handle handle)
{
    uintptr_t             key;
    UARTCC32XX_Object    *object = handle->object;

    if ((object->state.readMode != UART_MODE_CALLBACK) ||
        (object->readSize == 0)) {
        return;
    }

    key = HwiP_disable();

    object->state.drainByISR = false;
    /*
     * Indicate that what we've currently received is what we asked for so that
     * the existing logic handles the completion.
     */
    object->readSize -= object->readCount;
    object->readCount = 0;

    HwiP_restore(key);

    object->readFxns.readTaskFxn(handle);
}

/*
 *  ======== UARTCC32XX_readPolling ========
 */
int_fast32_t UARTCC32XX_readPolling(UART_Handle handle, void *buf, size_t size)
{
    int32_t                      count = 0;
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    unsigned char               *buffer = (unsigned char *)buf;

    /* Read characters. */
    while (size) {
        /* Grab data from the RingBuf before getting it from the RX data reg */
        MAP_UARTIntDisable(hwAttrs->baseAddr, UART_INT_RX | UART_INT_RT);
        if (RingBuf_get(&object->ringBuffer, buffer) == -1) {
            *buffer = MAP_UARTCharGet(hwAttrs->baseAddr);
        }
        if (object->state.rxEnabled) {
            MAP_UARTIntEnable(hwAttrs->baseAddr, UART_INT_RX | UART_INT_RT);
        }

        DebugP_log2("UART:(%p) Read character 0x%x", hwAttrs->baseAddr,
            *buffer);
        count++;
        size--;

        if (object->state.readDataMode == UART_DATA_TEXT && *buffer == '\r') {
            /* Echo character if enabled. */
            if (object->state.readEcho) {
                MAP_UARTCharPut(hwAttrs->baseAddr, '\r');
            }
            *buffer = '\n';
        }

        /* Echo character if enabled. */
        if (object->state.readDataMode == UART_DATA_TEXT &&
                object->state.readEcho) {
            MAP_UARTCharPut(hwAttrs->baseAddr, *buffer);
        }

        /* If read return mode is newline, finish if a newline was received. */
        if (object->state.readDataMode == UART_DATA_TEXT &&
                object->state.readReturnMode == UART_RETURN_NEWLINE &&
                *buffer == '\n') {
            return (count);
        }

        buffer++;
    }

    DebugP_log2("UART:(%p) Read polling finished, %d bytes read",
        hwAttrs->baseAddr, count);

    return (count);
}

/*
 *  ======== UARTCC32XX_write ========
 */
int_fast32_t UARTCC32XX_write(UART_Handle handle, const void *buffer,
        size_t size)
{
    unsigned int                 key;
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;

    if (!size) {
        return 0;
    }

    key = HwiP_disable();

    if (object->writeCount) {
        HwiP_restore(key);
        DebugP_log1("UART:(%p) Could not write data, uart in use.",
            hwAttrs->baseAddr);

        return (UART_ERROR);
    }

    /* Save the data to be written and restore interrupts. */
    object->writeBuf = buffer;
    object->writeSize = size;
    object->writeCount = size;

    if (object->state.txEnabled == false){
        object->state.txEnabled = true;
        Power_setConstraint(PowerCC32XX_DISALLOW_LPDS);
        DebugP_log1("UART:(%p) UART_write set write power constraint",
            hwAttrs->baseAddr);
    }

    HwiP_restore(key);

    if (!(MAP_UARTIntStatus(hwAttrs->baseAddr, false) & UART_INT_TX)) {
        /*
         *  Start the transfer going if the raw interrupt status TX bit
         *  is 0.  This will cause the ISR to fire when we enable
         *  UART_INT_TX.  If the RIS TX bit is not cleared, we don't
         *  need to call writeData(), since the ISR will fire once we
         *  enable the interrupt, causing the transfer to start.
         */
        writeData(handle, false);
    }
    if (object->writeCount) {
        MAP_UARTTxIntModeSet(hwAttrs->baseAddr, UART_TXINT_MODE_FIFO);
        MAP_UARTIntEnable(hwAttrs->baseAddr, UART_INT_TX);
    }

    /* If writeMode is blocking, block and get the state. */
    if (object->state.writeMode == UART_MODE_BLOCKING) {
        /* Pend on semaphore and wait for Hwi to finish. */
        if (SemaphoreP_OK != SemaphoreP_pend(object->writeSem,
                    object->writeTimeout)) {
            /* Semaphore timed out, make the write empty and log the write. */
            MAP_UARTIntDisable(hwAttrs->baseAddr, UART_INT_TX);
            MAP_UARTIntClear(hwAttrs->baseAddr, UART_INT_TX);
            object->writeCount = 0;

            DebugP_log2("UART:(%p) Write timed out, %d bytes written",
                hwAttrs->baseAddr, object->writeCount);
        }
        return (object->writeSize - object->writeCount);
    }

    return (0);
}

/*
 *  ======== UARTCC32XX_writeCancel ========
 */
void UARTCC32XX_writeCancel(UART_Handle handle)
{
    uintptr_t                    key;
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    unsigned int                 written;
    uint_fast16_t                retVal;

    key = HwiP_disable();

    /* Return if there is no write. */
    if (!object->writeCount) {
        HwiP_restore(key);
        return;
    }

    /* Set size = 0 to prevent writing and restore interrupts. */
    written = object->writeCount;
    object->writeCount = 0;
    MAP_UARTIntDisable(hwAttrs->baseAddr, UART_INT_TX);
    MAP_UARTIntClear(hwAttrs->baseAddr, UART_INT_TX);

    retVal = Power_releaseConstraint(PowerCC32XX_DISALLOW_LPDS);
    DebugP_assert(retVal == Power_SOK);
    object->state.txEnabled = false;

    HwiP_restore(key);

    /* Reset the write buffer so we can pass it back */
    object->writeCallback(handle, (void *)object->writeBuf,
        object->writeSize - written);

    DebugP_log2("UART:(%p) Write canceled, %d bytes written",
        hwAttrs->baseAddr, object->writeSize - written);

    (void)retVal;
}

/*
 *  ======== UARTCC32XX_writePolling ========
 */
int_fast32_t UARTCC32XX_writePolling(UART_Handle handle, const void *buf,
        size_t size)
{
    int32_t                    count = 0;
    UARTCC32XX_Object         *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    unsigned char             *buffer = (unsigned char *)buf;

    /* Write characters. */
    while (size) {
        if (object->state.writeDataMode == UART_DATA_TEXT && *buffer == '\n') {
            MAP_UARTCharPut(hwAttrs->baseAddr, '\r');
            count++;
        }
        MAP_UARTCharPut(hwAttrs->baseAddr, *buffer);

        DebugP_log2("UART:(%p) Wrote character 0x%x", hwAttrs->baseAddr,
            *buffer);
        buffer++;
        count++;
        size--;
    }

    while (MAP_UARTBusy(hwAttrs->baseAddr)) {
        ;
    }

    DebugP_log2("UART:(%p) Write polling finished, %d bytes written",
        hwAttrs->baseAddr, count);

    return (count);
}

/*
 *  ======== errorCallback ========
 *  Generic log function for when unexpected events occur.
 */
static void errorCallback(UART_Handle handle, uintptr_t error)
{
    if (error & UART_RXERROR_OVERRUN) {
        DebugP_log1("UART:(%p): OVERRUN ERROR",
            ((UARTCC32XX_HWAttrsV1 const *)handle->hwAttrs)->baseAddr);
    }
    if (error & UART_RXERROR_BREAK) {
        DebugP_log1("UART:(%p): BREAK ERROR",
            ((UARTCC32XX_HWAttrsV1 const *)handle->hwAttrs)->baseAddr);
    }
    if (error & UART_RXERROR_PARITY) {
        DebugP_log1("UART:(%p): PARITY ERROR",
            ((UARTCC32XX_HWAttrsV1 const *)handle->hwAttrs)->baseAddr);
    }
    if (error & UART_RXERROR_FRAMING) {
        DebugP_log1("UART:(%p): FRAMING ERROR",
            ((UARTCC32XX_HWAttrsV1 const *)handle->hwAttrs)->baseAddr);
    }
}

/*
 *  ======== getPowerMgrId ========
 */
static unsigned int getPowerMgrId(unsigned int baseAddr)
{
    switch (baseAddr) {
        case UARTA0_BASE:
            return (PowerCC32XX_PERIPH_UARTA0);
        case UARTA1_BASE:
            return (PowerCC32XX_PERIPH_UARTA1);
        default:
            return ((unsigned int)-1);
    }
}

/*
 *  ======== initHw ========
 */
static void initHw(UART_Handle handle)
{
    ClockP_FreqHz                 freq;
    UARTCC32XX_Object            *object = handle->object;
    UARTCC32XX_HWAttrsV1 const   *hwAttrs = handle->hwAttrs;

    /* Enable UART and its interrupt. */
    MAP_UARTIntClear(hwAttrs->baseAddr, UART_INT_TX | UART_INT_RX |
            UART_INT_RT);
    MAP_UARTEnable(hwAttrs->baseAddr);

    /* Set the FIFO level to 7/8 empty and 4/8 full. */
    MAP_UARTFIFOLevelSet(hwAttrs->baseAddr, UART_FIFO_TX1_8, UART_FIFO_RX1_8);

    if (isFlowControlEnabled(hwAttrs)) {
        /* Set flow control */
        MAP_UARTFlowControlSet(hwAttrs->baseAddr,
                UART_FLOWCONTROL_TX | UART_FLOWCONTROL_RX);
    }
    else {
        MAP_UARTFlowControlSet(hwAttrs->baseAddr, UART_FLOWCONTROL_NONE);
    }

    ClockP_getCpuFreq(&freq);
    MAP_UARTConfigSetExpClk(hwAttrs->baseAddr, freq.lo, object->baudRate,
        dataLength[object->dataLength] | stopBits[object->stopBits] |
        parityType[object->parityType]);

    MAP_UARTIntEnable(hwAttrs->baseAddr, UART_INT_RX | UART_INT_RT |
        UART_INT_OE | UART_INT_BE | UART_INT_PE | UART_INT_FE);
}

/*
 *  ======== postNotifyFxn ========
 *  Called by Power module when waking up from LPDS.
 */
static int postNotifyFxn(unsigned int eventType, uintptr_t eventArg,
        uintptr_t clientArg)
{
    initHw((UART_Handle)clientArg);

    return (Power_NOTIFYDONE);
}

/*
 *  ======== readBlockingTimeout ========
 */
static void readBlockingTimeout(uintptr_t arg)
{
    UARTCC32XX_Object *object = ((UART_Handle)arg)->object;
    object->state.bufTimeout = true;
    SemaphoreP_post(object->readSem);
}

/*
 *  ======== readIsrBinaryBlocking ========
 *  Function that is called by the ISR
 */
static bool readIsrBinaryBlocking(UART_Handle handle)
{
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    int                          readIn;

    readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    while (readIn != -1) {
        if (readIn > 0xFF) {
            errorCallback(handle, (readIn >> 8) & 0xF);
            MAP_UARTRxErrorClear(hwAttrs->baseAddr);
            return (false);
        }

        if (RingBuf_put(&object->ringBuffer, (unsigned char)readIn) == -1) {
            DebugP_log1("UART:(%p) Ring buffer full!!", hwAttrs->baseAddr);
            return (false);
        }
        DebugP_log2("UART:(%p) buffered '0x%02x'", hwAttrs->baseAddr,
            (unsigned char)readIn);

        if (object->state.callCallback) {
            object->state.callCallback = false;
            object->readCallback(handle, NULL, 0);
        }
        readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    }
    return (true);
}

/*
 *  ======== readIsrBinaryCallback ========
 */
static bool readIsrBinaryCallback(UART_Handle handle)
{
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    int                          readIn;
    bool                         ret = true;

    readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    while (readIn != -1) {
        if (readIn > 0xFF) {
            errorCallback(handle, (readIn >> 8) & 0xF);
            MAP_UARTRxErrorClear(hwAttrs->baseAddr);
            ret = false;
            break;
        }

        if (RingBuf_put(&object->ringBuffer, (unsigned char)readIn) == -1) {
            DebugP_log1("UART:(%p) Ring buffer full!!", hwAttrs->baseAddr);
            ret = false;
            break;
        }
        DebugP_log2("UART:(%p) buffered '0x%02x'", hwAttrs->baseAddr,
            (unsigned char)readIn);
        readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    }

    /*
     * Check and see if a UART_read in callback mode told use to continue
     * servicing the user buffer...
     */
    if (object->state.drainByISR) {
        readTaskCallback(handle);
    }

    return (ret);
}

/*
 *  ======== readIsrTextBlocking ========
 */
static bool readIsrTextBlocking(UART_Handle handle)
{
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    int                          readIn;

    readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    while (readIn != -1) {
        if (readIn > 0xFF) {
            errorCallback(handle, (readIn >> 8) & 0xF);
            MAP_UARTRxErrorClear(hwAttrs->baseAddr);
            return (false);
        }

        if (readIn == '\r') {
            /* Echo character if enabled. */
            if (object->state.readEcho) {
                MAP_UARTCharPut(hwAttrs->baseAddr, '\r');
            }
            readIn = '\n';
        }
        if (RingBuf_put(&object->ringBuffer, (unsigned char)readIn) == -1) {
            DebugP_log1("UART:(%p) Ring buffer full!!", hwAttrs->baseAddr);
            return (false);
        }
        DebugP_log2("UART:(%p) buffered '0x%02x'", hwAttrs->baseAddr,
            (unsigned char)readIn);

        if (object->state.readEcho) {
            MAP_UARTCharPut(hwAttrs->baseAddr, (unsigned char)readIn);
        }
        if (object->state.callCallback) {
            object->state.callCallback = false;
            object->readCallback(handle, NULL, 0);
        }
        readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    }
    return (true);
}

/*
 *  ======== readIsrTextCallback ========
 */
static bool readIsrTextCallback(UART_Handle handle)
{
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    int                          readIn;
    bool                         ret = true;

    readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    while (readIn != -1) {
        if (readIn > 0xFF) {
            errorCallback(handle, (readIn >> 8) & 0xF);
            MAP_UARTRxErrorClear(hwAttrs->baseAddr);
            ret = false;
            break;
        }

        if (readIn == '\r') {
            /* Echo character if enabled. */
            if (object->state.readEcho) {
                MAP_UARTCharPut(hwAttrs->baseAddr, '\r');
            }
            readIn = '\n';
        }
        if (RingBuf_put(&object->ringBuffer, (unsigned char)readIn) == -1) {
            DebugP_log1("UART:(%p) Ring buffer full!!", hwAttrs->baseAddr);
            ret = false;
            break;
        }
        DebugP_log2("UART:(%p) buffered '0x%02x'", hwAttrs->baseAddr,
            (unsigned char)readIn);

        if (object->state.readEcho) {
            MAP_UARTCharPut(hwAttrs->baseAddr, (unsigned char)readIn);
        }
        readIn = MAP_UARTCharGetNonBlocking(hwAttrs->baseAddr);
    }

    /*
     * Check and see if a UART_read in callback mode told use to continue
     * servicing the user buffer...
     */
    if (object->state.drainByISR) {
        readTaskCallback(handle);
    }

    return (ret);
}

/*
 *  ======== readSemCallback ========
 *  Simple callback to post a semaphore for the blocking mode.
 */
static void readSemCallback(UART_Handle handle, void *buffer, size_t count)
{
    UARTCC32XX_Object *object = handle->object;

    SemaphoreP_post(object->readSem);
}

/*
 *  ======== readTaskBlocking ========
 */
static int readTaskBlocking(UART_Handle handle)
{
    unsigned char              readIn;
    uintptr_t                  key;
    UARTCC32XX_Object         *object = handle->object;
    unsigned char             *buffer = object->readBuf;

    object->state.bufTimeout = false;
    /*
     * It is possible for the object->timeoutClk and the callback function to
     * have posted the object->readSem Semaphore from the previous UART_read
     * call (if the code below didn't get to stop the clock object in time).
     * To clear this, we simply do a NO_WAIT pend on (binary) object->readSem
     * so that it resets the Semaphore count.
     */
    SemaphoreP_pend(object->readSem, SemaphoreP_NO_WAIT);

    if ((object->readTimeout != 0) &&
                (object->readTimeout != UART_WAIT_FOREVER)) {
        ClockP_setTimeout(object->timeoutClk, object->readTimeout);
        ClockP_start(object->timeoutClk);
    }

    while (object->readCount) {
        key = HwiP_disable();

        if (RingBuf_get(&object->ringBuffer, &readIn) < 0) {
            object->state.callCallback = true;
            HwiP_restore(key);

            if (object->readTimeout == 0) {
                break;
            }

            SemaphoreP_pend(object->readSem, SemaphoreP_WAIT_FOREVER);
            if (object->state.bufTimeout == true) {
                break;
            }
            RingBuf_get(&object->ringBuffer, &readIn);
        }
        else {
            HwiP_restore(key);
        }

        DebugP_log2("UART:(%p) read '0x%02x'",
            ((UARTCC32XX_HWAttrsV1 const *)(handle->hwAttrs))->baseAddr,
            (unsigned char)readIn);

        *buffer = readIn;
        buffer++;
        /* In blocking mode, readCount doesn't not need a lock */
        object->readCount--;

        if (object->state.readDataMode == UART_DATA_TEXT &&
                object->state.readReturnMode == UART_RETURN_NEWLINE &&
                readIn == '\n') {
            break;
        }
    }

    ClockP_stop(object->timeoutClk);
    return (object->readSize - object->readCount);
}

/*
 *  ======== readTaskCallback ========
 *  This function is called the first time by the UART_read task and tries to
 *  get all the data it can get from the ringBuffer. If it finished, it will
 *  perform the user supplied callback. If it didn't finish, the ISR must handle
 *  the remaining data. By setting the drainByISR flag, the UART_read function
 *  handed over the responsibility to get the remaining data to the ISR.
 */
static int readTaskCallback(UART_Handle handle)
{
    unsigned int               key;
    UARTCC32XX_Object         *object = handle->object;
    unsigned char              readIn;
    unsigned char             *bufferEnd;
    bool                       makeCallback = false;
    size_t                     tempCount;

    object->state.drainByISR = false;
    bufferEnd = (unsigned char*) object->readBuf + object->readSize;

    while (object->readCount) {
        if (RingBuf_get(&object->ringBuffer, &readIn) < 0) {
            break;
        }

        DebugP_log2("UART:(%p) read '0x%02x'",
            ((UARTCC32XX_HWAttrsV1 const *)(handle->hwAttrs))->baseAddr,
            (unsigned char)readIn);

        *(unsigned char *) (bufferEnd - object->readCount *
            sizeof(unsigned char)) = readIn;

        key = HwiP_disable();

        object->readCount--;

        HwiP_restore(key);

        if ((object->state.readDataMode == UART_DATA_TEXT) &&
            (object->state.readReturnMode == UART_RETURN_NEWLINE) &&
            (readIn == '\n')) {
            makeCallback = true;
            break;
        }
    }

    if (!object->readCount || makeCallback) {
        tempCount = object->readSize;
        object->readSize = 0;
        object->readCallback(handle, object->readBuf,
            tempCount - object->readCount);
    }
    else {
        object->state.drainByISR = true;
    }

    return (0);
}

/*
 *  ======== releasePowerConstraint ========
 */
static void releasePowerConstraint(UART_Handle handle)
{
    UARTCC32XX_Object *object = handle->object;
    uint_fast16_t      retVal;

    retVal = Power_releaseConstraint(PowerCC32XX_DISALLOW_LPDS);
    DebugP_assert(retVal == Power_SOK);
    object->state.txEnabled = false;

    DebugP_log1("UART:(%p) UART released write power constraint",
        ((UARTCC32XX_HWAttrsV1 const *)(handle->hwAttrs))->baseAddr);

    (void)retVal;
}

/*
 *  ======== writeData ========
 */
static void writeData(UART_Handle handle, bool inISR)
{
    UARTCC32XX_Object           *object = handle->object;
    UARTCC32XX_HWAttrsV1 const  *hwAttrs = handle->hwAttrs;
    unsigned char               *writeOffset;

    writeOffset = (unsigned char *)object->writeBuf +
        object->writeSize * sizeof(unsigned char);
    while (object->writeCount) {
        if (!MAP_UARTCharPutNonBlocking(hwAttrs->baseAddr,
                *(writeOffset - object->writeCount))) {
            /* TX FIFO is FULL */
            break;
        }
        if ((object->state.writeDataMode == UART_DATA_TEXT) &&
            (*(writeOffset - object->writeCount) == '\n')) {
            MAP_UARTCharPut(hwAttrs->baseAddr, '\r');
        }
        object->writeCount--;
    }

    if (!object->writeCount) {
        MAP_UARTIntDisable(hwAttrs->baseAddr, UART_INT_TX);
        MAP_UARTIntClear(hwAttrs->baseAddr, UART_INT_TX);

        /*
         *  Set TX interrupt for end of transmission mode.
         *  The TXRIS bit will be set only when all the data
         *  (including stop bits) have left the serializer.
         */
        MAP_UARTTxIntModeSet(hwAttrs->baseAddr, UART_TXINT_MODE_EOT);

        if (!UARTBusy(hwAttrs->baseAddr)) {
            object->writeCallback(handle, (void *)object->writeBuf,
                    object->writeSize);
            releasePowerConstraint(handle);
        }
        else {
            UARTIntEnable(hwAttrs->baseAddr, UART_INT_TX);
        }

        DebugP_log2("UART:(%p) Write finished, %d bytes written",
            hwAttrs->baseAddr, object->writeSize - object->writeCount);
    }
}

/*
 *  ======== writeSemCallback ========
 *  Simple callback to post a semaphore for the blocking mode.
 */
static void writeSemCallback(UART_Handle handle, void *buffer, size_t count)
{
    UARTCC32XX_Object *object = handle->object;

    SemaphoreP_post(object->writeSem);
}
