/*
 * Copyright (c) 2015-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.
 */

/*
 *  ======== NVSCC26XX.c ========
 */

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#include <ti/drivers/dpl/HwiP.h>
#include <ti/drivers/dpl/SemaphoreP.h>

#include <ti/drivers/NVS.h>
#include <ti/drivers/nvs/NVSCC26XX.h>

#include <ti/devices/DeviceFamily.h>
#include DeviceFamily_constructPath(driverlib/flash.h)
#include DeviceFamily_constructPath(driverlib/vims.h)
#include DeviceFamily_constructPath(driverlib/aon_batmon.h)

/* max number of bytes to write at a time to minimize interrupt latency */
#define MAX_WRITE_INCREMENT 8

/* Max number of writes per row of memory */
#define MAX_WRITES_PER_FLASH_ROW    (83)

static int_fast16_t checkEraseRange(NVS_Handle handle, size_t offset,
                        size_t size);
static int_fast16_t doErase(NVS_Handle handle, size_t offset, size_t size);
static uint8_t disableFlashCache(void);
static void restoreFlashCache(uint8_t mode);

extern NVS_Config NVS_config[];
extern const uint8_t NVS_count;

/* NVS function table for NVSCC26XX implementation */
const NVS_FxnTable NVSCC26XX_fxnTable = {
    NVSCC26XX_close,
    NVSCC26XX_control,
    NVSCC26XX_erase,
    NVSCC26XX_getAttrs,
    NVSCC26XX_init,
    NVSCC26XX_lock,
    NVSCC26XX_open,
    NVSCC26XX_read,
    NVSCC26XX_unlock,
    NVSCC26XX_write
};

/*
 *  Semaphore to synchronize access to flash region.
 */
static SemaphoreP_Handle  writeSem;

static size_t sectorSize;         /* fetched during init() */
static size_t sectorBaseMask;     /* for efficient argument checking */

/*
 *  ======== NVSCC26XX_close ========
 */
void NVSCC26XX_close(NVS_Handle handle)
{
    NVSCC26XX_Object *object;

    object = handle->object;
    object->opened = false;
}

/*
 *  ======== NVSCC26XX_control ========
 */
int_fast16_t NVSCC26XX_control(NVS_Handle handle, uint_fast16_t cmd,
                 uintptr_t arg)
{
    return (NVS_STATUS_UNDEFINEDCMD);
}

/*
 *  ======== NVSCC26XX_erase ========
 */
int_fast16_t NVSCC26XX_erase(NVS_Handle handle, size_t offset, size_t size)
{
    int_fast16_t status;

    SemaphoreP_pend(writeSem, SemaphoreP_WAIT_FOREVER);

    status = doErase(handle, offset, size);

    SemaphoreP_post(writeSem);

    return (status);
}

/*
 *  ======== NVSCC26XX_getAttrs ========
 */
void NVSCC26XX_getAttrs(NVS_Handle handle, NVS_Attrs *attrs)
{
    NVSCC26XX_HWAttrs const *hwAttrs;

    hwAttrs = handle->hwAttrs;

    /* FlashSectorSizeGet() returns the size of a flash sector in bytes. */
    attrs->regionBase  = hwAttrs->regionBase;
    attrs->regionSize  = hwAttrs->regionSize;
    attrs->sectorSize  = FlashSectorSizeGet();
}

/*
 *  ======== NVSCC26XX_init ========
 */
void NVSCC26XX_init()
{
    unsigned int key;
    SemaphoreP_Handle sem;

    /* initialize energy saving variables */
    sectorSize = FlashSectorSizeGet();
    sectorBaseMask = ~(sectorSize - 1);

    /* speculatively create a binary semaphore for thread safety */
    sem = SemaphoreP_createBinary(1);
    /* sem == NULL will be detected in 'open' */

    key = HwiP_disable();

    if (writeSem == NULL) {
        /* use the binary sem created above */
        writeSem = sem;
        HwiP_restore(key);
    }
    else {
        /* init already called */
        HwiP_restore(key);
        /* delete unused Semaphore */
        if (sem) SemaphoreP_delete(sem);
    }
}

/*
 *  ======== NVSCC26XX_lock =======
 */
int_fast16_t NVSCC26XX_lock(NVS_Handle handle, uint32_t timeout)
{
    if (SemaphoreP_pend(writeSem, timeout) != SemaphoreP_OK) {
        return (NVS_STATUS_TIMEOUT);
    }
    return (NVS_STATUS_SUCCESS);
}

/*
 *  ======== NVSCC26XX_open =======
 */
NVS_Handle NVSCC26XX_open(uint_least8_t index, NVS_Params *params)
{
    NVSCC26XX_Object *object;
    NVSCC26XX_HWAttrs const *hwAttrs;
    NVS_Handle handle;

    /* Confirm that 'init' has successfully completed */
    if (writeSem == NULL) {
        NVSCC26XX_init();
        if (writeSem == NULL) {
            return (NULL);
        }
    }

    /* verify NVS region index */
    if (index >= NVS_count) {
        return (NULL);
    }

    handle = &NVS_config[index];
    object = NVS_config[index].object;
    hwAttrs = NVS_config[index].hwAttrs;

    SemaphoreP_pend(writeSem, SemaphoreP_WAIT_FOREVER);

    if (object->opened == true) {
        SemaphoreP_post(writeSem);
        return (NULL);
    }

    /* The regionBase must be aligned on a flash page boundary */
    if ((size_t)(hwAttrs->regionBase) & (sectorSize - 1)) {
        SemaphoreP_post(writeSem);
        return (NULL);
    }

    /* The region cannot be smaller than a sector size */
    if (hwAttrs->regionSize < sectorSize) {
        SemaphoreP_post(writeSem);
        return (NULL);
    }

    /* The region size must be a multiple of sector size */
    if (hwAttrs->regionSize != (hwAttrs->regionSize & sectorBaseMask)) {
        SemaphoreP_post(writeSem);
        return (NULL);
    }

#if defined(NVSCC26XX_INSTRUMENTED)
    /* Check scoreboard parameters are defined & correct */
    if (hwAttrs->scoreboard &&
        (hwAttrs->flashPageSize == 0 || hwAttrs->scoreboardSize <
        (hwAttrs->regionSize / hwAttrs->flashPageSize))) {
        SemaphoreP_post(writeSem);
        return (NULL);
    }
#endif

    object->opened = true;

    SemaphoreP_post(writeSem);

    return (handle);
}

/*
 *  ======== NVSCC26XX_read =======
 */
int_fast16_t NVSCC26XX_read(NVS_Handle handle, size_t offset, void *buffer,
                 size_t bufferSize)
{
    NVSCC26XX_HWAttrs const *hwAttrs;

    hwAttrs = handle->hwAttrs;

    /* Validate offset and bufferSize */
    if (offset + bufferSize > hwAttrs->regionSize) {
        return (NVS_STATUS_INV_OFFSET);
    }

    /*
     *  Get exclusive access to the region.  We don't want someone
     *  else to erase the region while we are reading it.
     */
    SemaphoreP_pend(writeSem, SemaphoreP_WAIT_FOREVER);

    memcpy(buffer, (char *)(hwAttrs->regionBase) + offset, bufferSize);

    SemaphoreP_post(writeSem);

    return (NVS_STATUS_SUCCESS);
}

/*
 *  ======== NVSCC26XX_unlock =======
 */
void NVSCC26XX_unlock(NVS_Handle handle)
{
    SemaphoreP_post(writeSem);
}

/*
 *  ======== NVSCC26XX_write =======
 */
int_fast16_t NVSCC26XX_write(NVS_Handle handle, size_t offset, void *buffer,
                 size_t bufferSize, uint_fast16_t flags)
{
    NVSCC26XX_HWAttrs const *hwAttrs;
    unsigned int key;
    unsigned int size;
    uint32_t status = 0;
    int i;
    uint8_t mode;
    uint8_t *srcBuf, *dstBuf;
    size_t writeIncrement;
    int retval = NVS_STATUS_SUCCESS;

#if defined(NVSCC26XX_INSTRUMENTED)
    size_t   bytesWritten;
    uint32_t sbIndex, writeOffset;
#endif

    hwAttrs = handle->hwAttrs;

    /* Validate offset and bufferSize */
    if (offset + bufferSize > hwAttrs->regionSize) {
        return (NVS_STATUS_INV_OFFSET);
    }

    /* Get exclusive access to the Flash region */
    SemaphoreP_pend(writeSem, SemaphoreP_WAIT_FOREVER);

    /* If erase is set, erase destination sector(s) first */
    if (flags & NVS_WRITE_ERASE) {
        size = bufferSize & sectorBaseMask;
        if (bufferSize & (~sectorBaseMask)) {
            size += sectorSize;
        }

        retval = doErase(handle, offset & sectorBaseMask, size);
        if (retval != NVS_STATUS_SUCCESS) {
            SemaphoreP_post(writeSem);
            return (retval);
        }
    }
    else if (flags & NVS_WRITE_PRE_VERIFY) {
        /*
         *  If pre-verify, each destination byte must be able to be changed to the
         *  source byte (1s to 0s, not 0s to 1s).
         *  this is satisfied by the following test:
         *     src == (src & dst)
         */
        dstBuf = (uint8_t *)((uint32_t)(hwAttrs->regionBase) + offset);
        srcBuf = buffer;
        for (i = 0; i < bufferSize; i++) {
            if (srcBuf[i] != (srcBuf[i] & dstBuf[i])) {
                SemaphoreP_post(writeSem);
                return (NVS_STATUS_INV_WRITE);
            }
        }
    }

    srcBuf = buffer;
    size   = bufferSize;
    dstBuf = (uint8_t *)((uint32_t)(hwAttrs->regionBase) + offset);

    mode = disableFlashCache();

    while (size) {
        if (size > MAX_WRITE_INCREMENT) {
            writeIncrement = MAX_WRITE_INCREMENT;
        }
        else {
            writeIncrement = size;
        }
        key = HwiP_disable();
        status = FlashProgram((uint8_t*)srcBuf, (uint32_t)dstBuf,
                     writeIncrement);
        HwiP_restore(key);

        if (status != 0) {
            break;
        }
        else {
            size -= writeIncrement;
            srcBuf += writeIncrement;
            dstBuf += writeIncrement;
        }
    }

    restoreFlashCache(mode);

#if defined(NVSCC26XX_INSTRUMENTED)
    if (hwAttrs->scoreboard) {
        /*
         * Write counts are updated even if an error occurs & not all data was
         * written.
         */
        bytesWritten = bufferSize - size;
        writeOffset = offset;

        while (bytesWritten) {
            if (bytesWritten > MAX_WRITE_INCREMENT) {
                writeIncrement = MAX_WRITE_INCREMENT;
            }
            else {
                writeIncrement = bytesWritten;
            }

            sbIndex = writeOffset / hwAttrs->flashPageSize;
            hwAttrs->scoreboard[sbIndex]++;

            /* Spin forever if the write limit is exceeded */
            if (hwAttrs->scoreboard[sbIndex] > MAX_WRITES_PER_FLASH_ROW) {
                while (1);
            }

            writeOffset += writeIncrement;
            bytesWritten -= writeIncrement;
        }
    }
#endif

    if (status != 0) {
        retval = NVS_STATUS_ERROR;
    }
    else if (flags & NVS_WRITE_POST_VERIFY) {
        /*
         *  Note: This validates the entire region even on erase mode.
         */
        dstBuf = (uint8_t *)((uint32_t)(hwAttrs->regionBase) + offset);
        srcBuf = buffer;

        for (i = 0; i < bufferSize; i++) {
            if (srcBuf[i] != dstBuf[i]) {
                retval = NVS_STATUS_ERROR;
                break;
            }
        }
    }

    SemaphoreP_post(writeSem);

    return (retval);
}

/*
 *  ======== checkEraseRange ========
 */
static int_fast16_t checkEraseRange(NVS_Handle handle, size_t offset,
                        size_t size)
{
    NVSCC26XX_HWAttrs const *hwAttrs = handle->hwAttrs;

    if (offset != (offset & sectorBaseMask)) {
        return (NVS_STATUS_INV_ALIGNMENT);    /* poorly aligned start */
                                              /* address */
    }

    if (offset >= hwAttrs->regionSize) {
        return (NVS_STATUS_INV_OFFSET);   /* offset is past end of region */
    }

    if (offset + size > hwAttrs->regionSize) {
        return (NVS_STATUS_INV_SIZE);     /* size is too big */
    }

    if (size != (size & sectorBaseMask)) {
        return (NVS_STATUS_INV_SIZE);     /* size is not a multiple of */
                                          /* sector size */
    }

    return (NVS_STATUS_SUCCESS);
}

/*
 *  ======== doErase ========
 */
static int_fast16_t doErase(NVS_Handle handle, size_t offset, size_t size)
{
    NVSCC26XX_HWAttrs const *hwAttrs = handle->hwAttrs;
    unsigned int key;
    uint8_t mode;
    uint32_t status = 0;
    uint32_t sectorBase;
    int_fast16_t rangeStatus;

#if defined(NVSCC26XX_INSTRUMENTED)
    uint32_t i;
    uint32_t sbIndex;
#endif

    /* sanity test the erase args */
    rangeStatus = checkEraseRange(handle, offset, size);

    if (rangeStatus != NVS_STATUS_SUCCESS) {
        return (rangeStatus);
    }

    sectorBase = (uint32_t)hwAttrs->regionBase + offset;

    mode = disableFlashCache();

    while (size) {
        key = HwiP_disable();
        status = FlashSectorErase(sectorBase);
        HwiP_restore(key);

        if (status != FAPI_STATUS_SUCCESS) {
            break;
        }

#if defined(NVSCC26XX_INSTRUMENTED)
        if (hwAttrs->scoreboard) {
            /*
             * Sector successfully erased; now we must clear scoreboard write
             * counts for all pages in the sector.
             */
            sbIndex = (sectorBase - (uint32_t) hwAttrs->regionBase) /
                hwAttrs->flashPageSize;

            for (i = 0; i < (sectorSize / hwAttrs->flashPageSize); i++) {
                hwAttrs->scoreboard[sbIndex + i] = 0;
            }
        }
#endif

        sectorBase += sectorSize;
        size -= sectorSize;
    }

    restoreFlashCache(mode);

    if (status != FAPI_STATUS_SUCCESS) {
        return (NVS_STATUS_ERROR);
    }

    return (NVS_STATUS_SUCCESS);
}

/*
 *  ======== disableFlashCache ========
 *  When updating the Flash, the VIMS (Vesatile Instruction Memory System)
 *  mode must be set to GPRAM or OFF, before programming, and both VIMS
 *  flash line buffers must be set to disabled.
 */
static uint8_t disableFlashCache(void)
{
    uint8_t mode = VIMSModeGet(VIMS_BASE);

    VIMSLineBufDisable(VIMS_BASE);

    if (mode != VIMS_MODE_DISABLED) {
        VIMSModeSet(VIMS_BASE, VIMS_MODE_DISABLED);
        while (VIMSModeGet(VIMS_BASE) != VIMS_MODE_DISABLED);
    }

    return (mode);
}

/*
 *  ======== restoreFlashCache ========
 */
static void restoreFlashCache(uint8_t mode)
{
    if (mode != VIMS_MODE_DISABLED) {
        VIMSModeSet(VIMS_BASE, VIMS_MODE_ENABLED);
    }

    VIMSLineBufEnable(VIMS_BASE);
}
