/** @brief Test QSPI interface
 *
 * @copyright 2020 Remsdaq Ltd.
 *
 * @file c4_qspi_test.c
 *
 * @date 24 Jul 2020
 * @author Dan Smithers <dwhs@remsdaq.com>
 */

/*******************************************************************************
 * Includes
 ******************************************************************************/

#include "c4_qspi_test.h"
#include "c4_qspi.h"
#include "c4_privilege_modes.h"

#include "lib/c4_qspi_types.h"

#include <stddef.h>

/*******************************************************************************
 * Defines
 ******************************************************************************/
#ifndef min
# define min(a, b) ((a)<(b)?(a):(b))
#endif
/*******************************************************************************
 * Structures
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/** @brief Perform a write-read test on the QSPI flash functions
 *
 * Write a known pattern ('0's) to the sector(s) affected, then write the
 * byte pattern at the specified offset and length. Compare what is written
 * with what was supposed to be written using memory-mapped flash addresses.
 *
 * @param startSector [in] First sector to use in test.
 * @param offset [in] Offset from the start of test sectors to write data.
 * @param pattern [in] Characters to write as test pattern. This is a NULL terminated string.
 * @param noBytes [in] Number of bytes to write test pattern. If less than string length then it will be truncated, if more then repeated.
 *
 * @return exit code.
 * @retval C4_ERR_OK success.
 * @retval C4_ERR_FAIL failure.
 */
exit_code_t doWriteTest(uint32_t startSector, uint32_t offset, const char *pattern, uint32_t noBytes);

/** @brief Test the QSPI flash read function
 *
 * Write a known pattern ('0's) to the sector(s) affected, then write the
 * byte pattern at the specified offset and length.
 *
 * @param startSector [in] First sector to use in test.
 * @param offset [in] Offset from the start of test sectors to write data.
 * @param noBytes [in] Number of bytes to write test pattern. If less than string length then it will be truncated, if more then repeated.
 *
 * @return exit code.
 * @retval C4_ERR_OK success.
 * @retval C4_ERR_FAIL failure.
 */
exit_code_t doReadTest(uint32_t startSector, uint32_t offset, uint32_t noBytes);


/** @brief Test the QSPI flash byte-erase function
 *
 * Write a known pattern ('0's) to the sector(s) affected, then write the
 * byte pattern at the specified offset and length.
 *
 * @param startSector [in] First sector to use in test.
 * @param offset [in] Offset from the start of test sectors to write data.
 * @param noBytes [in] Number of bytes to write test pattern. If less than string length then it will be truncated, if more then repeated.
 *
 * @return exit code.
 * @retval C4_ERR_OK success.
 * @retval C4_ERR_FAIL failure.
 */
exit_code_t doEraseTest(uint32_t startSector, uint32_t offset, uint32_t noBytes);

/*******************************************************************************
 * Variables
 ******************************************************************************/
extern flexspi_nor_config_t sFlashConfig;
extern bool FLASH_INITIALISED;

/*******************************************************************************
 * Interface Implementation
 ******************************************************************************/
exit_code_t QSPI_test(void)
{
    exit_code_t rc = C4_ERR_OK;

    uint32_t sector = 512;

    C4_PRINT("%s\n", __FUNCTION__);

    if (!FLASH_INITIALISED)
    {
        C4_PRINT("QSPI uninitialised\n");
    }
    else
    {
        C4_PRINT("QSPI must be uninitialised at the start of tests.\n");
        rc = C4_ERR_UNINITIALISED;
    }

    uint8_t testBuf[10];
    C4_PRINT("Testing uninitialised calls\n");
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_UNINITIALISED != C4_QSPI_write(FlexSPI_AMBA_BASE, 4, (uint8_t *)"Uninitialised")))
    {
        C4_PRINT("Uninitialised call to write test fail\n");
        rc = C4_ERR_UNINITIALISED;
    }
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_UNINITIALISED != C4_QSPI_read(FlexSPI_AMBA_BASE, 4, testBuf)))
    {
        C4_PRINT("Uninitialised call to read test fail\n");
        rc = C4_ERR_UNINITIALISED;
    }
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_UNINITIALISED != C4_QSPI_erase(FlexSPI_AMBA_BASE, 4)))
    {
        C4_PRINT("Uninitialised call to erase test fail\n");
        rc = C4_ERR_UNINITIALISED;
    }

    // Now call initialise function.
    rc = C4_QSPI_init();

    C4_PRINT("Testing write range\n");
    // test write before QSPI range is rejected.
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_RANGE != C4_QSPI_write(FlexSPI_AMBA_BASE-1, 4, (uint8_t *)"fail")))
    {
        C4_PRINT("Write before flash test fail\n");
        rc = C4_ERR_RANGE;
    }
    // test write after QSPI range is rejected.
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_RANGE != C4_QSPI_write(FlexSPI_AMBA_BASE + sFlashConfig.memConfig.sflashA1Size-3, 4, (uint8_t *)"fail")))
    {
        C4_PRINT("Write after flash test fail\n");
        rc = C4_ERR_RANGE;
    }

    C4_PRINT("Testing read range\n");
    // test read before QSPI range is rejected.
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_RANGE != C4_QSPI_read(FlexSPI_AMBA_BASE-1, 4, testBuf)))
    {
        C4_PRINT("Read before flash test fail\n");
        rc = C4_ERR_RANGE;
    }
    // test read after QSPI range is rejected.
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_RANGE != C4_QSPI_read(FlexSPI_AMBA_BASE + sFlashConfig.memConfig.sflashA1Size-3, 4, testBuf)))
    {
        C4_PRINT("Read after flash test fail\n");
        rc = C4_ERR_RANGE;
    }
    C4_PRINT("Testing erase range\n");
    // test erase before QSPI range is rejected.
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_RANGE != C4_QSPI_erase(FlexSPI_AMBA_BASE-1, 4)))
    {
        C4_PRINT("Erase before flash test fail\n");
        rc = C4_ERR_RANGE;
    }
    // test erase after QSPI range is rejected.
    if ((C4_ERR_OK == rc) &&
        (C4_ERR_RANGE != C4_QSPI_erase(FlexSPI_AMBA_BASE + sFlashConfig.memConfig.sflashA1Size-3, 4)))
    {
        C4_PRINT("Erase after flash test fail\n");
        rc = C4_ERR_RANGE;
    }

    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Testing flash functions\n");
        C4_PRINT("Case 0 write whole sector %ld\n", sector);
        rc = doWriteTest(sector, 0, "0", 0);
    }
    ++sector;

    // case 1 write at the beginning of a sector
    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 1 write bytes at beginning of sector %ld\n", sector);
        rc = doWriteTest(sector, 0, "1", 1);
        ++sector;
    }

    // case 2 write at the end of a sector
    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 2 write bytes at end of sector %ld\n", sector);
        rc = doWriteTest(sector, C4_FLASH_SECTOR_SIZE - 3, "2", 3);
        ++sector;
    }

    // case 3 write bytes in the middle of a sector
    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 3 write bytes in the middle of sector %ld\n", sector);
        char three[513];
        memset(three, '3', 512);
        three[512] = '\0';
        rc = doWriteTest(sector, C4_FLASH_SECTOR_SIZE / 2, three, 1234);
        ++sector;
    }

    // case 4 write spanning two sectors with one byte in second
    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 4 write bytes at spanning two partial sectors from %ld\n", sector);
        char four[C4_FLASH_SECTOR_SIZE / 2 + 2];
        memset(four, '4', C4_FLASH_SECTOR_SIZE / 2 + 1);
        four[C4_FLASH_SECTOR_SIZE / 2] = '\0';
        rc = doWriteTest(sector, C4_FLASH_SECTOR_SIZE / 2, four, C4_FLASH_SECTOR_SIZE / 2 + 1);
        sector+=2;
    }

    // case 5 write spanning two complete sectors
    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 5 write bytes spanning two complete sectors from %ld\n", sector);
        char five[C4_FLASH_SECTOR_SIZE * 2 + 1];
        memset(five, '5', C4_FLASH_SECTOR_SIZE * 2);
        five[C4_FLASH_SECTOR_SIZE * 2] = '\0';
        rc = doWriteTest(sector, 0, five, C4_FLASH_SECTOR_SIZE * 2);
        sector+=2;
    }

    // case 6 write spanning three sectors
    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 6 write bytes spanning three sectors from %ld\n", sector);
        char six[C4_FLASH_SECTOR_SIZE * 2 + 1];
        memset(six, '6', C4_FLASH_SECTOR_SIZE * 2);
        six[C4_FLASH_SECTOR_SIZE * 2] = '\0';
        rc = doWriteTest(sector, 1, six, C4_FLASH_SECTOR_SIZE * 2);
        sector+=3;
    }

    // case 7 write spanning four sectors
    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 7 write bytes spanning four sectors from %ld\n", sector);
        char seven[C4_FLASH_SECTOR_SIZE * 3 + 1];
        memset(seven, '7', C4_FLASH_SECTOR_SIZE * 3);
        seven[C4_FLASH_SECTOR_SIZE * 3] = '\0';
        rc = doWriteTest(sector, 10, seven, C4_FLASH_SECTOR_SIZE * 3);
        sector+=4;
    }

    if (C4_ERR_OK == rc)
    {
        C4_PRINT("Case 8 read bytes sector%ld\n", sector);
        rc = doReadTest(sector, 10, 100);
        ++sector;
    }

    return rc;
}

/*************************************************************************
 * Local Functions
 ************************************************************************/


exit_code_t doWriteTest(uint32_t startSector, uint32_t offset, const char *pattern, uint32_t noBytes)
{
    exit_code_t rc = C4_ERR_OK;
    const uintptr_t mapAddr = startSector * C4_FLASH_SECTOR_SIZE + FlexSPI_AMBA_BASE;

    // calculate number of bytes involved in this test i.e. number of sectors used * sector size.
    // startByte = offset ->
    // firstByte = (startByte / C4_FLASH_SECTOR_SIZE) * C4_FLASH_SECTOR_SIZE
    // endbyte   = offset + noBytes - 1
    // lastByte  = ((endByte / C4_FLASH_SECTOR_SIZE) + 1) * C4_FLASH_SECTOR_SIZE) - 1)
    //
    // If noBytes == 0 then the test length is one sector

    const uint32_t testLen = (noBytes ?
                              ( (offset + noBytes - 1) / C4_FLASH_SECTOR_SIZE - offset / C4_FLASH_SECTOR_SIZE + 1) * C4_FLASH_SECTOR_SIZE :
                              C4_FLASH_SECTOR_SIZE);
    C4_PRINT("%s(%ld, %ld, 0x%p, %ld) - testing bytes %ld\n",
//    C4_PRINT("%s(%d, %d, \"%s\", %d) - testing bytes %d\n",
           __FUNCTION__, startSector, offset, pattern, noBytes, testLen);
    char buffer[testLen+1];

    // clear sector with '0's
    memset(buffer, '0', testLen);
    buffer[testLen] = '\0';
//    C4_PRINT("Clearing sectors with \"%s\"\n %d @ 0x%p\n", buffer, testLen, buffer);
    rc = C4_QSPI_write(mapAddr, testLen, (uint8_t *)buffer);

    // write pattern
    uint32_t bytesWritten = 0;
    while (noBytes > bytesWritten)
    {
        uint32_t bytesToWrite = min(noBytes - bytesWritten, strlen(pattern));
        C4_PRINT("LINE %d noBytes %ld bytesWritten %ld bytesToWrite %ld\n", __LINE__, noBytes, bytesWritten, bytesToWrite);
        rc = C4_QSPI_write(mapAddr + offset + bytesWritten, bytesToWrite, (uint8_t *)pattern);
        bytesWritten += bytesToWrite;
    }

    // test region before pattern - up to offset
    if ((offset > 0) && (C4_ERR_OK == rc))
    {
        C4_PRINT("Test before pattern 0x%p-0x%p\n", (void *)mapAddr, (void *)(mapAddr + offset - 1));
        if ( (rc = memcmp((void *)(mapAddr), buffer, offset)) )
        {
            C4_PRINT("LINE %d Memory test failed before pattern %d\n", __LINE__, rc);
            rc = C4_ERR_FAILED;
        }
    }

    //test pattern - from offset to offset + noBytes - 1
    if ((noBytes > 0) && (C4_ERR_OK == rc))
    {
        C4_PRINT("Test pattern 0x%p-0x%p\n", (void *)(mapAddr + offset), (void *)(mapAddr + offset + noBytes - 1));
        uint32_t bytesRead = 0;
        while (noBytes > bytesRead)
        {
            uint32_t bytesToRead = min(noBytes - bytesRead, strlen(pattern));
            if( (rc = memcmp((void *)(mapAddr + offset + bytesRead), pattern, bytesToRead)) )
            {
                C4_PRINT("LINE %d Memory test failed inside pattern %d\n", __LINE__, rc);
                rc = C4_ERR_FAILED;
                break;
            }
            bytesRead += bytesToRead;
        }
    }

    //test region after pattern - from offset + noBytes to testLen
    if ((offset + noBytes < testLen) && (C4_ERR_OK == rc))
    {
        C4_PRINT("Test after pattern 0x%p-0x%p\n", (void *)(mapAddr + offset + noBytes), (void *)(mapAddr + testLen - 1));
        if ( (rc = memcmp((void *)(mapAddr + offset + noBytes), buffer, testLen - offset - noBytes)) )
        {
            C4_PRINT("LINE %d Memory test failed after pattern %d\n", __LINE__, rc);
            rc = C4_ERR_FAILED;
        }
    }
    return rc;
}


/*************************************************************************/
const char loremIpsum[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent ut massa odio. Maecenas non porta neque, facilisis iaculis nulla. In faucibus congue semper. Donec commodo tempus erat, in auctor lectus. Donec scelerisque malesuada venenatis. Sed est lacus, malesuada at est et, congue iaculis ex. Nulla non mollis velit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam iaculis pharetra tincidunt. Praesent tincidunt rhoncus lacus, at mattis libero efficitur sit amet. Nulla scelerisque ante ex. Pellentesque lobortis enim augue, a imperdiet nunc luctus pretium. Donec pellentesque mi diam, hendrerit molestie magna laoreet ut. Donec condimentum ipsum urna, vitae consectetur augue pharetra vulputate. Fusce consequat, justo vestibulum pharetra placerat, sapien lorem dapibus magna, et hendrerit elit eros at nibh. Praesent vitae condimentum tortor.\n"\
"Phasellus in mattis leo, et vulputate dolor. In tortor sem, ultrices facilisis ante non, lacinia tempus ante. Maecenas pretium neque sed sapien porttitor tincidunt. Sed vestibulum rutrum varius. Donec fringilla commodo ligula quis tristique. Suspendisse dapibus sollicitudin accumsan. Ut lobortis ut elit nec ultrices. Pellentesque eget egestas lacus, quis dictum mi. Praesent pretium quam vel tortor venenatis, quis iaculis enim feugiat. Quisque sodales neque ut ex scelerisque, ut ornare odio rutrum. Duis ac dolor vel dui dictum facilisis. Cras pretium sem vitae sem facilisis varius. Aenean lobortis mauris felis, eget bibendum augue sodales eu. Quisque at elit massa. Aenean dignissim tortor vitae dui tincidunt, in pellentesque magna lacinia.\n"\
"Nulla facilisi. Etiam bibendum diam vestibulum, lacinia magna in, blandit neque. Pellentesque rhoncus enim eget lectus vulputate placerat. Pellentesque pharetra finibus rhoncus. Mauris eget elit vitae lectus viverra tristique. Phasellus eleifend ipsum eget placerat rhoncus. Aenean viverra felis eu ipsum hendrerit, non pulvinar nisl molestie. Nullam varius nulla eros, at congue velit malesuada eu. In sapien ligula, rhoncus vel nulla vitae, aliquet posuere quam. Donec a metus nec libero egestas blandit eleifend in libero. In vel est viverra, pharetra enim at, posuere arcu.\n"\
"Donec nec porttitor massa. Fusce nec pellentesque nibh. Pellentesque ullamcorper tortor nulla, eu tempor massa sodales commodo. Praesent facilisis felis at dolor aliquam rutrum. Aenean metus risus, dictum et pretium vitae, porta quis diam. Nunc gravida ipsum efficitur ex ullamcorper, sit amet dictum ipsum vestibulum. Morbi ac posuere ligula, ut vulputate dolor. Praesent vitae lorem purus. In ultricies urna enim, in suscipit purus dapibus eu. Quisque nec purus lobortis quam venenatis tempus eu ut libero. Praesent vitae diam faucibus, laoreet neque sed, posuere neque. Sed a auctor enim, vel auctor odio.\n"\
"Aenean sodales nulla a eros porttitor, ac vestibulum sem vulputate. Quisque dui sapien, hendrerit ac neque sed, egestas placerat nisi. Sed facilisis odio ac libero pellentesque consectetur. Maecenas lobortis consectetur tortor, vitae aliquet tellus lobortis nec. Nullam imperdiet commodo leo, et posuere neque aliquet vel. Maecenas porttitor ligula at feugiat lacinia. Maecenas et volutpat risus. Nullam condimentum ante lacus, ac tristique libero commodo ut. Nulla ac blandit purus. Nam a ullamcorper enim, id mattis mi. Aliquam maximus dolor elit, vel venenatis nisi ultricies faucibus. Duis eget nisi id neque efficitur faucibus eu at massa.\n"\
"In pellentesque lectus ut justo bibendum, non mattis turpis aliquam. Quisque egestas quis ligula id malesuada. Quisque nec faucibus mauris, sed blandit diam. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis leo orci. Donec tincidunt id risus eget posuere. Duis nec commodo lectus, feugiat egestas dolor. Praesent faucibus accumsan facilisis. Vivamus congue nec libero nec placerat. Etiam vel eleifend velit. Maecenas magna lectus, mollis at turpis id, convallis dictum orci. Aliquam ac leo euismod, vehicula nulla eu, ultricies purus. Morbi eget arcu vel diam sodales vulputate. Cras hendrerit magna est, non ullamcorper libero fermentum nec. In hac habitasse platea dictumst.\n"\
"Aliquam elit diam, lobortis vel commodo ut, convallis nec turpis. Fusce accumsan, eros eget egestas tempor, leo urna laoreet mi, eu dictum nisi augue nec justo. Morbi sodales fringilla luctus. Nullam rhoncus lectus risus. Integer et dignissim purus, id aliquet enim. Phasellus fringilla, magna eu finibus imperdiet, nibh quam consectetur velit, vitae tristique turpis augue vel ex. Quisque id lacinia quam. Aenean vitae ornare risus, mattis pretium purus. Sed tortor urna, malesuada eget tristique pharetra, semper eleifend nisl.\n"\
"Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed et metus facilisis, tincidunt leo eget, dapibus tortor. Vivamus pulvinar massa tellus, non varius purus convallis sed. Nulla eleifend, ligula et vulputate sagittis, velit turpis vulputate odio, eget hendrerit risus sapien vitae velit. Donec eu nulla nec tortor malesuada congue. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In tempor, lacus eu ornare dapibus, magna est pretium lectus, ac mattis felis urna nec quam. Mauris in ullamcorper nisi. Sed eu orci tellus.\n"\
"Duis ac dui felis. Quisque dictum libero at dictum lacinia. Morbi efficitur id leo vel iaculis. Mauris tempor leo volutpat odio auctor, quis consequat sapien pharetra. Aliquam dignissim arcu diam, ac consequat risus vulputate et. Fusce condimentum ut ipsum aliquet iaculis. Nulla accumsan luctus massa ac faucibus. Duis sagittis porta urna, a imperdiet sapien. Nulla mi leo, fermentum non quam vel, tristique cursus felis. Cras eu lorem vitae mi finibus iaculis.\n"\
"Morbi cursus dapibus ornare. Nullam viverra lorem lacus, et varius ipsum fermentum in. Phasellus nulla nunc, iaculis at porta eget, tristique vel lacus. Nullam mi velit, maximus eget nunc maximus, fermentum pellentesque est. Nam sollicitudin, neque eu cursus aliquet, nisl odio condimentum sapien, vel volutpat ante nisi vitae libero. Interdum et malesuada fames ac ante ipsum primis in faucibus. Suspendisse maximus porttitor nibh quis ornare. Nunc pulvinar auctor purus non pellentesque.\n"\
"Quisque nec vulputate leo. In elementum massa vel varius ullamcorper. Ut ultricies vulputate tortor, at viverra erat condimentum at. Ut suscipit tincidunt semper. Vivamus vel fringilla elit. Morbi varius cursus lacus consectetur iaculis. Integer id enim et sapien aliquet fringilla vel eu velit. Mauris sed tincidunt augue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc convallis ac tortor eu tincidunt. Nunc pulvinar imperdiet nisl ut ullamcorper. Vestibulum convallis pulvinar massa. Maecenas molestie nisi diam, et vestibulum dolor viverra at.\n"\
"Aliquam finibus turpis sit amet orci sodales, eget lacinia tellus interdum. Nulla vitae libero eget dui elementum hendrerit. Phasellus dictum pretium sem, nec viverra felis vestibulum ut. Aenean ipsum lorem, pharetra sed aliquet sed, sagittis vel arcu. Nam ac tellus lacinia velit ultricies finibus. Aenean vel commodo odio, ac scelerisque tortor. Fusce odio est, bibendum ac commodo ac, faucibus sed nibh. Nunc eget ligula quam. Fusce dictum vel eros ut porta.\n"\
"Integer tincidunt eget lacus ac ultrices. Interdum et malesuada fames ac ante ipsum primis in faucibus. Suspendisse lorem odio, lacinia vel eros in, commodo eleifend eros. Pellentesque sodales, lacus eu placerat tempus, purus dui venenatis risus, at dignissim libero turpis tincidunt mi. Nunc volutpat semper eros, consequat pharetra orci imperdiet in. Donec condimentum nisi nec ipsum porttitor fringilla. Etiam a quam quam. Fusce ut fermentum turpis, eu egestas nulla. Ut nec tristique nisl. Sed orci lacus, porttitor at purus vel, pharetra dignissim nunc. Proin aliquet, mi quis euismod rutrum, ligula nunc aliquam dolor, ut gravida libero turpis in diam. Morbi in turpis dictum, volutpat erat vel, condimentum enim. Curabitur finibus est ut erat tincidunt, sit amet tincidunt lacus tristique. Morbi convallis elementum turpis, vitae ultrices turpis ultricies quis. Etiam sodales diam sit amet consequat varius.\n"\
"Phasellus suscipit sapien at pulvinar tincidunt. Nam non pharetra mauris. Praesent nec ligula mattis, mollis lacus vel, tincidunt libero. Nam quis pretium ligula, quis sodales mauris. Nullam mollis auctor elementum. Vestibulum justo mi, aliquam venenatis viverra eu, blandit ut eros. Maecenas euismod tellus neque, quis blandit augue molestie nec. Phasellus quis erat vitae velit rutrum sodales congue eu nisl. Sed eu nibh eu est bibendum sollicitudin ut ac dolor. Ut posuere arcu eu leo lacinia eleifend. Aenean varius ex eu suscipit varius. Nulla et mattis felis. Suspendisse ultrices ut metus non ultrices. Ut eget bibendum nulla.\n"\
"Sed semper dapibus interdum. Mauris elementum lorem et enim blandit, vel aliquet tortor porta. Etiam diam lacus, pulvinar non tempus vel, volutpat et lacus. Maecenas nec vulputate ex, at lobortis eros. Morbi in ante felis. Fusce vel tincidunt velit. Pellentesque quis velit diam. Nullam accumsan mi augue, in luctus metus sagittis ut. Morbi tincidunt nisi a nisi lobortis imperdiet.\n"\
"Integer eu fringilla urna. Curabitur fermentum orci euismod tellus rutrum, at ullamcorper quam sollicitudin. Duis gravida erat at ultricies vestibulum. Morbi a lectus quis lectus vestibulum molestie vel sit amet ante. Nulla porttitor, est a auctor vulputate, libero ipsum faucibus mauris, faucibus egestas sapien orci et lacus. Maecenas vehicula erat eget magna pharetra, ut rhoncus turpis scelerisque. Integer laoreet commodo lorem, ac vulputate orci rhoncus eget. Praesent ultricies elit eu nunc malesuada tincidunt. Donec eget ornare ex. Etiam elit lacus, rhoncus at nisi maximus, feugiat elementum orci. Proin molestie tellus eget massa facilisis, eu auctor quam ultrices. Sed finibus, dui sed ultricies aliquet, nunc magna dolor.\n";


/*************************************************************************/

exit_code_t doReadTest(uint32_t startSector, uint32_t offset, uint32_t noBytes)
{
    exit_code_t rc = C4_ERR_OK;
    const uintptr_t mapAddr = startSector * C4_FLASH_SECTOR_SIZE + FlexSPI_AMBA_BASE;

    // calculate number of bytes involved in this test i.e. number of sectors used * sector size.
    // startByte = offset ->
    // firstByte = (startByte / C4_FLASH_SECTOR_SIZE) * C4_FLASH_SECTOR_SIZE
    // endbyte   = offset + noBytes - 1
    // lastByte  = ((endByte / C4_FLASH_SECTOR_SIZE) + 1) * C4_FLASH_SECTOR_SIZE) - 1)
    //
    // If noBytes == 0 then the test length is one sector

    const uint32_t testLen = (noBytes ?
                              ( (offset + noBytes - 1) / C4_FLASH_SECTOR_SIZE - offset / C4_FLASH_SECTOR_SIZE + 1) * C4_FLASH_SECTOR_SIZE :
                              C4_FLASH_SECTOR_SIZE);

    C4_PRINT("%s(%ld, %ld, %ld) - testing bytes %ld\n",
            __FUNCTION__, startSector, offset, noBytes, testLen);
    uint8_t buffer[noBytes+1];

    // write some data
    rc = C4_QSPI_write(mapAddr, testLen, (uint8_t *)loremIpsum);

    // read pattern
    rc = C4_QSPI_read(mapAddr + offset, noBytes, buffer);


    //test that data read matches the text that we wrote - from offset to offset + noBytes - 1
    if (noBytes > 0)
    {
        C4_PRINT("Test text 0x%p-0x%p\n", (void *)(mapAddr + offset), (void *)(mapAddr + offset + noBytes - 1));
        if( (rc = memcmp((void *)(loremIpsum + offset), buffer, noBytes)) )
        {
            C4_PRINT("LINE %d Memory test failed inside pattern %d\n", __LINE__, rc);
            rc = C4_ERR_FAILED;
        }
    }
    return rc;
}

/*************************************************************************/

exit_code_t doEraseTest(uint32_t startSector, uint32_t offset, uint32_t noBytes)
{
    exit_code_t rc = C4_ERR_OK;

    const uintptr_t mapAddr = startSector * C4_FLASH_SECTOR_SIZE + FlexSPI_AMBA_BASE;

    // calculate number of bytes involved in this test i.e. number of sectors used * sector size.
    // startByte = offset ->
    // firstByte = (startByte / C4_FLASH_SECTOR_SIZE) * C4_FLASH_SECTOR_SIZE
    // endbyte   = offset + noBytes - 1
    // lastByte  = ((endByte / C4_FLASH_SECTOR_SIZE) + 1) * C4_FLASH_SECTOR_SIZE) - 1)
    //
    // If noBytes == 0 then the test length is one sector

    const uint32_t testLen = (noBytes ?
                              ( (offset + noBytes - 1) / C4_FLASH_SECTOR_SIZE - offset / C4_FLASH_SECTOR_SIZE + 1) * C4_FLASH_SECTOR_SIZE :
                              C4_FLASH_SECTOR_SIZE);

    C4_PRINT("%s(%ld, %ld, %ld) - testing bytes %ld\n",
            __FUNCTION__, startSector, offset, noBytes, testLen);
    char buffer[testLen+1];

    // clear sector with '0's
    memset(buffer, '0', testLen);
    buffer[testLen] = '\0';
//    C4_PRINT("Clearing sectors with \"%s\"\n %d @ 0x%p\n", buffer, testLen, buffer);
    rc = C4_QSPI_write(mapAddr, testLen, (uint8_t *)buffer);

    // perform erase
    rc = C4_QSPI_erase(mapAddr + offset, noBytes);

    // test region before erase - up to offset
    if ((offset > 0) && (C4_ERR_OK == rc))
    {
        C4_PRINT("Test before erase 0x%p-0x%p\n", (void *)(mapAddr), (void *)(mapAddr + offset - 1));
        if ( (rc = memcmp((void *)(mapAddr), buffer, offset)) )
        {
            C4_PRINT("LINE %d Memory test failed before pattern %d\n", __LINE__, rc);
            rc = C4_ERR_FAILED;
        }
    }

    //test erase - from offset to offset + noBytes - 1
    if ((noBytes > 0) && (C4_ERR_OK == rc))
    {
        C4_PRINT("Test erase 0x%p-0x%p\n", (void *)(mapAddr + offset), (void *)(mapAddr + offset + noBytes - 1));
        uint8_t testBuf[noBytes+1];
        memset(testBuf, 0xFFC, noBytes);
        testBuf[noBytes] = '\0';

        if( (rc = memcmp((void *)(mapAddr + offset), testBuf, noBytes)) )
        {
            C4_PRINT("LINE %d Memory test failed inside pattern %d\n", __LINE__, rc);
            rc = C4_ERR_FAILED;
        }
    }

    //test region after erase - from offset + noBytes to testLen
    if ((offset + noBytes < testLen) && (C4_ERR_OK == rc))
    {
        C4_PRINT("Test after erase 0x%p-0x%p\n", (void *)(mapAddr + offset + noBytes), (void *)(mapAddr + testLen - 1));
        if ( (rc = memcmp((void *)(mapAddr + offset + noBytes), buffer, testLen - offset - noBytes)) )
        {
            C4_PRINT("LINE %d Memory test failed after pattern %d\n", __LINE__, rc);
            rc = C4_ERR_FAILED;
        }
    }

    return rc;
}
