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

/*
 *  ======== NVSRAM.c ========
 */

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

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

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

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);

extern NVS_Config NVS_config[];
extern const uint8_t NVS_count;

/* NVS function table for NVSRAM implementation */
const NVS_FxnTable NVSRAM_fxnTable = {
    NVSRAM_close,
    NVSRAM_control,
    NVSRAM_erase,
    NVSRAM_getAttrs,
    NVSRAM_init,
    NVSRAM_lock,
    NVSRAM_open,
    NVSRAM_read,
    NVSRAM_unlock,
    NVSRAM_write
};

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

/*
 *  ======== NVSRAM_close ========
 */
void NVSRAM_close(NVS_Handle handle)
{
    ((NVSRAM_Object *) handle->object)->isOpen = false;
}

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

/*
 *  ======== NVSRAM_erase ========
 */
int_fast16_t NVSRAM_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);
}

/*
 *  ======== NVSRAM_getAttrs ========
 */
void NVSRAM_getAttrs(NVS_Handle handle, NVS_Attrs *attrs)
{
    NVSRAM_HWAttrs const *hwAttrs = handle->hwAttrs;

    attrs->regionBase = hwAttrs->regionBase;
    attrs->regionSize = hwAttrs->regionSize;
    attrs->sectorSize = hwAttrs->sectorSize;
}

/*
 *  ======== NVSRAM_init ========
 */
void NVSRAM_init()
{
    uintptr_t         key;
    SemaphoreP_Handle sem;

    /* 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);
        }
    }
}

/*
 *  ======== NVSRAM_lock =======
 */
int_fast16_t NVSRAM_lock(NVS_Handle handle, uint32_t timeout)
{
    switch (SemaphoreP_pend(writeSem, timeout)) {
        case SemaphoreP_OK:
            return (NVS_STATUS_SUCCESS);

        case SemaphoreP_TIMEOUT:
            return (NVS_STATUS_TIMEOUT);

        case SemaphoreP_FAILURE:
        default:
            return (NVS_STATUS_ERROR);
    }
}

/*
 *  ======== NVSRAM_open =======
 */
NVS_Handle NVSRAM_open(uint_least8_t index, NVS_Params *params)
{
    NVS_Handle            handle;
    NVSRAM_Object        *object;
    NVSRAM_HWAttrs const *hwAttrs;

    /* Confirm that 'init' has successfully completed */
    if (writeSem == NULL) {
        NVSRAM_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;

    /* for efficient argument checking */
    object->sectorBaseMask = ~(hwAttrs->sectorSize - 1);

    SemaphoreP_pend(writeSem, SemaphoreP_WAIT_FOREVER);

    if (object->isOpen) {
        SemaphoreP_post(writeSem);

        return (NULL);
    }

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

        return (NULL);
    }

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

        return (NULL);
    }

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

    object->isOpen = true;

    SemaphoreP_post(writeSem);

    return (handle);
}

/*
 *  ======== NVSRAM_read =======
 */
int_fast16_t NVSRAM_read(NVS_Handle handle, size_t offset, void *buffer,
    size_t bufferSize)
{
    NVSRAM_HWAttrs const *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);
}

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

/*
 *  ======== NVSRAM_write =======
 */
int_fast16_t NVSRAM_write(NVS_Handle handle, size_t offset, void *buffer,
    size_t bufferSize, uint_fast16_t flags)
{
    size_t                i;
    uint8_t              *dstBuf;
    uint8_t              *srcBuf;
    int_fast16_t          result;
    NVSRAM_Object        *object = handle->object;
    NVSRAM_HWAttrs const *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) {
        result = doErase(handle, offset & object->sectorBaseMask,
            (bufferSize + hwAttrs->sectorSize) & object->sectorBaseMask);
        if (result != NVS_STATUS_SUCCESS) {
            SemaphoreP_post(writeSem);

            return (result);
        }
    }
    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);
            }
        }
    }

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

    SemaphoreP_post(writeSem);

    return (NVS_STATUS_SUCCESS);
}

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

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

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

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

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

    return (NVS_STATUS_SUCCESS);
}

/*
 *  ======== doErase ========
 */
static int_fast16_t doErase(NVS_Handle handle, size_t offset, size_t size)
{
    void *                sectorBase;
    int_fast16_t          rangeStatus;
    NVSRAM_HWAttrs const *hwAttrs = handle->hwAttrs;

    /* sanity test the erase args */
    rangeStatus = checkEraseRange(handle, offset, size);
    if (rangeStatus != NVS_STATUS_SUCCESS) {
        return (rangeStatus);
    }

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

    memset(sectorBase, 0xFF, size);

    return (NVS_STATUS_SUCCESS);
}
