/*
 * spi_flash.c
 *
 *  Created on: Aug 3, 2024
 *      Author: tm
 */

// MT29F4G01 chosen due to availability, small package size.  MT29F2G08ABAGAM79A3WC1L might be an option.  Micron website is in terrible shape and datasheets are hard to find.
// GigaDevice GD5F2GQ5UEYIGR (256MB), WinBond W25N01GVZEIT and ISSI IS37SML01G1-LLI are other vendor options.

//c program using Tm4c123g TivaWare Library to erase pages, program and read MT29F4G01 with source code for all SPI functions


#include <stdint.h>
#include <stdbool.h>
#include "board_util.h"
#include "system.h"
#include "spi_flash.h"


#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
//#include "inc/hw_spi.h"
//#include "driverlib/sysctl.h"
//#include "driverlib/gpio.h"
//#include "driverlib/spi.h"

#include "user_io.h"

// Data Loggging in Float:
//  - start of 10 year deployment, nand flash is bulk erased and checked for bad blocks.   bad block table written in fram (other option is to replace chips with bad blocks)
//  - as data is sampled, it is stored/cached in i2c FRAM.
//  - when n number of nand flash pages (n x 4KB) are in FRAM, flash is powered up and 'burst written' into flash, flash is powered off.
//  - 128KB fram, upto 64KB for 'flash cache'.  16 pages are cached in FRAM.   Not using ECC memory in flash.
//  - fram also keeps track of 'next page' nand page address to be written
//  - typically argo floats are not recovered.   the datalogging is for engineering tests and in the rare event of a recovered float.
//  - error handling should not increase power consumption, best is to disable logging and not interfere with sampling.  subset of sampled data is sent via iridium satellite.
//  - retrieving data from nand flash can be read page at a time from nand flash and sent via zmodem - battery thrift is not a requirement for data retrieval.

#if BOARD_MSC == 3
    #if defined(PART_TM4C123GH6PM)
        #define MT29F4G01_PWR_PIN    GPIO_PIN_5
        #define MT29F4G01_PWR_PORT   GPIO_PORTE_BASE

        #define MT29F4G01_SSI_PORT   GPIO_PORTB_BASE
        #define MT29F4G01_PIN_SET    GPIO_PIN_4 | GPIO_PIN_6 | GPIO_PIN_7

        #define MT29F4G01_SCK_PIN_CONF    GPIO_PB4_SSI2CLK
        #define MT29F4G01_MOSI_PIN_CONF   GPIO_PB7_SSI2TX
        #define MT29F4G01_MISO_PIN_CONF   GPIO_PB6_SSI2RX

        #define MT29F4G01_CS1_PIN    GPIO_PIN_5
        #define MT29F4G01_CS1_PORT   GPIO_PORTB_BASE
        #define MT29F4G01_CS2_PIN    GPIO_PIN_4
        #define MT29F4G01_CS2_PORT   GPIO_PORTE_BASE
    #elif defined(PART_TM4C1290NCPDT)
        #define MT29F4G01_PWR_PIN    GPIO_PIN_3
        #define MT29F4G01_PWR_PORT   GPIO_PORTG_BASE

        #define MT29F4G01_SSI_PORT   GPIO_PORTG_BASE
        #define MT29F4G01_PIN_SET    GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7

        #define MT29F4G01_SCK_PIN_CONF    GPIO_PG7_SSI2CLK
        #define MT29F4G01_MOSI_PIN_CONF   GPIO_PG5_SSI2XDAT0
        #define MT29F4G01_MISO_PIN_CONF   GPIO_PG4_SSI2XDAT1

        #define MT29F4G01_CS1_PIN    GPIO_PIN_6
        #define MT29F4G01_CS1_PORT   GPIO_PORTG_BASE
    #else
        #error "Unknown part"
    #endif
#else
    #error "Unknown board"
#endif

#define LAUNCHPAD       1

int spi_write(uint32_t chipNum, uint8_t const * const tx_data, const size_t tx_data_len)
{
    return spi_write_then_read(chipNum, tx_data, tx_data_len, NULL, 0);
}

int spi_write_then_read(uint32_t chipNum, uint8_t const * const tx_data, const size_t tx_data_len, uint8_t * const rx_data, const size_t rx_data_len)
{
    size_t i;
    uint32_t temp;

    spi_flash_cs(chipNum, 0); // chip 0, write cs=0 (active)

    for(i = 0; i < tx_data_len; i++)
    {
        MAP_SSIDataPut(SSI2_BASE, tx_data[i]);
        MAP_SSIDataGet(SSI2_BASE, &temp);
    }

    for(i = 0; i < rx_data_len; i++)
    {
        MAP_SSIDataPut(SSI2_BASE, 0x00);
        MAP_SSIDataGet(SSI2_BASE, &temp);
        rx_data[i] = temp;
    }

    while( MAP_SSIBusy(SSI2_BASE) );

    spi_flash_cs(chipNum, 1); // chip 0, write cs=1 (disable)

    return 0;
}

int spi_write_then_write(uint32_t chipNum, uint8_t const * const tx_data1, const size_t tx_data1_len, uint8_t const * const tx_data2, const size_t tx_data2_len)
{
    size_t i;
    uint32_t temp;

    spi_flash_cs(chipNum, 0); // chip 0, write cs=0 (active)

    for(i = 0; i < tx_data1_len; i++)
    {
        MAP_SSIDataPut(SSI2_BASE, tx_data1[i]);
        MAP_SSIDataGet(SSI2_BASE, &temp);
    }

    for(i = 0; i < tx_data2_len; i++)
    {
        MAP_SSIDataPut(SSI2_BASE, tx_data2[i]);
        MAP_SSIDataGet(SSI2_BASE, &temp);
    }

    while( MAP_SSIBusy(SSI2_BASE) );

    spi_flash_cs(chipNum, 1); // chip 0, write cs=1 (disable)

    return 0;
}

void spi_flash_test(uint32_t testNum)
{
    spi_flash_open();   // power on, make sure SSI2 SPI port is config'd

    //spi_flash_readId();
    spi_flash_probe(0);
}

void spi_flash_power(uint32_t state)
{
    if(state == 0)  // power off = 0
    {
        MAP_GPIOPinWrite(MT29F4G01_PWR_PORT, MT29F4G01_PWR_PIN, 0x00);
    }
    else
    {
        MAP_GPIOPinWrite(MT29F4G01_PWR_PORT, MT29F4G01_PWR_PIN, 0xFF);
    }
}

MT29F_ECC_STATUS get_ecc_fMAP_status(const uint8_t status_reg)
{
    return (MT29F_ECC_STATUS)((status_reg & 0x70U) >> 4);
}

uint32_t linearAddressToBlockPageAddress(uint32_t linUserAddress)
{
    // Find block and page for a linear user storage address, ie skipping spare area

    // Block address is 17 bits in form of [16..6 | 5..0]
    // 16..6: 0-2047 is valid block range
    //  5..0: 0-63   is valid page range

    const uint32_t linear_user_page_number = linUserAddress / MT29F4G01_PAGE_STORAGE_SIZE;
    const uint32_t linear_block_number = linear_user_page_number / MT29F4G01_BLOCK_PAGE_CNT;
    const uint32_t offset_page_number  = linear_user_page_number % MT29F4G01_BLOCK_PAGE_CNT;

    return (linear_block_number << 6) | (offset_page_number);
}
uint32_t linearAddressToColumnAddress(uint32_t linUserAddress)
{
    // Find page offset for a linear user storage address, ie skipping spare area

    // Column address is 13 bits
    // 0-4351 is valid address within a page
    // 0-4095 is valid user area

    return linUserAddress % MT29F4G01_PAGE_STORAGE_SIZE;
}
uint32_t linearAddressToBlockNumber(uint32_t linUserAddress)
{
    const uint32_t linear_user_page_number = linUserAddress / MT29F4G01_PAGE_STORAGE_SIZE;
    const uint32_t linear_block_number = linear_user_page_number / MT29F4G01_BLOCK_PAGE_CNT;

    return linear_block_number;
}
bool isAddrInBadBlock(Bad_Block_Set const *const block_info, uint32_t linUserAddress)
{
    uint32_t blk_num = linearAddressToBlockNumber(linUserAddress);

    return block_info->bad_blocks[blk_num / 32] & (1U << (blk_num % 32));
}
int spi_flash_cmd_readId(uint32_t chipNum, uint8_t* out_mfg_id, uint8_t* out_dev_id)
{
    uint8_t out_data[2] = {MT29F_CMD_READ_ID, 0x00};
    uint8_t in_data[2]  = {0x00, 0x00};

    if( spi_write_then_read(chipNum, out_data, 2, in_data, 2) )
    {
        return -1;
    }

    if(out_mfg_id)
    {
        *out_mfg_id = in_data[0];
    }
    if(out_dev_id)
    {
        *out_dev_id = in_data[1];
    }

    return 0;
}

int spi_flash_cmd_getFeature(uint32_t chipNum, uint8_t feature_id, uint8_t* out_value)
{
    uint8_t out_data[2] = {MT29F_CMD_GET_FEATURE, feature_id};
    uint8_t in_data[1]  = {0x00};

    if( spi_write_then_read(chipNum, out_data, 2, in_data, 1) )
    {
        return -1;
    }

    if(out_value)
    {
        *out_value = in_data[0];
    }

    return 0;
}
int spi_flash_cmd_setFeature(uint32_t chipNum, uint8_t feature_id, uint8_t value)
{
    uint8_t out_data[3] = {MT29F_CMD_SET_FEATURE, feature_id, value};

    if( spi_write(chipNum, out_data, 3) )
    {
        return -1;
    }

    return 0;
}
int spi_flash_cmd_readPageToCache(uint32_t chipNum, uint32_t blockPageAddress)
{
    uint8_t out_data[4] = {MT29F_CMD_PAGE_READ, (blockPageAddress & 0x00010000U) >> 16, (blockPageAddress & 0x0000FF00U) >> 8, (blockPageAddress & 0x000000FFU) >> 0 };

    if( spi_write(chipNum, out_data, 4) )
    {
        return -1;
    }

    return 0;
}

int spi_flash_cmd_writeEnable(uint32_t chipNum)
{
    uint8_t out_data[1] = {MT29F_CMD_WRITE_ENABLE};

    if( spi_write(chipNum, out_data, 1) )
    {
        return -1;
    }

    return 0;
}
int spi_flash_cmd_writeDisable(uint32_t chipNum)
{
    uint8_t out_data[1] = {MT29F_CMD_WRITE_DISABLE};

    if( spi_write(chipNum, out_data, 1) )
    {
        return -1;
    }

    return 0;
}

int spi_flash_cmd_reset(uint32_t chipNum)
{
    uint8_t out_data[1] = {MT29F_CMD_RESET};

    if( spi_write(chipNum, out_data, 1) )
    {
        return -1;
    }

    return 0;
}

int spi_flash_cmd_readFromCache(uint32_t chipNum, uint32_t columnAddress, uint8_t* const out_cache_data, size_t out_data_len)
{
    uint8_t out_data[4] = {MT29F_CMD_READ_CACHE1, (columnAddress & 0x1F00U) >> 8, (columnAddress & 0x00FFU) >> 0, 0};

    if( spi_write_then_read(chipNum, out_data, 4, out_cache_data, out_data_len) )
    {
        return -1;
    }

    return 0;
}

int spi_flash_cmd_blockErase(uint32_t chipNum, uint32_t blockPageAddress)
{
    uint8_t out_data[4] = {MT29F_CMD_BLOCK_ERASE, (blockPageAddress & 0x00010000U) >> 16, (blockPageAddress & 0x0000FF00U) >> 8, (blockPageAddress & 0x000000FFU) >> 0 };
    if( spi_write(chipNum, out_data, 4) )
    {
        return -1;
    }

    return 0;
}

int spi_flash_cmd_programLoad(uint32_t chipNum, uint32_t columnAddress, uint8_t const * page_data, size_t page_data_len)
{
    uint8_t out_data[3] = {MT29F_CMD_PROGRAM_LOAD1, (columnAddress & 0x1F00U) >> 8, (columnAddress & 0x00FFU) >> 0};

    if( spi_write_then_write(chipNum, out_data, 3, page_data, page_data_len) )
    {
        return -1;
    }

    return 0;
}
int spi_flash_cmd_programLoadRandom(uint32_t chipNum, uint32_t columnAddress, uint8_t const * const page_data, size_t page_data_len)
{
    uint8_t out_data[3] = {MT29F_CMD_PROGRAM_LOAD_RANDOM1, (columnAddress & 0x1F00U) >> 8, (columnAddress & 0x00FFU) >> 0};

    if( spi_write_then_write(chipNum, out_data, 3, page_data, page_data_len) )
    {
        return -1;
    }

    return 0;
}
int spi_flash_cmd_programExec(uint32_t chipNum, uint32_t blockPageAddress)
{
    uint8_t out_data[4] = {MT29F_CMD_PROGRAM_EXEC, (blockPageAddress & 0x00010000U) >> 16, (blockPageAddress & 0x0000FF00U) >> 8, (blockPageAddress & 0x000000FFU) >> 0 };
    if( spi_write(chipNum, out_data, 4) )
    {
        return -1;
    }

    return 0;
}

int spi_flash_wait_oip_clear(uint32_t chipNum, uint8_t* out_status_reg)
{
    uint8_t status = 0;
    for(;;)
    {
        if( spi_flash_cmd_getFeature(chipNum, MT29F_FEATURE_ADDR_STATUS, &status) )
        {
            return -1;
        }

        if((status & 0x01U))
        {
            delay_usec(10);
            continue;
        }
        else
        {
            break;
        }
    }

    if(out_status_reg)
    {
        *out_status_reg = status;
    }

    return 0;
}

int spi_flash_wait_crbsy_clear(uint32_t chipNum, uint8_t* out_status_reg)
{
    uint8_t status = 0;
    for(;;)
    {
        if( spi_flash_cmd_getFeature(chipNum, MT29F_FEATURE_ADDR_STATUS, &status) )
        {
            return -1;
        }

        if((status & 0x80U))
        {
            delay_usec(10);
            continue;
        }
        else
        {
            break;
        }
    }

    if(out_status_reg)
    {
        *out_status_reg = status;
    }

    return 0;
}

int spi_flash_wait_reset(uint32_t chipNum, uint8_t* out_status_reg)
{
    if(spi_flash_cmd_reset(chipNum))
    {
        return -1;
    }

    return spi_flash_wait_oip_clear(chipNum, out_status_reg);
}

int spi_flash_wait_blockErase(uint32_t chipNum, uint32_t block_id)
{
    uint8_t status;

    if(block_id > (MT29F4G01_FLASH_BLOCK_CNT-1))
    {
        return -1;
    }

    if(spi_flash_cmd_writeEnable(chipNum))
    {
        return -1;
    }

    if( spi_flash_cmd_blockErase(chipNum, block_id << 6) )
    {
        return -1;
    }

    if( spi_flash_wait_oip_clear(chipNum, &status) )
    {
        return -1;
    }

    // Check for erase fail
    if(status & 0x04U)
    {
        return -1;
    }

    return 0;
}

int spi_flash_wait_readMeta1(uint32_t chipNum, uint32_t address, MT29F_Page_Meta1* const out_meta1)
{
    int ret;
    uint8_t status;
    MT29F_ECC_STATUS ecc_status;

    uint32_t blkPageAddr = linearAddressToBlockPageAddress(address);

    // read page to cache
    ret = spi_flash_cmd_readPageToCache(chipNum, blkPageAddr);
    if( ret )
    {
        return ret;
    }

    if( spi_flash_wait_oip_clear(chipNum, &status) )
    {
        return -1;
    }

    // fetch meta1
    ret = spi_flash_cmd_readFromCache(chipNum, MT29F_META1, (uint8_t*)out_meta1, sizeof(MT29F_Page_Meta1));
    if( ret )
    {
        return ret;
    }

    ecc_status = get_ecc_fMAP_status(status);
    switch(ecc_status)
    {
        case MT29F_ECC_STATUS_NO_ERROR:
        {
            // Yay
            break;
        }
        case MT29F_ECC_STATUS_CORRECTED_1_3:
        {
            // TODO: how to log
            break;
        }
        case MT29F_ECC_STATUS_CORRECTED_4_6:
        {
            // TODO: how to warn to refresh
            break;
        }
        case MT29F_ECC_STATUS_CORRECTED_7_8:
        {
            // TODO: how to warn to refresh
            break;
        }
        case MT29F_ECC_STATUS_NOT_CORRECTED:
        {
            // Data is no good
            // If we had extra ecc or FEC in the metadata area we could try to recover
            return -1;
        }
        default:
        {
            // Bug!
            return -1;
        }
    }

    return 0;
}

int spi_flash_wait_isPageFree(uint32_t chipNum, uint32_t address, bool* const out_page_is_free)
{
    MT29F_Page_Meta1 meta1;

    if(spi_flash_wait_readMeta1(chipNum, address, &meta1))
    {
        return -1;
    }

    // TODO: verify meta1 checksum, if we add one?
    if(out_page_is_free)
    {
        *out_page_is_free = (meta1.program_flag == 0xFFFFFFFFU);
    }

    return 0;
}

int spi_flash_setLockedBlocks(uint32_t chipNum, MT29F_LOCKED_BLOCKS lockedBlocks)
{
    uint8_t reg;
    uint8_t read_back;

    if( spi_flash_cmd_getFeature(chipNum, MT29F_FEATURE_ADDR_BLOCK_LOCK, &read_back) )
    {
        return -1;
    }

    reg = (read_back & (~0x7CU)) | (lockedBlocks & 0x7CU);

    if( spi_flash_cmd_setFeature(chipNum, MT29F_FEATURE_ADDR_BLOCK_LOCK, reg) )
    {
        return -1;
    }

    if( spi_flash_cmd_getFeature(chipNum, MT29F_FEATURE_ADDR_BLOCK_LOCK, &read_back) )
    {
        return -1;
    }

    if(reg != read_back)
    {
        return -1;
    }

    return 0;
}

int spi_flash_readBadBlockSet(uint32_t chipNum, Bad_Block_Set* out_bad_blocks)
{
    uint32_t i = 0;
    uint8_t is_bad_block = 0;

    if( ! out_bad_blocks )
    {
        return -1;
    }

    memset(out_bad_blocks, 0, sizeof(Bad_Block_Set));

    for(i = 0; i < MT29F4G01_FLASH_BLOCK_CNT; i++)
    {
        if( spi_flash_readBadBlockInfo(chipNum, i, &is_bad_block) )
        {
            return -1;
        }

        if(is_bad_block)
        {
            out_bad_blocks->bad_blocks[i/32U] |= (1U << (i % 32U));
        }
    }

    return 0;
}

int spi_flash_readBadBlockInfo(uint32_t chipNum, uint32_t block_id, uint8_t* out_is_bad_block)
{
    uint32_t spare_area_bb_mark = 0;
    uint8_t status;

    if(block_id > (MT29F4G01_FLASH_BLOCK_CNT-1))
    {
        return -1;
    }

    if( spi_flash_cmd_readPageToCache(chipNum, block_id << 6) )
    {
       return -1;
    }

    if( spi_flash_wait_oip_clear(chipNum, &status) )
    {
       return -1;
    }

    if( spi_flash_cmd_readFromCache(chipNum, MT29F_META2, (uint8_t*) &spare_area_bb_mark, 4) )
    {
       return -1;
    }

    if(out_is_bad_block)
    {
        *out_is_bad_block = (spare_area_bb_mark != 0xFFFFFFFFU);
    }

    return 0;
}

// API Function prototypes
void spi_flash_open(void)                      // init SPI peripheral for operation, turn on power to spi_flash
{
    uint32_t ulDataRx;

//PF0 - Flash enable 1
//PD1 - flash cs1
//PB5 on launchpad  CS1
//PE4 - flash cs2
//PE5 on Launchpad  CS2
//pf0 is GP_LogPwr on Launchpad

    spi_flash_cs(0, 1);     //chip=0, chip select = 1 (inactive)
    MAP_GPIOPinTypeGPIOOutput(MT29F4G01_CS1_PORT, MT29F4G01_CS1_PIN);
#if defined(MT29F4G01_CS2_PORT) &&defined(MT29F4G01_CS2_PIN)
    spi_flash_cs(1, 1);     //chip=1, chip select = 1 (inactive)
    MAP_GPIOPinTypeGPIOOutput(MT29F4G01_CS2_PORT, MT29F4G01_CS2_PIN);
#endif

    spi_flash_power(1);
    MAP_GPIOPinTypeGPIOOutput(MT29F4G01_PWR_PORT, MT29F4G01_PWR_PIN);

    // TPS7A02 - wait 2msec for 3.3v to stabilize
    delay_msec(2);

    // MT29F - wait 2msec for min startup / self reset time
    delay_usec(MT29F4G01_STARTUP_DELAY_US);

    // Setup SSI port 2
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);

    // wait for enable
    while( ! MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2) )
    {

    }

    MAP_SysCtlPeripheralReset(SYSCTL_PERIPH_SSI2);

    // wait for enable
    while( ! MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2) )
    {

    }

    // Configure the pin muxing
    MAP_GPIOPinConfigure(MT29F4G01_SCK_PIN_CONF);
    MAP_GPIOPinConfigure(MT29F4G01_MISO_PIN_CONF);
    MAP_GPIOPinConfigure(MT29F4G01_MOSI_PIN_CONF);
    MAP_GPIOPinTypeSSI(MT29F4G01_SSI_PORT, MT29F4G01_PIN_SET);

    // Configure and enable the SSI port for SPI master mode.
    //MOTO_MODE_0 is best for flash
	//APFTODO - make this 12.5Mhz like uSD in diskio.c  14 Mar 2025 TM
	MAP_SSIConfigSetExpClk(SSI2_BASE, GetSysClkHz(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 1000000, 8);
	MAP_SSIIntDisable(SSI2_BASE, SSI_TXEOT | SSI_DMATX | SSI_DMARX | SSI_TXFF | SSI_RXFF | SSI_RXTO | SSI_RXOR);
    _dsb();
    _isb();

#if defined(TARGET_IS_TM4C129_RA0) || defined(TARGET_IS_TM4C129_RA1) || defined(TARGET_IS_TM4C129_RA2)
	 MAP_SSIAdvModeSet(SSI2_BASE, SSI_ADV_MODE_LEGACY); // 123g does not have advanced mode
#endif

	MAP_SSIEnable(SSI2_BASE);
	_dsb();
	_isb();

    // Flush receive FIFO before starting to talk to the flash.
    while(MAP_SSIDataGetNonBlocking(SSI2_BASE, &ulDataRx))
    {
        ;
    }
}

void spi_flash_close(void)                     // deselect SPI peripheral, pins as GPIO for low power, turn off power to spi flash
{
    MAP_SSIDisable(SSI2_BASE);

    // Change the SSI pins to GPIO to reduce power consumption
    MAP_SysCtlPeripheralDisable(SYSCTL_PERIPH_SSI2);

    // Set nCS High
#if defined(MT29F4G01_CS1_PIN) && defined(MT29F4G01_CS1_PORT)
    MAP_GPIOPinWrite(MT29F4G01_CS1_PORT, MT29F4G01_CS1_PIN, 0xFF);
    MAP_GPIOPinTypeGPIOOutput(MT29F4G01_CS1_PORT, MT29F4G01_CS1_PIN);
#endif

#if defined(MT29F4G01_CS2_PIN) && defined(MT29F4G01_CS2_PORT)
    MAP_GPIOPinWrite(MT29F4G01_CS2_PORT, MT29F4G01_CS2_PIN, 0xFF);
    MAP_GPIOPinTypeGPIOOutput(MT29F4G01_CS2_PORT, MT29F4G01_CS2_PIN);
#endif

    // Set SCK, MOSI, MISO output low
    MAP_GPIOPinWrite(MT29F4G01_SSI_PORT, MT29F4G01_PIN_SET, 0x00);
    MAP_GPIOPinTypeGPIOOutput(MT29F4G01_SSI_PORT, MT29F4G01_PIN_SET);

    // Power down
    MAP_GPIOPinWrite(MT29F4G01_PWR_PORT, MT29F4G01_PWR_PIN, 0x00);   // turn 3.3v regulator LogPwr off
    MAP_GPIOPinTypeGPIOOutput(MT29F4G01_PWR_PORT, MT29F4G01_PWR_PIN);

    // Ensure gpio config is sync'd
    _dsb();
    _isb();
}

void spi_flash_cs(uint32_t chipNum, uint32_t state)  // chipNum = 0 or 1,  state = 0 for chip select disable, state = 1 for chip select enable
{
    if(chipNum == 0)
    {
        if(state > 0)
        {
            MAP_GPIOPinWrite(MT29F4G01_CS1_PORT, MT29F4G01_CS1_PIN, 0xFF);
        }
        else
        {
            MAP_GPIOPinWrite(MT29F4G01_CS1_PORT, MT29F4G01_CS1_PIN, 0x00);
        }
    }
    else
    {
        #if defined(MT29F4G01_CS2_PORT) && defined(MT29F4G01_CS2_PIN)
        if(state > 0)
        {
            MAP_GPIOPinWrite(MT29F4G01_CS2_PORT, MT29F4G01_CS2_PIN, 0xFF);
        }
        else
        {
            MAP_GPIOPinWrite(MT29F4G01_CS2_PORT, MT29F4G01_CS2_PIN, 0x00);
        }
        #else
            crash_phfetsos();
        #endif
    }
    // MT29F nCS-SCK setup time is ~3ns, so we probably don't need extra delay after setting the nCS line.
    // Just sync to make sure its not sitting in the store buffer should delay a few clock cycles
    // The board is pretty small and SCK is slow
    _dsb();
    _isb();
}

int spi_flash_probe(uint32_t chipNum)         // read id
{
    uint8_t mfgId  = 0;
    uint8_t chipId = 0;

    if(spi_flash_cmd_readId(chipNum, &mfgId, &chipId))
    {
        return -1;
    }

    if(mfgId != MT29F4G01_MF_ID)
    {
        return -1;
    }

    if( ! ((chipId != MT29F4G01_DEV_ID_3V3) || (chipId != MT29F4G01_DEV_ID_1V8)) )
    {
        return -1;
    }

    return(0);   // return 0 if probe succeeds
}

// The NAND Flash memory array is programmed and read in page-based operations; it is erased in block-based operations.
int spi_flash_bulkErase(uint32_t chipNum)     // use block erase to erase the entire device.
{
    uint32_t i = 0;
    uint8_t is_bad_block = 0;

    for(i = 0; i < MT29F4G01_FLASH_BLOCK_CNT; i++)
    {
        if( spi_flash_readBadBlockInfo(chipNum, i, &is_bad_block) )
        {
            return -1;
        }

        if(is_bad_block)
        {
            // You must not erase bad blocks, as you won't be able to tell it is bad afterwards
            continue;
        }

        // TODO: also check any other bad block table we have, eg one based on page program failure

        if( spi_flash_wait_blockErase(chipNum, i) )
        {
            return -1;
        }
    }

    return 0;
}

int spi_flash_writePage(uint32_t chipNum, uint32_t address, uint8_t const * const data, uint32_t length)
{
    uint8_t status = 0;
    MT29F_Page_Meta1 meta1;

    // we force alignment to a page start by checking of colAddr is 0
    const uint32_t colAddr     = linearAddressToColumnAddress(address);
    const uint32_t blkPageAddr = linearAddressToBlockPageAddress(address);

    // We must write to begining of the page, as the metadata does not currently have a start offset
    if(colAddr != MT29F_MAIN0)
    {
        return -1;
    }

    // writes are limited to the main area as we control meta1 in this api
    if(length > MT29F4G01_PAGE_STORAGE_SIZE)
    {
        return -1;
    }

    // Mark page as programmed, record length and TODO: data checksum
    meta1.program_flag = 0;
    meta1.main_length  = length;
    meta1.main_crc     = 0;

    if( spi_flash_cmd_writeEnable(chipNum) )
    {
        return -1;
    }

    if( spi_flash_cmd_programLoad(chipNum, colAddr, data, length) )
    {
        return -1;
    }

    if( spi_flash_cmd_programLoadRandom(chipNum, MT29F_META1, (uint8_t*)&meta1, sizeof(meta1)) )
    {
        return -1;
    }

    if( spi_flash_cmd_programExec(chipNum, blkPageAddr) )
    {
        return -1;
    }

    if( spi_flash_wait_oip_clear(chipNum, &status) )
    {
        return -1;
    }

    // Check for prog fail
    if(status & 0x08U)
    {
        // TODO: when do we mark page or block bad?
        return -1;
    }

    return 0;
}



int spi_flash_readPage(uint32_t chipNum, uint32_t address, uint8_t* const data, uint32_t max_length, uint32_t* out_num_read)
{
    MT29F_Page_Meta1 meta1;
    uint8_t status;
    MT29F_ECC_STATUS ecc_status;
    uint32_t num_to_read = max_length;

    const uint32_t colAddr     = linearAddressToColumnAddress(address);
    const uint32_t blkPageAddr = linearAddressToBlockPageAddress(address);

    if( ! out_num_read )
    {
        return -1;
    }

    // Limit reads to the user area, and do not span pages
    if( MT29F4G01_PAGE_STORAGE_SIZE < (colAddr + max_length)  )
    {
        num_to_read = MT29F4G01_PAGE_STORAGE_SIZE - colAddr;
    }

    // 13h (PAGE READ command to cache)
    // 0Fh (GET FEATURE command to read the status)
    // 03h or 0Bh (READ FROM CACHE)

    if( spi_flash_cmd_readPageToCache(chipNum, blkPageAddr) )
    {
        return -1;
    }

    if( spi_flash_wait_oip_clear(chipNum, &status) )
    {
        return -1;
    }

    ecc_status = get_ecc_fMAP_status(status);
    switch(ecc_status)
    {
        case MT29F_ECC_STATUS_NO_ERROR:
        {
            // Yay
            break;
        }
        case MT29F_ECC_STATUS_CORRECTED_1_3:
        {
            // TODO: how to log
            break;
        }
        case MT29F_ECC_STATUS_CORRECTED_4_6:
        {
            // TODO: how to warn to refresh
            break;
        }
        case MT29F_ECC_STATUS_CORRECTED_7_8:
        {
            // TODO: how to warn to refresh
            break;
        }
        case MT29F_ECC_STATUS_NOT_CORRECTED:
        {
            // Data is no good
            // If we had extra ecc or FEC in the metadata area we could try to recover
            return -1;
        }
        default:
        {
            // Bug!
            return -1;
        }
    }

    // Read the metadata to ensure this page was written and has that much data
    if( spi_flash_cmd_readFromCache(chipNum, MT29F_META1, (uint8_t*)&meta1, sizeof(meta1)) )
    {
        return -1;
    }

    if(meta1.program_flag == 0xFFFFFFFFU)
    {
        return -1;
    }

    // Limit read size to the actual programmed data in the page
    if( (colAddr + meta1.main_length) < num_to_read )
    {
        num_to_read = meta1.main_length - colAddr;
    }

    if( spi_flash_cmd_readFromCache(chipNum, colAddr, data, num_to_read) )
    {
        return -1;
    }

    *out_num_read = num_to_read;

    return 0;
}





#ifdef GEMINIAI_CODE

// TODO: add vars to fxns indicating state of: flash power, spi peripheral, chip select

void SPIInit();
void CS_Enable();
void CS_Disable();
void WriteEnable();
void BulkErase();
void SectorErase(uint32_t address);
void PageProgram(uint32_t address, const uint8_t *data, uint32_t length);
uint8_t ReadByte(uint32_t address);

int main()
{
    SPIInit();

    // Example usage
    BulkErase();
    PageProgram(0x10000, data, 256); // Program 256 bytes at address 0x10000
    uint8_t readData = ReadByte(0x10000);

    return 0;
}

// SPI initialization function
void SPIInit() {
    // Configure SPI pins, enable clock, and initialize the SPI module
    // Refer to TivaWare documentation for detailed steps
}

// Enable chip select
void CS_Enable()
{
    GPIOPinWrite(MT29F4G01_CS_PORT, MT29F4G01_CS_PIN, 0);
}

// Disable chip select
void CS_Disable() {
    GPIOPinWrite(MT29F4G01_CS_PORT, MT29F4G01_CS_PIN, MT29F4G01_CS_PIN);
}

// Write enable command
void WriteEnable() {
    CS_Enable();
    SPIDataPut(SSI0_BASE, WRITE_ENABLE); // Replace SSI0_BASE with your SPI base
    while(SPIBusy(SSI0_BASE));
    CS_Disable();
}

// Bulk erase command
void BulkErase() {
    WriteEnable();
    CS_Enable();
    SPIDataPut(SSI0_BASE, BULK_ERASE);
    while(SPIBusy(SSI0_BASE));
    CS_Disable();
    // Wait for erase completion (check status register)
}

// Sector erase command
void SectorErase(uint32_t address) {
    // ... implementation
}

// Page program command
void PageProgram(uint32_t address, const uint8_t *data, uint32_t length) {
    // ... implementation
}

// Read byte from specified address
uint8_t ReadByte(uint32_t address) {
    // ... implementation
}


//c program using Tm4c123g TivaWare Library with SPI interface  to program pages and read pages in MT29F4G01 with source code with source code for program page and read page

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "inc/hw_spi.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/spi.h"

#define MT29F4G01_CS_PIN GPIO_PIN_0 // Replace with your CS pin
#define MT29F4G01_CS_PORT GPIO_PORTA // Replace with your CS port

// MT29F4G01 commands
#define WRITE_ENABLE 0x06
#define PAGE_PROGRAM 0x02
#define READ 0x03

// Function prototypes
void SPIInit();
void CS_Enable();
void CS_Disable();
void WriteEnable();
void PageProgram(uint32_t address, const uint8_t *data, uint32_t length);
void ReadPage(uint32_t address, uint8_t *data, uint32_t length);

int main() {
    SPIInit();

    // Example usage
    uint8_t data[256];
    // ... populate data array

    PageProgram(0x10000, data, sizeof(data)); // Program 256 bytes at address 0x10000

    // Read back data
    uint8_t readData[256];
    ReadPage(0x10000, readData, sizeof(readData));

    return 0;
}

// SPI initialization function
void SPIInit() {
    // Configure SPI pins, enable clock, and initialize the SPI module
    // Refer to TivaWare documentation for detailed steps
}

// Enable chip select
void CS_Enable() {
    GPIOPinWrite(MT29F4G01_CS_PORT, MT29F4G01_CS_PIN, 0);
}

// Disable chip select
void CS_Disable() {
    GPIOPinWrite(MT29F4G01_CS_PORT, MT29F4G01_CS_PIN, MT29F4G01_CS_PIN);
}

// Write enable command
void WriteEnable() {
    CS_Enable();
    SPIDataPut(SSI0_BASE, WRITE_ENABLE); // Replace SSI0_BASE with your SPI base
    while(SPIBusy(SSI0_BASE));
    CS_Disable();
}

// Page program command
void PageProgram(uint32_t address, const uint8_t *data, uint32_t length) {
    WriteEnable();
    CS_Enable();
    SPIDataPut(SSI0_BASE, PAGE_PROGRAM);
    SPIDataPut(SSI0_BASE, (address >> 16) & 0xFF);
    SPIDataPut(SSI0_BASE, (address >> 8) & 0xFF);
    SPIDataPut(SSI0_BASE, address & 0xFF);

    for (uint32_t i = 0; i < length; i++) {
        SPIDataPut(SSI0_BASE, data[i]);
    }

    while(SPIBusy(SSI0_BASE));
    CS_Disable();

    // Wait for programming to complete (check status register)
}

// Read page command
void ReadPage(uint32_t address, uint8_t *data, uint32_t length) {
    CS_Enable();
    SPIDataPut(SSI0_BASE, READ);
    SPIDataPut(SSI0_BASE, (address >> 16) & 0xFF);
    SPIDataPut(SSI0_BASE, (address >> 8) & 0xFF);
    SPIDataPut(SSI0_BASE, address & 0xFF);

    for (uint32_t i = 0; i < length; i++) {
        SPIDataPut(SSI0_BASE, 0xFF); // Dummy byte
        while(SPIBusy(SSI0_BASE));
        data[i] = SPIDataGet(SSI0_BASE);
    }

    CS_Disable();
}

#endif



#ifdef NOCODE
while(MAP_SSIDataGetNonBlocking(SSI3_BASE, &data[0]));     // Flush receive FIFO

MAP_SSIDataPutNonBlocking(SSI3_BASE, WREG + REG_GPIOCFG);   // Start writing at REG_GPIOCFG
MAP_SSIDataPutNonBlocking(SSI3_BASE, 2);                    // Write 3 bytes (n-1)
MAP_SSIDataPutNonBlocking(SSI3_BASE, gpiocfg);
MAP_SSIDataPutNonBlocking(SSI3_BASE, gpiodir);
MAP_SSIDataPutNonBlocking(SSI3_BASE, gpiodat);

while(MAP_SSIBusy(SSI3_BASE))
    ;                      // Let SSI finish sending bytes
iso_deassert_CS();                                  // Finish SSI register write transaction



iso_assert_CS();                                    // Start SSI read transaction
MAP_SSIDataPutNonBlocking(SSI3_BASE, NOP);          // Clock out result bytes
MAP_SSIDataPutNonBlocking(SSI3_BASE, NOP);          // Clock out result bytes
MAP_SSIDataPutNonBlocking(SSI3_BASE, NOP);          // Clock out result bytes
while(MAP_SSIBusy(SSI3_BASE));                      // Let SSI finish sending bytes

for(byte=0; byte<3; byte++) // Get the 24-bit result.
{
    MAP_SSIDataGetNonBlocking(SSI3_BASE, &data[byte]);      // Read the result bytes
    data[byte] &= 0x00FF;                                   // Save only LSB
}

iso_deassert_CS();              // Finish SSI read transaction
#endif
