/* flaneth - flash and ethernet
   Copyright (C) 2007-2012 Stefan Schuermans <stefan@schuermans.info>
   Copyleft: GNU public license V2 - http://www.gnu.org/copyleft/gpl.html
   a BlinkenArea project - http://www.blinkenarea.org/ */

#include <avr/interrupt.h>
#include <avr/eeprom.h>

#include "config.h"
#include "debug.h"
#include "eeprom.h"
#include "macros.h"
#include "rtl8019.h"

// flags for what needs to be written to the EEPROM
unsigned char EepromNeedWrite = 0x00;   // (extern)

// writing what where
unsigned char EepromWriteFlags = 0;     // some flags
#define EepromWriteFlagActive 0x01      // if writing something to EEPROM
#define EepromWriteFlagCheck 0x02       // if writing check byte to EEPROM
unsigned char *pEepromWriteSrc; // pointer to source data
unsigned short EepromWriteDestAddr;     // destination address in EEPROM
unsigned char EepromWriteCnt;   // number of bytes still to write

// memory layout of EEPROM
// - every byte B is stored as B, ~B for data safety
// - so everythings needs double space
#define EEPROM_ADDR_MAC 0
#define EEPROM_ADDR_IP (EEPROM_ADDR_MAC + 2 * sizeof( ConfigMac ))
#define EEPROM_ADDR_MASK (EEPROM_ADDR_IP + 2 * sizeof( ConfigIp ))
#define EEPROM_ADDR_GW (EEPROM_ADDR_MASK + 2 * sizeof( ConfigMask ))
#define EEPROM_ADDR_S8P_PORT (EEPROM_ADDR_GW + 2 * sizeof( ConfigGw ))
#define EEPROM_ADDR_S8P_KEY (EEPROM_ADDR_S8P_PORT + 2 * sizeof( ConfigS8pPort ))
#define EEPROM_ADDR_HTTP_PORT (EEPROM_ADDR_S8P_KEY + 2 * sizeof( ConfigS8pKey ))
#define EEPROM_ADDR_DEF_OUT (EEPROM_ADDR_HTTP_PORT + 2 * sizeof( ConfigHttpPort ))
#define EEPROM_ADDR_FreeFromHere (EEPROM_ADDR_DEF_OUT + 2 * sizeof( ConfigDefOut ))

// get configuration from EEPROM - helper function
// checks data in EEPROM
// gets data from EEPROM
// returns 0x00 if successful
static inline unsigned char EepromGetConfigHelp(unsigned short SrcAddr,
                                                unsigned char *pDest,
                                                unsigned char Len)
{
  unsigned char i, b;

  // check data in EEPROM
  EEAR = SrcAddr;
  for (i = 0; i < Len; i++) {
    // read byte
    bit_set(EECR, EERE);
    b = EEDR;
    EEAR++;
    // XOR in check byte
    bit_set(EECR, EERE);
    b ^= EEDR;
    EEAR++;
    // XOR result must be 0xFF
    if (b != 0xFF)
      return 0x01;      // error
  }

  // read data from EEPROM
  EEAR = SrcAddr;
  for (; Len > 0; Len--) {
    // read byte
    bit_set(EECR, EERE);
    *(pDest++) = EEDR;
    EEAR += 2;  // skip check byte
  }
  return 0x00;  // success
}

// get configuration from EEPROM
void EepromGetConfig(void)      // (extern)
{
  // get MAC
  if (EepromGetConfigHelp(EEPROM_ADDR_MAC, ConfigMac, sizeof(ConfigMac)) ==
      0x00)
    // re-initialize RTL8019
    RtlReinit();

  // get IP, subnet mask, gateway IP
  EepromGetConfigHelp(EEPROM_ADDR_IP, ConfigIp, sizeof(ConfigIp));
  EepromGetConfigHelp(EEPROM_ADDR_MASK, ConfigMask, sizeof(ConfigMask));
  EepromGetConfigHelp(EEPROM_ADDR_GW, ConfigGw, sizeof(ConfigGw));

  // get port for S8P
  EepromGetConfigHelp(EEPROM_ADDR_S8P_PORT, (unsigned char *)&ConfigS8pPort,
                      sizeof(ConfigS8pPort));

  // get symmetric key for S8P
  EepromGetConfigHelp(EEPROM_ADDR_S8P_KEY, ConfigS8pKey,
                      sizeof(ConfigS8pKey));

  // get port for webserver
  EepromGetConfigHelp(EEPROM_ADDR_HTTP_PORT, (unsigned char *)&ConfigHttpPort,
                      sizeof(ConfigHttpPort));

  // get default output state
  EepromGetConfigHelp(EEPROM_ADDR_DEF_OUT, &ConfigDefOut,
                      sizeof(ConfigDefOut));
}

// task function to do the work - call from main loop
void EepromTask(void)   // (extern)
{
  // not writing something to EEPROM
  if (!(EepromWriteFlags & EepromWriteFlagActive)) {
    // MAC needs to be written
    if (EepromNeedWrite & EepromNeedWriteMac) {
      EepromNeedWrite &= ~EepromNeedWriteMac;
      pEepromWriteSrc = ConfigMac;
      EepromWriteDestAddr = EEPROM_ADDR_MAC;
      EepromWriteCnt = sizeof(ConfigMac);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // IP needs to be written
    else if (EepromNeedWrite & EepromNeedWriteIp) {
      EepromNeedWrite &= ~EepromNeedWriteIp;
      pEepromWriteSrc = ConfigIp;
      EepromWriteDestAddr = EEPROM_ADDR_IP;
      EepromWriteCnt = sizeof(ConfigIp);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // subnet mask needs to be written
    else if (EepromNeedWrite & EepromNeedWriteMask) {
      EepromNeedWrite &= ~EepromNeedWriteMask;
      pEepromWriteSrc = ConfigMask;
      EepromWriteDestAddr = EEPROM_ADDR_MASK;
      EepromWriteCnt = sizeof(ConfigMask);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // gateway IP needs to be written
    else if (EepromNeedWrite & EepromNeedWriteGw) {
      EepromNeedWrite &= ~EepromNeedWriteGw;
      pEepromWriteSrc = ConfigGw;
      EepromWriteDestAddr = EEPROM_ADDR_GW;
      EepromWriteCnt = sizeof(ConfigGw);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // port for S8P needs to be written
    else if (EepromNeedWrite & EepromNeedWriteS8pPort) {
      EepromNeedWrite &= ~EepromNeedWriteS8pPort;
      pEepromWriteSrc = (unsigned char *)&ConfigS8pPort;
      EepromWriteDestAddr = EEPROM_ADDR_S8P_PORT;
      EepromWriteCnt = sizeof(ConfigS8pPort);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // symmetric key for S8P needs to be written
    else if (EepromNeedWrite & EepromNeedWriteS8pKey) {
      EepromNeedWrite &= ~EepromNeedWriteS8pKey;
      pEepromWriteSrc = ConfigS8pKey;
      EepromWriteDestAddr = EEPROM_ADDR_S8P_KEY;
      EepromWriteCnt = sizeof(ConfigS8pKey);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // port for webserver needs to be written
    else if (EepromNeedWrite & EepromNeedWriteHttpPort) {
      EepromNeedWrite &= ~EepromNeedWriteHttpPort;
      pEepromWriteSrc = (unsigned char *)&ConfigHttpPort;
      EepromWriteDestAddr = EEPROM_ADDR_HTTP_PORT;
      EepromWriteCnt = sizeof(ConfigHttpPort);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // if default output state needs to be written
    else if (EepromNeedWrite & EepromNeedWriteDefOut) {
      EepromNeedWrite &= ~EepromNeedWriteDefOut;
      pEepromWriteSrc = &ConfigDefOut;
      EepromWriteDestAddr = EEPROM_ADDR_DEF_OUT;
      EepromWriteCnt = sizeof(ConfigDefOut);
      EepromWriteFlags = EepromWriteFlagActive;
    }
    // nothing needs to be written
    else
      return;

    debug_eeprom_printf("write adr=0x%04X len=%u", EepromWriteDestAddr,
                        EepromWriteCnt);
  }
  // EEPROM still busy
  if (bit_is_set(EECR, EEWE))
    // try again later
    return;

  // disable interrupts
  cli();

  // get address in EEPROM and data to write
  EEAR = EepromWriteDestAddr++;
  if (EepromWriteFlags & EepromWriteFlagCheck)  // check byte
  {
    EEDR = ~*(pEepromWriteSrc++);       // invert and advance source pointer
    EepromWriteCnt--;   // count down number of bytes to write
    if (EepromWriteCnt <= 0)    // finished
      EepromWriteFlags &= ~EepromWriteFlagActive;
  } else        // not check byte
    EEDR = *pEepromWriteSrc;    // use data byte as it is
  EepromWriteFlags ^= EepromWriteFlagCheck;     // toggle check byte bit

  // initiate write to EEPROM
  bit_set(EECR, EEMWE);
  bit_set(EECR, EEWE);

  // enable interrupts
  sei();
}
