/*
 * Author: Jon Trulson <jtrulson@ics.com>
 * Copyright (c) 2015 Intel Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <unistd.h>
#include <math.h>
#include <iostream>
#include <string>

#include "m24lr64e.hpp"

using namespace upm;
using namespace std;


M24LR64E::M24LR64E(int bus, AccessMode mode):
  m_i2c(bus)
{
  if (mode == USER_MODE)
    m_addr = M24LR64E_DEFAULT_I2C_ADDR;
  else
    m_addr = M24LR64E_DEFAULT_I2C_ADDR_E2;

  mraa::Result rv;
  if ( (rv = m_i2c.address(m_addr)) != mraa::SUCCESS)
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": I2c.address() failed");
      return;
    }
}

M24LR64E::~M24LR64E()
{
}

bool M24LR64E::submitPasswd(uint32_t passwd)
{
  // this device actually uses two bytes to address a register
  const int pktLen = 11;
  uint8_t buf[pktLen];

  buf[0] = 0x09;
  buf[1] = 0x00;

  buf[2] = ((passwd >> 24) & 0xff);
  buf[3] = ((passwd >> 16) & 0xff);
  buf[4] = ((passwd >> 8) & 0xff);
  buf[5] = (passwd & 0xff);

  buf[6] = 0x09;

  // the password is written twice
  buf[7] = ((passwd >> 24) & 0xff);
  buf[8] = ((passwd >> 16) & 0xff);
  buf[9] = ((passwd >> 8) & 0xff);
  buf[10] = (passwd & 0xff);
  
  if (m_i2c.write(buf, pktLen))
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": I2c.write() failed");
      return false;
    }

  return true;
}

bool M24LR64E::writePasswd(uint32_t passwd)
{
  const int pktLen = 11;
  uint8_t buf[pktLen];

  buf[0] = 0x09;
  buf[1] = 0x00;

  buf[2] = ((passwd >> 24) & 0xff);
  buf[3] = ((passwd >> 16) & 0xff);
  buf[4] = ((passwd >> 8) & 0xff);
  buf[5] = (passwd & 0xff);

  buf[6] = 0x07;

  // the password is written twice
  buf[7] = ((passwd >> 24) & 0xff);
  buf[8] = ((passwd >> 16) & 0xff);
  buf[9] = ((passwd >> 8) & 0xff);
  buf[10] = (passwd & 0xff);
  
  if (m_i2c.write(buf, pktLen))
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": I2c.write() failed");
      return false;
    }

  return true;
}

void M24LR64E::sectorProtectConfig(unsigned int sectorNumber, 
                                   bool protectEnable, 
                                   SectorAccessRight accessRight, 
                                   SectorSelectPassWd passwd)
{
  if(!protectEnable) {
    EEPROM_Write_Byte(sectorNumber,0x0);
  } else {
    EEPROM_Write_Byte(sectorNumber,
                      protectEnable | (accessRight<<1) |(passwd<<2));
  }
}

void M24LR64E::clearSectorProtect(void)
{
  uint8_t buf[64]={0x0};
  EEPROM_Write_Bytes(0, buf, 64);
}


void M24LR64E::sectorWriteLockBit(unsigned int sectorNumber, 
                                  bool sockEnable)
{
  unsigned int sectorAddress = SECTOR_SECURITY_STATUS_BASE_ADDR 
    + (sectorNumber/8);
  uint8_t sectorBit = sectorNumber % 8;
  uint8_t preStatus = EEPROM_Read_Byte(sectorAddress);
  
  bool status = (preStatus >> sectorBit) & 0x01;
  if(status != sockEnable) {
    if(status == true) {
      writeByte(sectorAddress,preStatus&(~(1<<sectorBit)));
    } else {
      writeByte(sectorAddress,preStatus|(1<<sectorBit));
    }
  }
}

uint8_t M24LR64E::getDSFID()
{
  return EEPROM_Read_Byte(DSFID_ADDR);
}

uint8_t M24LR64E::getAFI()
{
  return EEPROM_Read_Byte(AFI_ADDR);
}

uint8_t *M24LR64E::getUID()
{
  uint8_t* buffer = new uint8_t[UID_LENGTH];
  EEPROM_Read_Bytes(UID_ADDR, buffer, UID_LENGTH);

  return buffer;
}

uint32_t M24LR64E::getMemorySize()
{
  uint32_t volume = 0x0;
  volume = EEPROM_Read_Byte(MEM_SIZE_ADDR); 
  volume = volume<<8|EEPROM_Read_Byte(MEM_SIZE_ADDR+1);
  volume = volume<<8|EEPROM_Read_Byte(MEM_SIZE_ADDR+2);
  return volume;
}

void M24LR64E::clearMemory()
{
  for(int i = 0; i < EEPROM_I2C_LENGTH; i++){
    writeByte(i,0x0);
  }
}

mraa::Result M24LR64E::writeByte(unsigned int address, uint8_t data)
{
  return EEPROM_Write_Byte(address, data);
}

mraa::Result M24LR64E::writeBytes(unsigned int address, uint8_t* buffer, int len)
{
  return EEPROM_Write_Bytes(address, buffer, len);
}

uint8_t M24LR64E::readByte(unsigned int address)
{
  return EEPROM_Read_Byte(address);
}

int M24LR64E::readBytes(unsigned int address, uint8_t* buffer, int len)
{
  return EEPROM_Read_Bytes(address, buffer, len);
}

mraa::Result M24LR64E::EEPROM_Write_Byte(unsigned int address, uint8_t data)
{
  const int pktLen = 3;
  uint8_t buf[pktLen];
  mraa::Result rv;
  
  buf[0] = ((address >> 8) & 0xff);
  buf[1] = (address & 0xff);
  buf[2] = data;

  if ((rv = m_i2c.write(buf, pktLen)))
    throw std::runtime_error(std::string(__FUNCTION__) +
                             ": I2c.write() failed");

  usleep(I2C_WRITE_TIME * 1000);
  return rv;
}

mraa::Result M24LR64E::EEPROM_Write_Bytes(unsigned int address, uint8_t* data,
                                  int len)
{
  const int pktLen = 2 + len;
  uint8_t buf[pktLen];
  mraa::Result rv;
  
  buf[0] = ((address >> 8) & 0xff);
  buf[1] = (address & 0xff);

  for (int i=0; i<len; i++)
    buf[2+i] = data[i];

  if ((rv = m_i2c.write(buf, pktLen)))
    throw std::runtime_error(std::string(__FUNCTION__) +
                             ": I2c.write() failed");

  usleep(I2C_WRITE_TIME * 1000);

  return rv;
}

uint8_t M24LR64E::EEPROM_Read_Byte(unsigned int address)
{
  const int apktLen = 2;
  uint8_t abuf[apktLen];

  abuf[0] = ((address >> 8) & 0xff);
  abuf[1] = (address & 0xff);
  
  if (m_i2c.write(abuf, apktLen))
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": I2c.write() failed");
      return 0x00;
    }

  const int pktLen = 1;
  uint8_t buf[apktLen];

  buf[0] = 0;
  
  if (m_i2c.read(buf, pktLen) != pktLen)
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": I2c.write() failed");
      return 0x00;
    }
  
  return buf[0];
}

int M24LR64E::EEPROM_Read_Bytes(unsigned int address, 
                                         uint8_t* buffer, int len)
{
  const int apktLen = 2;
  uint8_t abuf[apktLen];

  abuf[0] = ((address >> 8) & 0xff);
  abuf[1] = (address & 0xff);
  
  if (m_i2c.write(abuf, apktLen))
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": I2c.write() failed");
      return false;
    }

  int rv = m_i2c.read(buffer, len);
  if (rv != len)
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": I2c.read() failed");
    }

  return rv;
}
