/*  -*-  mode: c; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4; coding: utf-8  -*-  */
/************************************************************************************
 **                                                                                 **
 **                               mcHF QRP Transceiver                              **
 **                             K Atanassov - M0NKA 2014                            **
 **                                                                                 **
 **---------------------------------------------------------------------------------**
 **                                                                                 **
 **  File name:                                                                     **
 **  Description:                                                                   **
 **  Last Modified:                                                                 **
 **  Licence:       GNU GPLv3                                                      **
 ************************************************************************************/

// Common
#include "uhsdr_board.h"
#include "uhsdr_hw_i2c.h"

#include "serial_eeprom.h"

// for MAX_VAR_ADDR only
#include "ui_configuration.h"

typedef struct SerialEEPROM_Configuration
{
    uint8_t device_type;
    uint8_t use_type;
    bool    detection;
} SerialEEPROM_Configuration_t;

// static SerialEEPROM_Configuration_t ser_eeprom_config;

const SerialEEPROM_EEPROMTypeDescriptor SerialEEPROM_eepromTypeDescs[SERIAL_EEPROM_DESC_NUM] = {
        // 0
        {
                .size = 0,
                .supported = false,
                .pagesize = 0,
                .name = "No EEPROM"
        },
        // 1
        {
                .size = 0,
                .supported = false,
                .pagesize = 0,
                .name = "Wrong Signature"
        },
        // 2
        {
                .size = 0,
                .supported = false,
                .pagesize = 0,
                .name = "Unknown Type"
        },
        // 3
        {
                .size = 0,
                .supported = false,
                .pagesize = 0,
                .name = "Not used"
        },
        // 4
        {
                .size = 0,
                .supported = false,
                .pagesize = 64,
                .name = "Not used"
        },
        // 5
        {
                .size = 0,
                .supported = false,
                .pagesize = 0,
                .name = "Not used"
        },
        // 6
        {
                .size = 0,
                .supported = false,
                .pagesize = 0,
                .name = "Not used"
        },
        // 7
        {
                .size = 128,
                .supported = false,
                .pagesize = 8,
                .name = "24xx01"
        },
        // 8
        {
                .size = 256,
                .supported = false,
                .pagesize = 8,
                .name = "24xx02"
        },
        // 9
        {
                .size = 2*256,
                .supported = false,
                .pagesize = 16,
                .name = "24xx04"
        },
        // 10
        {
                .size = 1*1024,
                .supported = false,
                .pagesize = 16,
                .name = "24xx08"
        },
        // 11
        {
                .size = 2*1024,
                .supported = false,
                .pagesize = 16,
                .name = "24xx16"
        },
        // 12
        {
                .size = 4*1024,
                .supported = false,
                .pagesize = 32,
                .name = "24xx32"
        },
        // 13
        {
                .size = 8*1024,
                .supported = false,
                .pagesize = 32,
                .name = "24xx64"
        },
        // 14
        {
                .size = 16*1024,
                .supported = false,
                .pagesize = 64,
                .name = "24xx128"
        },
        // 15
        {
                .size = 32*1024,
                .supported = true,
                .pagesize = 64,
                .name = "24xx256"
        },
        // 16
        {
                .size = 64*1024,
                .supported = true,
                .pagesize = 128,
                .name = "24xx512"
        },
        // 17
        {
                .size = 128*1024,
                .supported = true,
                .pagesize = 128,
                .name = "24xx1025"
        },
        // 18
        {
                .size = 128 * 1024,
                .supported = true,
                .pagesize = 128,
                .name = "24xx1026/CM01"
        },
        // 19
        {
                .size = 256*1024,
                .supported = true,
                .pagesize = 256,
                .name = "24CM02"
        }
};

typedef struct
{
    uint8_t devaddr;
    uint16_t addr;
    uint16_t addr_size;
} SerialEEPROM_24CXX_Descriptor;

static SerialEEPROM_24CXX_Descriptor serialEeprom_desc;
// THIS CAN BE USED ONLY WITH SINGLE EEPROM AND SINGLE THREAD
// NOT THREAD SAFE, USE local variable instead then



#define MEM_DEVICE_WRITE_ADDR 0xA0
// serial eeprom functions by DF8OE

static uint16_t SerialEEPROM_24Cxx_DeviceConnected(void)
{
    uint16_t retVal = UhsdrHw_I2C_DeviceReady(&hi2c2,MEM_DEVICE_WRITE_ADDR);
    return retVal;
}


static void SerialEEPROM_24Cxx_AdjustAddrs(const uint8_t Mem_Type, uint8_t* devaddr_ptr, uint32_t* Addr_ptr)
{

    *devaddr_ptr = MEM_DEVICE_WRITE_ADDR;

    if (*Addr_ptr > 0xFFFF) {
        switch (Mem_Type)
        {
        case 17: // 24LC1025
            *devaddr_ptr = MEM_DEVICE_WRITE_ADDR + 8;            // 24LC1025
            break;
        case  18:
            *devaddr_ptr = MEM_DEVICE_WRITE_ADDR + 2;            // 24LC1026
            break;
        case  19: // 24CM02
            *devaddr_ptr = MEM_DEVICE_WRITE_ADDR + ((*Addr_ptr & 0x30000) >> 15); // the upper bits 16 and 17 determine the I2C address offset
            break;
        }
        *Addr_ptr &= 0xFFFF; // mask address to 16bits in all cases
    }
}

static uint16_t SerialEEPROM_24Cxx_ackPolling(uint32_t Addr, uint8_t Mem_Type)
{
    uint8_t devaddr;

    SerialEEPROM_24Cxx_AdjustAddrs(Mem_Type,&devaddr,&Addr);

    uint16_t retVal = UhsdrHw_I2C_DeviceReady(&hi2c2,devaddr); // != HAL_OK?0xFD00:0;
    return retVal;
}

static void SerialEEPROM_24Cxx_StartTransfer_Prep(uint32_t Addr, uint8_t Mem_Type, SerialEEPROM_24CXX_Descriptor* eeprom_desc_ptr)
{
    SerialEEPROM_24Cxx_AdjustAddrs(Mem_Type,&eeprom_desc_ptr->devaddr,&Addr);

    eeprom_desc_ptr->addr = (uint16_t)((0xFFFF)&(Addr));
    if (Mem_Type > 8)
    {
        eeprom_desc_ptr->addr_size = I2C_MEMADD_SIZE_16BIT;
    }
    else
    {
        eeprom_desc_ptr->addr_size = I2C_MEMADD_SIZE_8BIT;
    }
}

uint16_t SerialEEPROM_24Cxx_Write(uint32_t Addr, uint8_t Data, uint8_t Mem_Type)
{
    SerialEEPROM_24Cxx_StartTransfer_Prep(Addr, Mem_Type,&serialEeprom_desc);
    uint16_t retVal = UhsdrHw_I2C_WriteRegister(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,Data);

    if (!retVal)
    {
        retVal = SerialEEPROM_24Cxx_ackPolling(Addr,Mem_Type);
    }

    return retVal;
}


uint16_t SerialEEPROM_24Cxx_Read(uint32_t Addr, uint8_t Mem_Type)
{
    uint8_t value;
    SerialEEPROM_24Cxx_StartTransfer_Prep(Addr, Mem_Type,&serialEeprom_desc);
    uint16_t retVal = UhsdrHw_I2C_ReadRegister(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,&value);
    if (!retVal)
    {
        retVal = value;
    }
    return retVal;
}

uint16_t SerialEEPROM_24Cxx_ReadBulk(uint32_t Addr, uint8_t *buffer, uint16_t length, uint8_t Mem_Type)
{
    uint16_t retVal = 0xFFFF;
    if (Mem_Type < SERIAL_EEPROM_DESC_NUM) {
        uint32_t page, count;
        count = 0;

        page =SerialEEPROM_eepromTypeDescs[Mem_Type].pagesize;

        while(count < length)
        {
            if (length - count < page)
            {
                // make sure we do not read more than asked for! Buffer Overflow!
                page = (length - count);
            }
            SerialEEPROM_24Cxx_StartTransfer_Prep(Addr + count, Mem_Type,&serialEeprom_desc);
            retVal = UhsdrHw_I2C_ReadBlock(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,&buffer[count],page);
            count+=page;
            if (retVal)
            {
                break;
            }
        }
    }
    return retVal;
}

/**
 * Write a consecutive area of memory into the I2C eeprom starting with Addr.
 * @param Addr  the location of the first byte in the eeprom. May have any value within the memory size of  the eeprom
 * @param buffer memory to copy from
 * @param length how many bytes to copy
 * @param Mem_Type which eeprom type are we having
 * @return 0 if success, I2C error code otherwise
 */
uint16_t SerialEEPROM_24Cxx_WriteBulk(uint32_t Addr, const uint8_t *buffer, uint16_t length, uint8_t Mem_Type)
{
    uint16_t retVal = 0;
    if (Mem_Type < SERIAL_EEPROM_DESC_NUM) {
        uint32_t count = 0;

        const uint32_t page = SerialEEPROM_eepromTypeDescs[Mem_Type].pagesize;


        while(retVal == 0 && count < length)
        {
            SerialEEPROM_24Cxx_StartTransfer_Prep(Addr + count, Mem_Type,&serialEeprom_desc);

            uint32_t transfer_size = page;

            // correct the transfer_size to keep inside a single page
            // if the start address is not on a page boundary
            // this will happen only for the first bulkd write (if at all)
            // then the Addr + count will be aligned on page boundaries

            transfer_size -= ((Addr+count) % page);

            if (length - count < transfer_size)
            {
                transfer_size = length - count;
            }

            retVal = UhsdrHw_I2C_WriteBlock(SERIALEEPROM_I2C,serialEeprom_desc.devaddr,serialEeprom_desc.addr,serialEeprom_desc.addr_size,&buffer[count],transfer_size);
            count+=transfer_size;
            if (retVal)
            {
                break;
            }
            retVal = SerialEEPROM_24Cxx_ackPolling(Addr,Mem_Type);
        }
    }
    return retVal;
}

/**
 * @brief in general a non-destructive probing to identify the used EEPROM, in some cases devices are written (and changed data restored)
 * this code uses hardware properties to identify the connected I2C eeprom, no previously signature etc. used or required
 * @returns identified eeprom chip type or EEPROM_SER_UNKNOWN
 */
static uint8_t SerialEEPROM_24Cxx_DetectProbeHardware(void)
{
    uint8_t ser_eeprom_type = EEPROM_SER_UNKNOWN;

    const uint8_t testsignatureA = 0x66;
    const uint8_t testsignatureB = 0x77; // has to be different from testsignature 1

    const uint16_t testaddr1 = 0x0001;
    const uint16_t testaddr1plus128 = testaddr1 + 0x80;

    // 8 Bit Test First
    uint16_t test8Byte1 = SerialEEPROM_24Cxx_Read(testaddr1,8);


    // first decide if 8 or 16 bit addressable eeprom by trying to read with 8 or 16 bit algorithm
    // if an algorithm succeeds we know the address width
    if(test8Byte1 < 0x100)
    {
        // 8 bit addressing
        uint16_t test8Byte2 = SerialEEPROM_24Cxx_Read(testaddr1plus128,8);

        SerialEEPROM_24Cxx_Write(testaddr1,testsignatureB,8);
        SerialEEPROM_24Cxx_Write(testaddr1plus128,testsignatureB,8);

        if (SerialEEPROM_24Cxx_Read(testaddr1,8) == testsignatureB)
        {
            SerialEEPROM_24Cxx_Write(testaddr1,testsignatureA,8);              // write test signature
            ser_eeprom_type = 7;             // smallest possible 8 bit EEPROM (128Bytes)
            if(SerialEEPROM_24Cxx_Read(testaddr1plus128,8) == testsignatureB)
            {
                ser_eeprom_type = 8;
            }
        }
        SerialEEPROM_24Cxx_Write(testaddr1,test8Byte1,8);         // write back old data
        SerialEEPROM_24Cxx_Write(testaddr1plus128,test8Byte2,8);
    }
    if (ser_eeprom_type == EEPROM_SER_UNKNOWN)
    {
        // 16 bit addressing check
        // the other banks are mapped to different I2C
        // device addresses. We simply try to read from them
        // and if it succeeds we know the device type
        // We need to check unique addresses for each EEPROM type
        // 0xA0 + 0x10000: 0xA8
        if(SerialEEPROM_24Cxx_Read(0x10000,17) < 0x100)
        {
            ser_eeprom_type = 17;            // 24LC1025
        }
        // 0xA0 + 0x10000: 0xA2
        if(SerialEEPROM_24Cxx_Read(0x10000,18) < 0x100)
        {
            ser_eeprom_type = 18;            // 24LC1026
        }
        // 0xA0 + 0x10000: 0xA2 + 0x20000: 0xA4 + 0x30000: 0xA6
        if(SerialEEPROM_24Cxx_Read(0x20000,19) < 0x100)
        {
            ser_eeprom_type = 19;            // 24CM02
        }

        // it is not a large EEPROM (i.e. >64KB Data)
        //
        // the following test requires writing to EEPROM,
        // we remember content of test locations
        // and write them back later
        if(ser_eeprom_type == EEPROM_SER_UNKNOWN)
        {
            const uint16_t testaddr1plus256 = 0x0100 + testaddr1; // same as before but 0x100 == 256 byte spacing
            uint16_t testByte1 = SerialEEPROM_24Cxx_Read(testaddr1,16);         // read old content
            uint16_t testByte2 = SerialEEPROM_24Cxx_Read(testaddr1plus256,16);     // of test bytes

            if (testByte1 < 0x100 && testByte2 < 0x100) {

                // successful read from both locations, let us start
                SerialEEPROM_24Cxx_Write(testaddr1, testsignatureA,16);         // write testsignature 1
                SerialEEPROM_24Cxx_Write(testaddr1plus256, testsignatureB,16);         // write testsignature 2
                if(SerialEEPROM_24Cxx_Read(testaddr1,16) == testsignatureA && SerialEEPROM_24Cxx_Read(testaddr1plus256,16) == testsignatureB)
                {
                    // 16 bit addressing
                    ser_eeprom_type = 9;         // smallest possible 16 bit EEPROM

                    // we look for the "looping" in data, i.e. we read back 0x66
                    // which we wrote to address  0x0003
                    // from a address we did not wrote it to
                    // once it occurs, we know the  size of the EEPROM
                    // since all of these EEPROMS  are ignoring the unused bits in  the address
                    for (int shift = 0; shift < 8; shift++) {
                        uint16_t shift_test_addr = (0x200 << shift)+testaddr1;
                        if(SerialEEPROM_24Cxx_Read(shift_test_addr,16) == testsignatureA) {
                            // now we write test signature 2 to make sure the match was no accident
                            // i.e. match with same content in EEPROM
                            SerialEEPROM_24Cxx_Write(shift_test_addr,testsignatureB,16);
                            if(SerialEEPROM_24Cxx_Read(testaddr1,16) == testsignatureB) {
                                // we found the looping,
                                // now stop checking and set EEPROM type
                                ser_eeprom_type = 9 + shift;
                                break;
                            }
                            else
                            {
                                // oops: just an accident, so we write back old content and continue
                                SerialEEPROM_24Cxx_Write(shift_test_addr,testsignatureA,16);
                            }
                        }
                    }

                }
                SerialEEPROM_24Cxx_Write(testaddr1,testByte1,16);         // write back old data
                SerialEEPROM_24Cxx_Write(testaddr1plus256,testByte2,16);
            }
        }
    }
    return ser_eeprom_type;
}

static void SerialEEPROM_Read_Signature(uint8_t ser_eeprom_type, uint16_t* type_p, uint16_t* state_p)
{
    *type_p = SerialEEPROM_24Cxx_Read(0,ser_eeprom_type);
    *state_p = SerialEEPROM_24Cxx_Read(1,ser_eeprom_type);
}

uint16_t SerialEEPROM_Set_UseStateInSignature(uint8_t state)
{
    return SerialEEPROM_24Cxx_Write(1,SER_EEPROM_IN_USE,ts.ser_eeprom_type);
}

uint16_t SerialEEPROM_Get_UseStateInSignature()
{
    return SerialEEPROM_24Cxx_Read(1,ts.ser_eeprom_type);
}

uint8_t SerialEEPROM_Detect() {

    uint16_t ser_eeprom_type = EEPROM_SER_UNKNOWN;

    // serial EEPROM init
    if(SerialEEPROM_24Cxx_DeviceConnected() != HAL_OK || SerialEEPROM_24xx_Exists() == false)  // Issue with Ser EEPROM, either not available or other problems
    {
        ser_eeprom_type = EEPROM_SER_NONE;             // no serial EEPROM available

    }
    else
    {

        // this initial read will bring us the first byte of any eeprom (8 or 16bits)
        // if we read anything but the 0xff == SER_EERPM_NOT_IN_USE value which indicates empty eeprom
        // we assume some configuration information tells us what EEPROM we have and how it is used
        if(SerialEEPROM_24Cxx_Read(0,16) != SER_EEPROM_NOT_IN_USE)
        {
            ser_eeprom_type = EEPROM_SER_WRONG_SIG;

            // unless we find a correct signature, we have to assume the EEPROM has incorrect/corrupted data

            uint16_t ser_eeprom_type_read;
            uint16_t ser_eeprom_sig;

            SerialEEPROM_Read_Signature(8, &ser_eeprom_type_read, &ser_eeprom_sig);

            // all 8 bit (i.e. 256 or 128 Byte) EEPROMS are marked as "too small" during detection
            if(ser_eeprom_sig == SER_EEPROM_TOO_SMALL && ser_eeprom_type_read >= SERIAL_EEPROM_DESC_REAL && ser_eeprom_type_read < 9)
            {
                ser_eeprom_type = ser_eeprom_type_read;
            }
            else
            {
                SerialEEPROM_Read_Signature(16, &ser_eeprom_type_read, &ser_eeprom_sig);

                // we either have a new EEPROM, just being initialized  ( ser_eeprom_sig = SER_EEPROM_NOT_IN_USE && valid type) or
                // we have an used EEPROM (ser_eeprom_sig = SER_EEPROM_IN_USE && valid type)
                // please note, that even though no 16 bit EEPROM gets the "too small" signature written in, these may still be
                // consider too small by the later code. Only EEPROMS which have the "supported" flag set in the descriptor will
                // be consider of sufficient size for actual use
                if((ser_eeprom_sig == SER_EEPROM_IN_USE || ser_eeprom_sig == SER_EEPROM_NOT_IN_USE) && ser_eeprom_type_read < SERIAL_EEPROM_DESC_NUM && ser_eeprom_type_read > 8)
                {
                    ser_eeprom_type = ser_eeprom_type_read;
                }
            }
        }
        else
        {
            ser_eeprom_type = SerialEEPROM_24Cxx_DetectProbeHardware();

            if (ser_eeprom_type >= SERIAL_EEPROM_DESC_REAL && ser_eeprom_type < SERIAL_EEPROM_DESC_NUM)
            {
                SerialEEPROM_24Cxx_Write(0,ser_eeprom_type,ser_eeprom_type);
                if (SerialEEPROM_eepromTypeDescs[ser_eeprom_type].supported == false)
                {
                    SerialEEPROM_24Cxx_Write(1,SER_EEPROM_TOO_SMALL,ser_eeprom_type);
                }
            }
        }
        // just to be save. Never ever deliver a type id outside the array boundaries.
        if (ser_eeprom_type >= SERIAL_EEPROM_DESC_NUM)
        {
            ser_eeprom_type = EEPROM_SER_UNKNOWN;
        }
    }

    return ser_eeprom_type;

}

static void SerialEEPROM_Clear_Variable(uint16_t addr)
{
        const uint8_t empty_var[2] = { 0xff, 0xff };
        SerialEEPROM_24Cxx_WriteBulk(addr*2, empty_var,2, ts.ser_eeprom_type);
}

void  SerialEEPROM_Clear_AllVariables()
{
    // variable 0 is the reserved signature variable
    for(uint16_t count=1; count <= MAX_VAR_ADDR; count++)
    {
        SerialEEPROM_Clear_Variable(count);
    }
}

void  SerialEEPROM_Clear_Signature()
{
    // variable 0 is the reserved signature variable
    ts.ser_eeprom_type = SerialEEPROM_24Cxx_DetectProbeHardware();
    SerialEEPROM_Clear_Variable(0);
}


bool SerialEEPROM_24xx_Exists()
{
    return SerialEEPROM_24Cxx_Read(0,8)  < 0x100;
}


//
// Interface for serial EEPROM functions
//
uint16_t SerialEEPROM_ReadVariable(uint16_t addr, uint16_t *value)      // reference to serial EEPROM read function
{
    uint8_t bytes[2];


    uint16_t retval = SerialEEPROM_24Cxx_ReadBulk(addr*2,bytes, 2, ts.ser_eeprom_type);

    if (retval == HAL_OK)
    {
        *value =  ((uint16_t)bytes[0])<<8;
        *value |= bytes[1];
    }

    return retval;
}

uint16_t SerialEEPROM_UpdateVariable(uint16_t addr, uint16_t value)
{
        uint16_t value_read = 0;
        uint16_t retval = SerialEEPROM_ReadVariable(addr,&value_read);
        if  (retval != 0 || value_read != value )
        {
            retval = SerialEEPROM_WriteVariable(addr,value);
        }
        return retval;
}

uint16_t SerialEEPROM_WriteVariable(uint16_t addr, uint16_t value)      // reference to serial EEPROM write function, writing unsigned 16 bit
{
    uint8_t bytes[2];

    bytes[0] = (uint8_t)((value&(0xFF00))>>8);
    bytes[1] = (uint8_t)(value&(0x00FF));

    return SerialEEPROM_24Cxx_WriteBulk(addr*2, bytes, 2, ts.ser_eeprom_type);
}
