/***************************************************************************//**
 * @file
 * @brief EEPROM driver for 24AA024 (2Kbit) EEPROM device on the DK.
 * @author Energy Micro AS
 * @version 3.20.2
 *******************************************************************************
 * @section License
 * <b>(C) Copyright 2012 Energy Micro AS, http://www.energymicro.com</b>
 *******************************************************************************
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 * 4. The source and compiled code may only be used on Energy Micro "EFM32"
 *    microcontrollers and "EFR4" radios.
 *
 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Energy Micro AS has no
 * obligation to support this Software. Energy Micro AS is providing the
 * Software "AS IS", with no express or implied warranties of any kind,
 * including, but not limited to, any implied warranties of merchantability
 * or fitness for any particular purpose or warranties against infringement
 * of any proprietary rights of a third party.
 *
 * Energy Micro AS will not be liable for any consequential, incidental, or
 * special damages, or any other relief, or for any claim by any third party,
 * arising from your use of this Software.
 *
 *****************************************************************************/

#include <stddef.h>
#include "i2cdrv.h"
#include "eeprom.h"

/*******************************************************************************
 *******************************   DEFINES   ***********************************
 ******************************************************************************/

/** Number of bytes in EEPROM */
#define EEPROM_DVK_LEN         0x100

/** Pagesize in EEPROM */
#define EEPROM_DVK_PAGESIZE    16


/*******************************************************************************
 ***************************   LOCAL FUNCTIONS   *******************************
 ******************************************************************************/

/***************************************************************************//**
 * @brief
 *   Do acknowledge polling on EEPROM device.
 *
 * @details
 *   When writing to an EEPROM, the EEPROM device will be busy for some time
 *   after issuing a (page) write. During this time, the EEPROM is not
 *   accessible, and will therefore not ACK any requests. This feature can
 *   be used to determine when the write is actually completed, and is denoted
 *   acknowledgement polling.
 *
 * @note
 *   This function will not return until the EEPROM device acknowledges (or some
 *   sort of I2C failure occurs). If trying to acknowledge poll a non-existing
 *   device, NACK will always result and this function will never return. Thus,
 *   it should not be used unless the EEPROM device is actually present.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 *
 * @param[in] addr
 *   I2C address for EEPROM, in 8 bit format, where LSB is reserved
 *   for R/W bit.
 *
 * @return
 *   Returns 0 when EEPROM acknowledges. Negative value is returned
 *   is some sort of error occurred during acknowledgement polling.
 ******************************************************************************/
static int EEPROM_AckPoll(I2C_TypeDef *i2c, uint8_t addr)
{
  I2C_TransferSeq_TypeDef    seq;
  I2C_TransferReturn_TypeDef ret;

  /* Unused parameter */
  (void) i2c;

  /* Do acknowledge polling waiting for write process to finish in EEPROM */
  seq.addr  = addr;
  seq.flags = I2C_FLAG_WRITE;
  /* Just access device with write operation */
  seq.buf[0].data = NULL;
  seq.buf[0].len  = 0;

  /* Wait for ACK from device */
  while (1)
  {
    ret = I2CDRV_Transfer(&seq);
    if (ret == i2cTransferDone)
    {
      break;
    }
    else if (ret == i2cTransferNack)
    {
      continue;
    }
    else
    {
      return((int) ret);
    }
  }

  return(0);
}


/*******************************************************************************
 **************************   GLOBAL FUNCTIONS   *******************************
 ******************************************************************************/

/***************************************************************************//**
 * @brief
 *   Read data from EEPROM.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 *
 * @param[in] addr
 *   I2C address for EEPROM, in 8 bit format, where LSB is reserved
 *   for R/W bit.
 *
 * @param[in] offset
 *   Offset in EEPROM to start reading from.
 *
 * @param[out] data
 *   Location to place read data, must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to read.
 *
 * @return
 *   Returns number of bytes read. Less than specified number of bytes is
 *   returned if reading beyond end of EEPROM. Negative value is returned
 *   is some sort of error occurred during read.
 ******************************************************************************/
int EEPROM_Read(I2C_TypeDef *i2c,
                uint8_t addr,
                unsigned int offset,
                uint8_t *data,
                unsigned int len)
{
  I2C_TransferSeq_TypeDef    seq;
  I2C_TransferReturn_TypeDef ret;
  uint8_t                    offsetLoc[1];

  /* Unused parameter */
  (void) i2c;

  if (offset >= EEPROM_DVK_LEN)
  {
    return(0);
  }

  if ((offset + len) > EEPROM_DVK_LEN)
  {
    len = EEPROM_DVK_LEN - offset;
  }

  seq.addr  = addr;
  seq.flags = I2C_FLAG_WRITE_READ;
  /* Select offset to start reading from */
  offsetLoc[0]    = (uint8_t) offset;
  seq.buf[0].data = offsetLoc;
  seq.buf[0].len  = 1;
  /* Select location/length of data to be read */
  seq.buf[1].data = data;
  seq.buf[1].len  = len;

  ret = I2CDRV_Transfer(&seq);
  if (ret != i2cTransferDone)
  {
    return((int) ret);
  }

  return((int) len);
}


/***************************************************************************//**
 * @brief
 *   Write data to EEPROM.
 *
 * @param[in] i2c
 *   Pointer to I2C peripheral register block.
 *
 * @param[in] addr
 *   I2C address for EEPROM, in 8 bit format, where LSB is reserved
 *   for R/W bit.
 *
 * @param[in] offset
 *   Offset in EEPROM to start writing to.
 *
 * @param[out] data
 *   Location holding data to write, must be at least @p len long.
 *
 * @param[in] len
 *   Number of bytes to write.
 *
 * @return
 *   Returns number of bytes written. Less than specified number of bytes is
 *   returned if writing beyond end of EEPROM. Negative value is returned
 *   is some sort of error occurred during write.
 ******************************************************************************/
int EEPROM_Write(I2C_TypeDef *i2c,
                 uint8_t addr,
                 unsigned int offset,
                 uint8_t *data,
                 unsigned int len)
{
  I2C_TransferSeq_TypeDef    seq;
  I2C_TransferReturn_TypeDef ret;
  int                        tmp;
  unsigned int               chunk;
  unsigned int               max;
  uint8_t                    offsetLoc[1];

  if (offset >= EEPROM_DVK_LEN)
  {
    return(0);
  }

  if ((offset + len) > EEPROM_DVK_LEN)
  {
    len = EEPROM_DVK_LEN - offset;
  }

  /* Write max one page at a time */
  while (len)
  {
    max = EEPROM_DVK_PAGESIZE - (offset % EEPROM_DVK_PAGESIZE);

    if (len > max)
    {
      chunk = max;
    }
    else
    {
      chunk = len;
    }

    seq.addr  = addr;
    seq.flags = I2C_FLAG_WRITE_WRITE;
    /* Select offset to start writing to */
    offsetLoc[0]    = (uint8_t) offset;
    seq.buf[0].data = offsetLoc;
    seq.buf[0].len  = 1;
    /* Select location/length of data to be written */
    seq.buf[1].data = data;
    seq.buf[1].len  = chunk;

    ret = I2CDRV_Transfer(&seq);
    if (ret != i2cTransferDone)
    {
      return((int) ret);
    }

    /* Update counters etc */
    data   += chunk;
    offset += chunk;
    len    -= chunk;

    /* Do acknowledge polling waiting for write process to finish in EEPROM */
    tmp = EEPROM_AckPoll(i2c, addr);
    if (tmp)
    {
      return(tmp);
    }
  }

  return((int) len);
}
