/*                                              -*- mode:C++; fill-column:100 -*-
  SFBEepromImpl.cpp - Support for I/O with the EEPROM - board-specific routines
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA
*/

#include "SFBEeprom.h"
#include "SFBConstants.h"       // For LOW, etc
#include "SFBPins.h"            // For BODY_EEPROM_SSEL1_PIN, etc
#include "SFBWiring.h"          // For digitalWrite, etc
#include "SFBAssert.h"          // For API_ASSERT_MAX, etc

/* EEPROM Instructions */
#define kEEREAD  0x03
#define kEEWRITE 0x02
#define kEEWRDI  0x04
#define kEEWREN  0x06
#define kEERDSR  0x05
#define kEEWRSR  0x01

#define EEPROM_SIZE_BYTES (1<<14)

static void startTransaction() {
  digitalWrite(BODY_EEPROM_SSEL1_PIN,LOW);
}

static void endTransaction() {
  digitalWrite(BODY_EEPROM_SSEL1_PIN,HIGH);
}

static void clockIt() {
  digitalWrite(BODY_EEPROM_SCK1_PIN,HIGH); // Rising clock edge
  digitalWrite(BODY_EEPROM_SCK1_PIN,LOW);  // Falling clock edge
}

/* Assumes SSEL already low */
static void sendByteBitBang(uint8_t data) {
  for (u32 bit = 1<<8; bit>>=1; ) {
    digitalWrite(BODY_EEPROM_MOSI1_PIN,(data&bit)?HIGH:LOW); // Set bit
    clockIt();
  }
}
  
/* Assumes SSEL already low */
static u8 readByteBitBang() {
  u8 data = 0;
  for (u32 bit = 1<<8; bit>>=1; ) {
    if (digitalRead(BODY_EEPROM_MISO1_PIN)) // Read input
      data |= bit;
    clockIt();
  }
  return data;
}

static void setWriteEnableBitBang(bool enable) {
  startTransaction();
  sendByteBitBang(enable?kEEWREN:kEEWRDI);
  endTransaction();
}

static u8 eepromStatus() {
  startTransaction();
  sendByteBitBang(kEERDSR);
  u8 res = readByteBitBang();
  endTransaction();
  return res;
}

void eeprom_startup_initializationImpl() {

  /* Cool down SSP1 (jest in case) */
  PCONP &= ~PCSSP1;

  /* Setup pin P0.6,7,8,9 for bitbanged SPI -- so, GPIOs 
     XXX This shouldn't be needed anymore, right?  pinMode now does it, right? */
  u32 bits = PINSEL0;
  BIT_PAIR_AT_POSITION(bits,0x0,12);  // P0.6 SSEL1
  BIT_PAIR_AT_POSITION(bits,0x0,14);  // P0.7 SCK1
  BIT_PAIR_AT_POSITION(bits,0x0,16);  // P0.8 MISO1
  BIT_PAIR_AT_POSITION(bits,0x0,18);  // P0.9 MOSI1
  PINSEL0 = bits;

  /* Setup our pins P0.6,7,8,9 for GPIO */
  pinMode(BODY_EEPROM_SSEL1_PIN,OUTPUT);
  pinMode(BODY_EEPROM_SCK1_PIN,OUTPUT);
  pinMode(BODY_EEPROM_MISO1_PIN,INPUT);
  pinMode(BODY_EEPROM_MOSI1_PIN,OUTPUT);

  /* Idle values */
  digitalWrite(BODY_EEPROM_SSEL1_PIN,HIGH);
  digitalWrite(BODY_EEPROM_SCK1_PIN,LOW);
}

void eepromWaitImpl(bool busyWait) {
  u32 blocks = 0;
  while (eepromStatus() & 1) {
    if (busyWait)
      for (u32 i = 0; i < 10; ++i)
        delayMicroseconds(100);
    else
      delay(1);
    ++blocks;
    API_ASSERT_MAX_CODE(blocks,20,E_API_EEPROM_RESPONSE);
  }
}

void eepromWriteImpl(u32 addr, const u8 * dataSrc, u32 length, bool busyWait) {

  bool needInit = true;

  while (length--) {

    if (needInit) {
      needInit = false;

      eepromWait(busyWait);
      setWriteEnableBitBang(true);
      startTransaction();

      sendByteBitBang(kEEWRITE);
      sendByteBitBang((addr>>8)&0xff);
      sendByteBitBang(addr&0xff);
    }

    sendByteBitBang(*dataSrc++);
    ++addr;

    if ((addr&0x3f)==0) {          // Just ended a page
      needInit = true;
      endTransaction();
    }
  }
  if (!needInit)
    endTransaction();
}

void eepromReadImpl(u32 addr, u8 * dataDest, u32 length) {

  eepromWait(false);

  startTransaction();
  sendByteBitBang(kEEREAD);
  sendByteBitBang((addr>>8)&0xff);
  sendByteBitBang(addr&0xff);

  for (u32 i=0; i< length; ++i) {      
    dataDest[i] = readByteBitBang();
  }
  endTransaction();
} 
