/* ********************************************
   Modul : eeprom.c

   Enthält alle Routinen zur Verwaltung des 24LC512
   E2PROMs

   Autor : Christian Julius
   ******************************************** */

#pragma GCC optimize ("O0")

/* ------------- INCLUDES ----------------- */
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>

// StdPeriph Lib + CMSIS
#include <stm32f4xx.h>
#include <stm32f4xx_i2c.h>

#include "eeprom.h"

/* ------------- DEFINES ------------------ */

#define E2P_DATA_VALID       0xaf     // Kennung für gültige Daten
#define E2P_DV_ADR           0xffff   // Adresse der Kennung

// Timeout und E2PROM Adresse
#define I2C_TIMEOUT_MAX         10000
#define MEM_DEVICE_WRITE_ADDR   0xA0
#define MEM_DEVICE_READ_ADDR    0xA1

//#define E2P_COMPARE_DATA

/* ----------- Private Functions ---------- */
static uint8_t e2p_WaitForWriteReady();
static uint8_t e2p_StartWriteAddress(uint16_t);
static uint8_t I2C_WaitForEvent(uint32_t);

/* ----------- Local Globals -------------- */
_Bool I2C_initialized = false;                  // Hardware eingestellt?
volatile uint32_t timeout = I2C_TIMEOUT_MAX;

/* ----------- Funktionen ----------------- */

/* /////////////////////////////////////////////////
    Schreibt den gesamten Work Struct ins E2PROM
    Eingabe: Zeiger auf Datenbereich, Anzahl Bytes
    Rückgabe: 0 = alles ok, 1 = Fehler
   ////////////////////////////////////////////// */

uint8_t e2p_Backup_Work(uint8_t *data, size_t nrBytes)
{
    if (!I2C_initialized) {
        e2p_Init_I2C(I2C_CHANNEL);
        I2C_initialized = true;
    }

    // Daten als ungültig markieren
    if (e2p_Write_Byte(E2P_DV_ADR,0xff) != SUCCESS)
        return ERROR;

    // Ganzen Struct wegschreiben
    if (e2p_PageWrite(data,E2P_DATA_START,nrBytes) != SUCCESS) {
        return ERROR;
    }

#ifdef E2P_COMPARE_DATA
    // Vergleiche die Daten, ob sie richtig geschrieben wurden
    uint16_t adr = E2P_DATA_START;
    for (uint16_t i = 0; i< nrBytes; i++) {
           if ( e2p_Read_Byte(adr++) != *(data++) ) {
               return ERROR;
           }
    }
#endif

    // Gültige Kennung einschreiben
    if (e2p_Write_Byte(E2P_DV_ADR,E2P_DATA_VALID) != SUCCESS)
        return ERROR;

    e2p_DeInit_I2C();

    return SUCCESS;
}

/* /////////////////////////////////////////////////
    Liest den Inhalt des E2Proms zurück in den Struct

    Eingabe: target = Zieladresse im Speicher
             nrBytes = Anzahl Bytes zu lesen

    Rückgabe: 0 = alles ok, 1 = Fehler
   ////////////////////////////////////////////// */

uint8_t e2p_Restore_Work(uint8_t *target, size_t nrBytes)
{
    if (!I2C_initialized) {
        e2p_Init_I2C(I2C_CHANNEL);
        I2C_initialized = true;
    }

    // Gültige Daten vorhanden?
    if (e2p_Read_Byte(E2P_DV_ADR) != E2P_DATA_VALID) {
            e2p_DeInit_I2C();
            return ERROR;
    }

    uint16_t cnt = E2P_DATA_START;
    for (uint16_t i = 0;i < nrBytes;i++) {
        *(target++) = e2p_Read_Byte(cnt++);
    }

//    // Kopierschleife
//    if (e2p_Read_MultiBytes(target,E2P_DATA_START,nrBytes)!= SUCCESS)
//            return ERROR;

    e2p_DeInit_I2C();
    return SUCCESS;
}


// ---------------------------------------------------------------
// Initialisiere das E2Prom
// ---------------------------------------------------------------
void e2p_Init_I2C()
{
    GPIO_InitTypeDef GPIO_InitStruct;       // Port Init Struct
    I2C_InitTypeDef I2C_InitStruct;         // I2C Init Struct

    // Peripherie Takt einschalten I2C3: PA8 (I2C SCL) und PC9 (I2C3_SDA)
    RCC_APB1PeriphClockCmd(I2C_RCC, ENABLE);                    // Enable APB1 peripheral clock für I2C3
    RCC_AHB1PeriphClockCmd(I2C_PERIPH_SCL_PORT, ENABLE);       // Enable clock für PortA SCL pin
    RCC_AHB1PeriphClockCmd(I2C_PERIPH_SDA_PORT, ENABLE);       // Enable clock für PortC SDA pin

    I2C_Cmd(I2C_CHANNEL, DISABLE);                              // I2C abschalten, damit Bus still bleibt

    // Port mit SCL einrichten
    GPIO_InitStruct.GPIO_Pin   = I2C_SCL_PIN;                // Pin A8 (I2C3_SCL)
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF;              // Pins als Alternate Function einstellen
    GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;           // IO Speed, nicht Baudrate, 2 Mhz reicht
    GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;             // Open Drain Output
    GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_NOPULL;
    GPIO_Init(I2C_SCL_PORT, &GPIO_InitStruct);

    // Port mit SDA einrichten
    GPIO_InitStruct.GPIO_Pin   = I2C_SDA_PIN;
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF;               // Pin C9 (SDA)
    GPIO_InitStruct.GPIO_Speed = GPIO_Fast_Speed;
    GPIO_InitStruct.GPIO_OType = GPIO_OType_OD;
    GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_NOPULL;
    GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct);

    // Pins sind nun auf AF verbunden
    // USART3 kann nun Kontrolle über Pins übernehmen
    GPIO_PinAFConfig(I2C_SCL_PORT, I2C_SCL_SOURCE, I2C_GPIO_AF);
    GPIO_PinAFConfig(I2C_SDA_PORT, I2C_SDA_SOURCE, I2C_GPIO_AF);

    // Struktur Parameter fuer I2C setzen
    I2C_StructInit(&I2C_InitStruct);                                        // Rücksetzen auf Default
    I2C_InitStruct.I2C_ClockSpeed           = 100000;                       // 100 khz Busfrequenz
    I2C_InitStruct.I2C_Mode                 = I2C_Mode_I2C;                 // Normaler I2C Mode
    I2C_InitStruct.I2C_DutyCycle            = I2C_DutyCycle_2;              // 50:50 Duty Cycle
    I2C_InitStruct.I2C_OwnAddress1          = 0xEE;
    I2C_InitStruct.I2C_Ack                  = I2C_Ack_Enable;               // ACK senden
    I2C_InitStruct.I2C_AcknowledgedAddress  = I2C_AcknowledgedAddress_7bit; // 7 Bit Adressierung

    // I2C einschalten
    I2C_Cmd(I2C_CHANNEL, ENABLE);

    // I2C3 mit den obigen Parametern konfigurieren
    I2C_Init(I2C_CHANNEL, &I2C_InitStruct);
}

// ---------------------------------------------------------------
// Setzt alle Einstellungen zurück auf Default
// ---------------------------------------------------------------
void e2p_DeInit_I2C()
{
    // I2C Einheit zurücksetzen
    I2C_DeInit(I2C_CHANNEL);
    RCC_APB1PeriphClockCmd(I2C_RCC, DISABLE);    // Enable APB1 peripheral clock für I2C3
    I2C_initialized = false;
}

// ---------------------------------------------------------------
// Beschreibe das E2PROM einzelnen mit mehreren Bytes
// ---------------------------------------------------------------
uint8_t e2p_WriteMultiBytes(uint16_t addr, uint8_t *data, size_t nrBytes)
{
    if (!I2C_initialized) {
         e2p_Init_I2C(I2C_CHANNEL);
         I2C_initialized = true;
    }

    for (uint16_t i = 0; i< nrBytes; i++) {
        if (e2p_Write_Byte(addr++,*(data++)))
            return ERROR;
    }

    return SUCCESS;
}

// ---------------------------------------------------------------
// Beschreibe das E2PROM mit einem Byte
// ---------------------------------------------------------------
uint8_t e2p_Write_Byte(uint16_t Addr, const uint8_t Data)
{
    if (!I2C_initialized) {
        e2p_Init_I2C(I2C_CHANNEL);
        I2C_initialized = true;
    }

    // Transmission starten
    e2p_StartWriteAddress(Addr);

    // Sende Datum
    I2C_SendData(I2C_CHANNEL, Data);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
        return ERROR;

    // Sende I2C3 STOP Condition
    I2C_GenerateSTOP(I2C_CHANNEL, ENABLE);

    // Warte bis fertig geschrieben
    if (e2p_WaitForWriteReady() != SUCCESS)
       return ERROR;

    return SUCCESS;
}

// ---------------------------------------------------------------
// Ein Byte aus E2PROM lesen
// ---------------------------------------------------------------
uint8_t e2p_Read_Byte(const uint16_t Addr)
{
    if (!I2C_initialized) {
        e2p_Init_I2C(I2C_CHANNEL);
        I2C_initialized = true;
    }

    // ACK ein
    I2C_AcknowledgeConfig(I2C_CHANNEL, ENABLE);

    // Transmission starten
    if (e2p_StartWriteAddress(Addr) != SUCCESS)
            return ERROR;

    // AF Flag evtl löschen
    I2C_CHANNEL->SR1 |= (uint16_t)0x0400;

    /* -------- E2PROM Lesemodus (MASTER Receiver Mode)------ */

   // Erzeuge START Bedingung
    I2C_GenerateSTART(I2C_CHANNEL, ENABLE);
    if (I2C_WaitForEvent( I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)
        return 0xff;

    // Adressiere das E2PROM
    I2C_Send7bitAddress(I2C_CHANNEL, MEM_DEVICE_READ_ADDR, I2C_Direction_Receiver);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS)
        return 0xff;

    /* Wichtiger Hinweis, bzgl der Reihenfolge des Bit Setzens, Manual. STM32F407, S834
        Die Konfiguration muss VOR dem Buszugriff erfolgen, d.h. das Datenbyte wird
        erst ganz zum Schluss ausgelesen */

    // NACK einstellen E2PROM anzuzeigen, dass keine weiteren Daten angefordert sind
    I2C_AcknowledgeConfig(I2C_CHANNEL, DISABLE);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS)
        return 0xff;

    // Stop Bedingung erzeugen NACH dem nächsten Lesezugriff
    I2C_GenerateSTOP(I2C_CHANNEL, ENABLE);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS)
        return 0xff;

    return I2C_ReceiveData(I2C_CHANNEL);
}

// ---------------------------------------------------------------
// Mehrere Bytes aus E2PROM lesen
// FIXMEEE !!! Läuft nicht !
// ---------------------------------------------------------------
uint8_t e2p_Read_MultiBytes(uint8_t *target, const uint16_t source, size_t const nrBytes)
{
    if (!I2C_initialized) {
        e2p_Init_I2C(I2C_CHANNEL);
        I2C_initialized = true;
    }

    // ACK ein
    I2C_AcknowledgeConfig(I2C_CHANNEL, ENABLE);

    // Transmission starten
    if (e2p_StartWriteAddress(source) != SUCCESS)
            return ERROR;

     // AF Flag evtl löschen
     I2C_CHANNEL->SR1 |= (uint16_t)0x0400;

    /* -------- E2PROM Lesemodus (MASTER Receiver Mode)------ */

   // Erzeuge START Bedingung
    I2C_GenerateSTART(I2C_CHANNEL, ENABLE);
    if (I2C_WaitForEvent( I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)
        return ERROR;

    // Adressiere das E2PROM
    I2C_Send7bitAddress(I2C_CHANNEL, MEM_DEVICE_READ_ADDR, I2C_Direction_Receiver);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) != SUCCESS)
        return ERROR;

    /* Wichtiger Hinweis, bzgl der Reihenfolge des Bit Setzens, Manual. STM32F407, S834
        Die Konfiguration muss VOR dem Buszugriff erfolgen, d.h. das Datenbyte wird
        erst ganz zum Schluss ausgelesen */
    uint8_t foo;
    for (uint16_t i = 0; i < (nrBytes-1); i++) {
            foo = I2C_ReceiveData(I2C_CHANNEL);
            *(target++) = foo;
    }

    // NACK einstellen E2PROM anzuzeigen, dass keine weiteren Daten angefordert sind
    I2C_AcknowledgeConfig(I2C_CHANNEL, DISABLE);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS)
        return ERROR;

    // Stop Bedingung erzeugen NACH dem nächsten Lesezugriff
    I2C_GenerateSTOP(I2C_CHANNEL, ENABLE);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_RECEIVED) != SUCCESS)
        return ERROR;

    *target = I2C_ReceiveData(I2C_CHANNEL);

    return SUCCESS;
}


// -----------------------------------------------------------------------
//   Schreibe viele Bytes .. aber nur an Pageadressen
//   Eingabe: Quelladresse im RAM, Zieladresse im E2P, Anzahl Bytes
//   Getestet: OK!
// -----------------------------------------------------------------------
uint8_t e2p_PageWrite(uint8_t *source, uint16_t target, uint16_t count) {

    #define BLOCKSIZE   128                 // je 128 Bytes wegschreiben

    if (!I2C_initialized) {
        e2p_Init_I2C(I2C_CHANNEL);
        I2C_initialized = true;
    }

    // ACK ein
    I2C_AcknowledgeConfig(I2C_CHANNEL, ENABLE);

    // Ist Zieladresse eine Pageadresse?
    if (target % 128 != 0) return ERROR;

    // Berechne die 64 Byte Blöcke
    uint16_t loops = count / BLOCKSIZE;
    uint16_t rest  = count - loops* BLOCKSIZE;

    // Sende die kompletten Blöcke
    for (uint16_t i = 0; i < loops; i++)
    {
        // Starte Übertragung
        if (e2p_StartWriteAddress(target) != SUCCESS)
            return ERROR;

        // Sende einen ganzen Block
        for (uint16_t k = 0;k < BLOCKSIZE; k++) {
                I2C_SendData(I2C_CHANNEL, *(source++));
                target++;
                if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
                    return ERROR;
        }

        I2C_GenerateSTOP(I2C_CHANNEL, ENABLE);
        if (e2p_WaitForWriteReady() != SUCCESS)
            return ERROR;
    }

    // Rest übertragen
    if (e2p_StartWriteAddress(target) != SUCCESS)
            return ERROR;

    for (uint16_t k = 0; k < rest; k++) {
        I2C_SendData(I2C_CHANNEL, *(source++));
        if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
           return ERROR;
    }

    I2C_GenerateSTOP(I2C_CHANNEL, ENABLE);
    if (e2p_WaitForWriteReady() != SUCCESS)
       return ERROR;

    return SUCCESS;
}

// ---------------------------------------------------------------
// Lösche das gesamte EEPROM mit 0x00
// ---------------------------------------------------------------
uint8_t e2p_Erase_EEPROM()
{
    #define BLOCKSIZE   128                 // je 128 Bytes wegschreiben
    #define E2P_BLOCKS  512

    if (!I2C_initialized) {
        e2p_Init_I2C(I2C_CHANNEL);
        I2C_initialized = true;
    }

    // ACK ein
    I2C_AcknowledgeConfig(I2C_CHANNEL, ENABLE);

    // Sende die kompletten Blöcke
    for (uint16_t i = 0; i < E2P_BLOCKS; i++)
    {
        // Starte Übertragung
        if (e2p_StartWriteAddress(i*BLOCKSIZE) != SUCCESS)
            return ERROR;

        // Lösche einen ganzen Block
        for (uint16_t k = 0;k < BLOCKSIZE; k++) {
                I2C_SendData(I2C_CHANNEL,0x00);
                if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
                    return ERROR;
        }

        I2C_GenerateSTOP(I2C_CHANNEL, ENABLE);

        // Warte bis der Write Cycle im E2Prom beendet wurde
        if (e2p_WaitForWriteReady() != SUCCESS)
            return ERROR;
    }

    return SUCCESS;
}


/* /////////////////////////////////////////////////////////
        Low Level Routinen für die Bedienung des E2PROMs
* /////////////////////////////////////////////////////// */


// Hilfsroutine zum Auslesen der Events mit Timeout
static uint8_t I2C_WaitForEvent(uint32_t event)
{
   timeout = I2C_TIMEOUT_MAX;
   while(I2C_CheckEvent(I2C_CHANNEL, event) != SUCCESS) {
        if ((timeout--) == 0) return ERROR;
    }
    return SUCCESS;
}

// ---------------------------------------------------------------
// Starte die E2PROM Sequenz: Startbit, E2prom Adresse, Zieladresse...
// ---------------------------------------------------------------
static uint8_t e2p_StartWriteAddress(uint16_t Addr)
{
    uint8_t lower_addr, upper_addr;

    lower_addr = (uint8_t)((0x00FF) & Addr);
    Addr = Addr>>8;
    upper_addr = (uint8_t)((0x00FF) & Addr);

    /* -------- E2PROM Adresszeiger auf Zieladresse setzen ------ */

    // Erzeuge START Bedingung
    I2C_GenerateSTART(I2C_CHANNEL, ENABLE);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_MODE_SELECT) != SUCCESS)
        return 0xff;

    // Adressiere das E2PROM
    I2C_Send7bitAddress(I2C_CHANNEL, MEM_DEVICE_WRITE_ADDR, I2C_Direction_Transmitter);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS)
        return 0xff;

    // Adresse als 2 x 8 Bit senden
    I2C_SendData(I2C_CHANNEL,upper_addr);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
        return 0xff;

    I2C_SendData(I2C_CHANNEL, lower_addr);
    if (I2C_WaitForEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED) != SUCCESS)
        return 0xff;

    return SUCCESS;
}

// ---------------------------------------------------------------
// Warte bis Schreibzyklus beendet ist (ACK Polling)
// ---------------------------------------------------------------
static uint8_t e2p_WaitForWriteReady() {

    // Warte bis Byte weggeschrieben wurde (Slave sendet solange kein ACK)
    uint32_t timeout = I2C_TIMEOUT_MAX;
    do{
        if (--timeout == 0)
            return ERROR;
        // Erzeuge Schreibzugriff......
        I2C_GenerateSTART(I2C_CHANNEL, ENABLE);
        while(!I2C_CheckEvent(I2C_CHANNEL, I2C_EVENT_MASTER_MODE_SELECT));
        I2C_Send7bitAddress(I2C_CHANNEL, MEM_DEVICE_WRITE_ADDR, I2C_Direction_Transmitter);
    } while(I2C_WaitForEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) != SUCCESS);

    // Sequenz abschliessen
    I2C_GenerateSTOP(I2C_CHANNEL, ENABLE);

    return SUCCESS;
}
