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

   Autor : Christian Julius
   ******************************************** */

/* ------------- INCLUDES ----------------- */
#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>

// StdPeriph Lib + CMSIS
#include <stm32f4xx.h>
#include <stm32f4xx_i2c.h>

#include "eeprom.h"

/* ------------- DEFINES ------------------ */

// 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 ----------------- */

// ---------------------------------------------------------------
// Initialisiere das E2Prom
// ---------------------------------------------------------------
void __attribute__((optimize(0))) 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)

    if (I2C_CHANNEL == I2C1)
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);    // Enable APB1 peripheral clock für I2C1
    if (I2C_CHANNEL == I2C2)
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);    // Enable APB1 peripheral clock für I2C2
    if (I2C_CHANNEL == I2C3)
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C3, 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_Speed_2MHz;           // 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_Speed_2MHz;
    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);

    if (I2C_CHANNEL == I2C1)
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, DISABLE);    // Enable APB1 peripheral clock für I2C3
    if (I2C_CHANNEL == I2C2)
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, DISABLE);    // Enable APB1 peripheral clock für I2C3
    if (I2C_CHANNEL == I2C3)
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C3, 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 __attribute__((optimize(0))) 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 __attribute__((optimize(0))) 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
//
// ---------------------------------------------------------------
uint8_t __attribute__((optimize(0))) 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
// -----------------------------------------------------------------------
uint8_t __attribute__((optimize(0))) 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 __attribute__((optimize(0))) 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 __attribute__((optimize(0))) 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 __attribute__((optimize(0))) 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 __attribute__((optimize(0))) 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;
}
