/*
 * spi_flash.h
 *
 *  Created on: Aug 4, 2024
 *      Author: tm
 */

#ifndef SPI_FLASH_H_
#define SPI_FLASH_H_

// MT29F4G01 4Gb density, Organization:
// – Page size ×1: 4352 bytes (4096 + 256 bytes)
// – Block size: 64 pages (256K + 16K bytes)
// – Plane size: 1 × 2048 blocks

// Page organization 4096 + 256
// Meta area is 64b meta2 + 64b meta1 + 128b ECC
// Meta 2 contains factory bad block data
// Main + Meta 1 can only be written once
// Meta 2 can be written more than once
// Max of 4 programs per page per erase cycle
// DO NOT CLOBBER Factory Bad Block - do not erase pages where the first byte of the factory bad block area is 0x00.
// Use Meta1 to store page checksum and is-written information
//Start   End     Size   Region                  Number    HWECC
//0000    01FF    512    Main                    0         Y
//0200    03FF    512    Main                    1         Y
//0400    05FF    512    Main                    2         Y
//0600    07FF    512    Main                    3         Y
//0800    09FF    512    Main                    4         Y
//0A00    0BFF    512    Main                    5         Y
//0C00    0DFF    512    Main                    6         Y
//0E00    0FFF    512    Main                    7         Y
//1000    1003      4    Factory Bad Block       -         N
//1004    1007      4    Meta 2                  1         N
//1008    100B      4    Meta 2                  2         N
//100C    100F      4    Meta 2                  3         N
//1010    1013      4    Meta 2                  4         N
//1014    1017      4    Meta 2                  5         N
//1018    101B      4    Meta 2                  6         N
//101C    101F      4    Meta 2                  7         N
//1020    1023      4    Meta 2                  8         N
//1024    1027      4    Meta 2                  9         N
//1028    102B      4    Meta 2                  10        N
//102C    102F      4    Meta 2                  11        N
//1030    1033      4    Meta 2                  12        N
//1034    1037      4    Meta 2                  13        N
//1038    103B      4    Meta 2                  14        N
//103C    103F      4    Meta 2                  15        N
//1040    1047      8    Meta 1                  0         Y
//1048    104F      8    Meta 1                  1         Y
//1050    1057      8    Meta 1                  2         Y
//1058    105F      8    Meta 1                  3         Y
//1060    1067      8    Meta 1                  4         Y
//1068    106F      8    Meta 1                  5         Y
//1070    1077      8    Meta 1                  6         Y
//1078    107F      8    Meta 1                  7         Y
//1080    108F     16    ECC Main 0 + Meta 1.0   -         Y
//1090    109F     16    ECC Main 1 + Meta 1.1   -         Y
//10A0    10AF     16    ECC Main 2 + Meta 1.2   -         Y
//10B0    10BF     16    ECC Main 3 + Meta 1.3   -         Y
//10C0    10CF     16    ECC Main 4 + Meta 1.4   -         Y
//10D0    10DF     16    ECC Main 5 + Meta 1.5   -         Y
//10E0    10EF     16    ECC Main 6 + Meta 1.6   -         Y
//10F0    10FF     16    ECC Main 7 + Meta 1.7   -         Y

// bytes in a page
#define MT29F4G01_PAGE_STORAGE_SIZE 4096
#define MT29F4G01_PAGE_SPARE_SIZE   256
#define MT29F4G01_PAGE_TOTAL_SIZE   (MT29F4G01_PAGE_STORAGE_SIZE+MT29F4G01_PAGE_SPARE_SIZE)
// 64 pages in a block
#define MT29F4G01_BLOCK_PAGE_CNT    64
// 262144 (256K) bytes
#define MT29F4G01_BLOCK_STORAGE_SIZE        (MT29F4G01_PAGE_STORAGE_SIZE * MT29F4G01_BLOCK_PAGE_CNT)
// 2048 blocks in a flash device
#define MT29F4G01_FLASH_BLOCK_CNT   2048
//131,072 pages per flash
#define MT29F4G01_FLASH_PAGE_CNT    (MT29F4G01_FLASH_BLOCK_CNT * MT29F4G01_BLOCK_PAGE_CNT)
// 536,870,912 bytes
#define MT29F4G01_FLASH_STORAGE_SIZE        (MT29F4G01_BLOCK_STORAGE_SIZE * MT29F4G01_FLASH_BLOCK_CNT)

#define MT29F4G01_MF_ID      0x2CU
#define MT29F4G01_DEV_ID_3V3 0x34U
#define MT29F4G01_DEV_ID_1V8 0x35U

#define MT29F4G01_STARTUP_DELAY_US 2000

#define MT29F4G01_BLOCK_ERASE_TYP_US 2000
#define MT29F4G01_BLOCK_ERASE_MAX_US 10000

#define MT29F4G01_PROGRAM_PAGE_ECC_TYP_US 220
#define MT29F4G01_PROGRAM_PAGE_ECC_MAX_US 600

#define MT29F4G01_READ_PAGE_ECC_TYP_US 80
#define MT29F4G01_READ_PAGE_ECC_MAX_US 115

#define MT29F4G01_DATA_TO_CACHE_ECC_TYP_US 80
#define MT29F4G01_DATA_TO_CACHE_ECC_MAX_US 100

// Time to abort a running command using RESET(0xFF)
#define MT29F4G01_RESET_READ_ECC_MAX_US 120
#define MT29F4G01_RESET_PROG_ECC_MAX_US 125
#define MT29F4G01_RESET_ERASE_ECC_MAX_US 615

typedef enum {
    MT29F_CMD_RESET                  = 0xFFU,
    MT29F_CMD_GET_FEATURE            = 0x0FU,
    MT29F_CMD_SET_FEATURE            = 0x1FU,
    MT29F_CMD_READ_ID                = 0x9FU,

    MT29F_CMD_PAGE_READ              = 0x13U,
    MT29F_CMD_READ_PAGE_CACHE_RANDOM = 0x30U,
    MT29F_CMD_READ_PAGE_CACHE_LAST   = 0x3FU,
    MT29F_CMD_READ_CACHE1            = 0x03U,

    MT29F_CMD_WRITE_ENABLE           = 0x06U,
    MT29F_CMD_WRITE_DISABLE          = 0x04U,

    MT29F_CMD_PROGRAM_LOAD1          = 0x02U,
    MT29F_CMD_PROGRAM_LOAD_RANDOM1   = 0x84U,

    MT29F_CMD_PROGRAM_EXEC           = 0x10U,

    MT29F_CMD_BLOCK_ERASE            = 0xD8U
} MT29F_CMD;

typedef enum {
    MT29F_FEATURE_ADDR_BLOCK_LOCK = 0xA0U,
    MT29F_FEATURE_ADDR_CONFIG     = 0xB0U,
    MT29F_FEATURE_ADDR_STATUS     = 0xC0U,
    MT29F_FEATURE_ADDR_DIE_SEL    = 0xD0U
} MT29F_FEATURE_ADDR;

typedef enum {
    MT29F_ECC_STATUS_NO_ERROR      = 0x00U,
    MT29F_ECC_STATUS_CORRECTED_1_3 = 0x01U,
    MT29F_ECC_STATUS_CORRECTED_4_6 = 0x03U, // Data refresh recommended
    MT29F_ECC_STATUS_CORRECTED_7_8 = 0x05U, // Data refresh required
    MT29F_ECC_STATUS_NOT_CORRECTED = 0x02U  // Data is corrupted
} MT29F_ECC_STATUS;

// There are lots more codes to partially lock the flash, but for now just lock all and unlock all
typedef enum {
    MT29F_LOCKED_BLOCKS_ALL_UNLOCK = 0x00U,
    MT29F_LOCKED_BLOCKS_ALL_LOCK   = 0x7CU
} MT29F_LOCKED_BLOCKS;

typedef enum {
    MT29F_MAIN0 = 0x0000U,
    MT29F_META2 = 0x1000U,
    MT29F_META1 = 0x1040U,
    MT29F_ECC   = 0x1080U
} MT29F_PAGE_OFFSETS;

typedef struct
{
    uint32_t bad_blocks[MT29F4G01_FLASH_BLOCK_CNT / 32];
} Bad_Block_Set;

typedef struct
{
    uint32_t program_flag; // 0xFFFFFFFF means page free, all other values mean page is not free
    uint32_t main_length;  // user data in main section starts at col 0 and continues for this long
    uint32_t main_crc;     // TODO: some checksum on the user data
//    uint32_t meta1_crc;     // TODO: some checksum on the meta1 data
} MT29F_Page_Meta1;

typedef struct
{
    uint8_t main[4096];
    uint8_t meta2[64];
    uint8_t meta1[64];
    uint8_t ecc[128];
} MT29F_Page;

// write then read
// return 0 on success
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);
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);

int spi_write(uint32_t chipNum, uint8_t const * const tx_data, const size_t tx_data_len);

// API Function prototypes
void spi_flash_test(uint32_t testNum);
void spi_flash_power(uint32_t state);

// Helper functions
MT29F_ECC_STATUS get_ecc_from_status(const uint8_t status_reg);
uint32_t linearAddressToBlockPageAddress(uint32_t linUserAddress);
uint32_t linearAddressToColumnAddress(uint32_t linUserAddress);
uint32_t linearAddressToBlockNumber(uint32_t linUserAddress);
bool isAddrInBadBlock(Bad_Block_Set const *const block_info, uint32_t linUserAddress);
// Low level functions
// toggle CS internally
int spi_flash_cmd_readId(uint32_t chipNum, uint8_t* out_mfg_id, uint8_t* out_dev_id);
int spi_flash_cmd_getFeature(uint32_t chipNum, uint8_t feature_id, uint8_t* out_value);
int spi_flash_cmd_setFeature(uint32_t chipNum, uint8_t feature_id, uint8_t value);
int spi_flash_cmd_readPageToCache(uint32_t chipNum, uint32_t blockPageAddress);
int spi_flash_cmd_writeEnable(uint32_t chipNum);
int spi_flash_cmd_writeDisable(uint32_t chipNum);
int spi_flash_cmd_reset(uint32_t chipNum);
int spi_flash_cmd_readFromCache(uint32_t chipNum, uint32_t columnAddress, uint8_t* const out_cache_data, size_t out_data_len);
int spi_flash_cmd_blockErase(uint32_t chipNum, uint32_t blockPageAddress);
int spi_flash_cmd_programLoad(uint32_t chipNum, uint32_t columnAddress, uint8_t const * const page_data, size_t page_data_len);
int spi_flash_cmd_programLoadRandom(uint32_t chipNum, uint32_t columnAddress, uint8_t const * const page_data, size_t page_data_len);
int spi_flash_cmd_programExec(uint32_t chipNum, uint32_t blockPageAddress);

// Higher Level Functions
// Wait for status oip bit to clear
int spi_flash_wait_oip_clear(uint32_t chipNum, uint8_t* out_status_reg);
// Wait for status crbsy bit to clear
int spi_flash_wait_crbsy_clear(uint32_t chipNum, uint8_t* out_status_reg);
// Send reset to abort active command and wait until OIP is clear
int spi_flash_wait_reset(uint32_t chipNum, uint8_t* out_status_reg);
// Erase a block. Take care not to erase a bad block
int spi_flash_wait_blockErase(uint32_t chipNum, uint32_t block_id);

// Read and decode our metadata1 area
// this contains
// the actual length of user data in the main page area,
// a page-programmed flag,
// and possibly a checksum
int spi_flash_wait_readMeta1(uint32_t chipNum, uint32_t address, MT29F_Page_Meta1* const out_meta1);

// Check if a page has been programmed by looking at the meta1 metadata
// address is a multiple of the page size
int spi_flash_wait_isPageFree(uint32_t chipNum, uint32_t address, bool* const out_page_is_free);

// write to the block lock register and verify
int spi_flash_setLockedBlocks(uint32_t chipNum, MT29F_LOCKED_BLOCKS lockedBlocks);

// Read factory bad block marks
// Bad_Block_Set is kinda large so also can read one at a time
int spi_flash_readBadBlockSet(uint32_t chipNum, Bad_Block_Set* out_bad_blocks);
int spi_flash_readBadBlockInfo(uint32_t chipNum, uint32_t block_id, uint8_t* out_is_bad_block);

void spi_flash_open(void);                      // init SPI peripheral for operation, turn on power to spi_flash
void spi_flash_close(void);                     // deselect SPI peripheral, pins as GPIO for low power, turn off power to spi flash
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

int spi_flash_probe(uint32_t chipNum);         // read id

int spi_flash_bulkErase(uint32_t chipNum);     // use block erase to erase the entire device.

// address must start at a page interval, length must be less than a user page
int spi_flash_writePage(uint32_t chipNum, uint32_t address, uint8_t const * const data, uint32_t length);

// length must be less than a user page
// returned length is limited to actual programmed data in page per metadata and is returned in out_num_read
// will return <0 on error or ecc uncorrectable
int spi_flash_readPage(uint32_t chipNum, uint32_t address, uint8_t* const data, uint32_t max_length, uint32_t* out_num_read);

#ifdef NOCODE
Notes from datasheet

//The PAGE READ (13h) command transfers data from the NAND Flash array to the cache
register. It requires a 24-bit address consisting of 7 dummy bits and a 17-bit block/page
address. After the block/page address is registered, the device starts the transfer from
the main array to the cache register. During this data transfer busy time of tRD, the GET
FEATURE command can be issued to monitor the operation.
Following successful completion of PAGE READ, the READ FROM CACHE command
must be issued to read data out of cache. The command sequence is as follows to transfer data from array to output:
• 13h (PAGE READ command to cache)
• 0Fh (GET FEATURE command to read the status)
• 03h or 0Bh (READ FROM CACHE)

    READ FROM CACHE ×1 (03h or 0Bh)
    The READ FROM CACHE ×1 command enables sequentially reading one or more data
    bytes from the cache buffer. The command is initiated by driving CS# LOW, shifting in
    command opcode 03h/0Bh, followed by a 16-bit column address and 8-bit dummy
    clocks. Both the commands run at fast mode.
    Data is returned from the addressed cache buffer, MSB first, on SO at the falling edge of
    SCK. The address is automatically incremented to the next higher address after each
    byte of data is shifted out, enabling a continuous stream of data. This command is completed by driving CS# HIGH.


READ ID (9Fh)
READ ID reads the 2-byte identifier code programmed into the device, which includes
ID and device configuration data as shown in the table below
Byte 0 Manufacturer ID (Micron) 0 0 1 0 1 1 0 0 2Ch
Byte 1 4Gb 3.3V Device ID 0 0 1 1 0 1 0 0 34h
Byte 1 4Gb 1.8V Device ID 0 0 1 1 0 1 0 1 35h

PAGE PROGRAM (02h/10h)
A PAGE PROGRAM operation sequence enables the host to input 1 byte to 4352 bytes of
data within a page to a cache register, and moves the data from the cache register to the
specified block and page address in the array. If more than 4352 bytes are loaded, then
those additional bytes are ignored by the cache register.
The page program sequence is as follows:
• 06h (WRITE ENABLE command)
• 02h (PROGRAM LOAD command)
• 10h (PROGRAM EXECUTE command)
• 0Fh (GET FEATURE command to read the status)

       Prior to performing the PROGRAM LOAD operation, a WRITE ENABLE (06h) command
       must be issued. As with any command that changes the memory contents, the WRITE
       ENABLE command must be executed in order to set the WEL bit. WRITE ENABLE is followed by a PROGRAM LOAD (02h) command. The PROGRAM LOAD command consists
       of an 8-bit op code, followed by 3 dummy bits and a 13-bit column address, and then
       the data bytes to be programmed. The data bytes are loaded into a cache register that is
       4352 bytes long. Only four partial-page programs are allowed on a single page. If more
       than 4352 bytes are loaded, those additional bytes are ignored by the cache register. The
       command sequence ends when CS# goes from LOW to HIGH

       PROGRAM EXECUTE (10h)
       The PROGRAM EXECUTE command consists of an 8-bit op code, followed by a 24-bit
       address. After the page/block address is registered, the device starts the transfer from
       the cache register to the main array and is busy for tPROG time. During this busy time,
       the status register can be polled to monitor the status of the operation (refer to the status register section). When the operation completes successfully, the next series of data
       can be loaded with the PROGRAM LOAD command.


       Block Erase Operations
       The BLOCK ERASE (D8h) command is used to erase at the block level. The blocks are
       organized as 64 pages per block. The BLOCK ERASE (D8h) command operates on one
       block at a time. The command sequence for the BLOCK ERASE operation is as follows:
       • 06h (WRITE ENABLE command)
       • D8h (BLOCK ERASE command)
       • 0Fh (GET FEATURE command to read the status register

#endif

#endif /* SPI_FLASH_H_ */
