/*
Copyright (C) 2002 Pedro Zorzenon Neto <pzn@vztech.com.br>

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

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Do not forget to visit Free Software Foundation site: http://fsf.org

$Id: eeprom.c,v 1.3 2003/07/05 18:23:22 pzn Exp $
*/
#include "common.h"
#include "memory.h"
#include "eeprom.h"
#include "mcdelay.h"

#include <stdlib.h>
#include <assert.h>

#include <stdio.h>

#ifndef __dos__
# include <unistd.h>
#endif

#define EEPROM_DELAY 25
#define EEPROM_POWERDELAY 250000

void exit_error (eeprom_t * eep, int status, char * s);
void eeprom_startbit (eeprom_t * self);
void eeprom_stopbit (eeprom_t * self);
void eeprom_writebit (eeprom_t * self, int value);
int eeprom_readbit (eeprom_t * self);
int eeprom_controlbyte (eeprom_t * self, int page, int readmode);
int eeprom_getbyte (eeprom_t * self);
void eeprom_sendbyte (eeprom_t * self, int value);

void exit_error (eeprom_t * eep, int status, char * s) {
  eeprom_destroy(eep);
  fprintf(stderr,"%s\n",s);
  exit(status);
}

eeprom_t * eeprom_init (unsigned long int parallel_port_address,
			unsigned long int write_size,
			unsigned int addr_size,
			unsigned long int read_size,
			unsigned long int total_size,
			unsigned char inv_page,
			unsigned int microdelay_factor) {
  eeprom_t * self;
  microdelay_init(microdelay_factor);
  self=malloc(sizeof(eeprom_t));
  assert(self!=NULL);
  self->pp=parport_init(parallel_port_address);
  self->write_size=write_size;
  self->addr_size=addr_size;
  self->read_size=read_size;
  self->total_size=total_size;
  self->inv_page=inv_page;

  /* hardware initialize */
  eeprom_power(self,0);

  return self;
}

void eeprom_destroy (eeprom_t * self) {
  if (self!=NULL) {
    eeprom_power(self,0); /* power off */
    parport_destroy(self->pp);
    free(self);
  }
}

/* TODO: implement the code */
/* int eeprom_readbyte (eeprom_t * self, int addr) {
  assert(0);
}
int eeprom_writebyte (eeprom_t * self, int addr, int value) {
  assert(0);
} */

int eeprom_fullread (eeprom_t * self, memory_t * mem) {
  int i, addr=0;
  int total_pages=0;
  int current_page=0;

  eeprom_power(self,1);

  total_pages=self->total_size / self->read_size;
  for (current_page=0; current_page<total_pages; current_page++) {

    /* Dummy write at address 0, only to set the address register */
    if (eeprom_controlbyte(self, current_page, 0)==0)
      exit_error(self, 0x20,"read error - control byte failed");
    for (i=0; i<self->addr_size; i++) {
      eeprom_sendbyte(self, 0);
      if (eeprom_readbit(self)!=0) /* check ACK */
	exit_error(self, 0x21,"read error - ACK not received");
    }

    /* Sequencial Read */
    eeprom_controlbyte(self, current_page, 1);
    for (addr=0; addr<self->read_size; addr++) {
      if (addr!=0) {
	eeprom_writebit(self, 0); /* send ACK */
      }
      memory_write (mem, addr+(self->read_size*current_page),
		    eeprom_getbyte (self)); /* send byte to memory */
    }

    if (eeprom_readbit(self)==0) /* check NOACK */
	exit_error(self, 0x22,"read error - NOACK not received");

    eeprom_stopbit(self);

    for (i=0; i<16; i++) { /* some dummy clock cycles */
      parport_writebit (self->pp, EEPROM_CLOCK, 1);
      microdelay(EEPROM_DELAY);
      parport_writebit (self->pp, EEPROM_CLOCK, 0);
      microdelay(EEPROM_DELAY);
    }

  }
  eeprom_power(self,0);
  return 1;
}

int eeprom_fullwrite (eeprom_t *self, memory_t * mem) {
  int i;
  int addr;

  int total_pages=0;
  int current_page=0;

  eeprom_power(self,1);

  total_pages=self->total_size / self->read_size;

  for (current_page=0; current_page<total_pages; current_page++) {

    for (addr=0; addr<(self->read_size); addr+=self->write_size) {

      if (eeprom_controlbyte(self, current_page, 0)==0)
	exit_error(self, 0x30,"write error - control byte failed");

      /* send address bytes */
      for (i=(self->addr_size-1); i>=0; i--) {
	eeprom_sendbyte (self, 0xFF & (addr>>(i*8)));
	if (eeprom_readbit(self)!=0) /* check ACK */
	  exit_error(self, 0x31,"write error - ACK not received");
      }

      for (i=addr; i<(addr+self->write_size); i++) {
	eeprom_sendbyte (self,
			 memory_read(mem,
				     i+ (self->read_size*current_page)));
	eeprom_writebit(self, 0); /* send ACK */
      }

      eeprom_stopbit(self);

      /* Acknoledge pooling */
	{
	  int timeout=10000, ack=0, i;
	  while ((timeout>0) && (ack==0)) {
	    timeout--;
	    if (eeprom_controlbyte(self, 0, 1))
	      { ack=1; }
	    eeprom_stopbit(self);
	    for (i=0; i<80; i++) { /* some dummy clock cycles */
	      parport_writebit (self->pp, EEPROM_CLOCK, 1);
	      microdelay(EEPROM_DELAY);
	      parport_writebit (self->pp, EEPROM_CLOCK, 0);
	      microdelay(EEPROM_DELAY);
	      if (i==40)
		eeprom_stopbit(self);
	    }
	  }
	  if (ack==0)
	    exit_error(self, 0x32,"write error - ACK pooling not received");
	}
    }
  }

  eeprom_power(self,0);

  return 1;

}

void eeprom_power (eeprom_t * self, int poweron) {
  if (poweron) {
    /* power on */
    int i;
    parport_writebyte(self->pp, 0xFF);
    parport_writebit (self->pp, EEPROM_NDATAIN, 0);
    microdelay(EEPROM_POWERDELAY); /* power up delay */

    for (i=0; i<256; i++) { /* some dummy clock cycles */
      parport_writebit (self->pp, EEPROM_CLOCK, 1);
      microdelay(EEPROM_DELAY);
      parport_writebit (self->pp, EEPROM_CLOCK, 0);
      microdelay(EEPROM_DELAY);
    }
  } else {
    /* power off */
    parport_writebyte(self->pp, 0x00);
    parport_writebit (self->pp, EEPROM_NDATAIN, 1);
  }
}

void eeprom_startbit (eeprom_t * self) {
  /* send start condition */
  parport_writebit (self->pp, EEPROM_CLOCK, 1);
  microdelay(EEPROM_DELAY);
  parport_writebit (self->pp, EEPROM_NDATAIN, 1);
  microdelay(EEPROM_DELAY);
  parport_writebit (self->pp, EEPROM_CLOCK, 0);
  microdelay(EEPROM_DELAY);
  parport_writebit (self->pp, EEPROM_NDATAIN, 0);
  microdelay(EEPROM_DELAY);
}

void eeprom_stopbit (eeprom_t * self) {
  /* send stop condition */
  parport_writebit (self->pp, EEPROM_NDATAIN, 1);
  microdelay(EEPROM_DELAY);
  parport_writebit (self->pp, EEPROM_CLOCK, 1);
  microdelay(EEPROM_DELAY);
  parport_writebit (self->pp, EEPROM_NDATAIN, 0);
  microdelay(EEPROM_DELAY);
  parport_writebit (self->pp, EEPROM_CLOCK, 0);
  microdelay(EEPROM_DELAY);
}

void eeprom_writebit (eeprom_t * self, int value) {
  if (value) {
    parport_writebit (self->pp, EEPROM_CLOCK, 1);
    microdelay(EEPROM_DELAY);
    parport_writebit (self->pp, EEPROM_CLOCK, 0);
    microdelay(EEPROM_DELAY);
  } else {
    parport_writebit (self->pp, EEPROM_NDATAIN, 1);
    microdelay(EEPROM_DELAY);
    parport_writebit (self->pp, EEPROM_CLOCK, 1);
    microdelay(EEPROM_DELAY);
    parport_writebit (self->pp, EEPROM_CLOCK, 0);
    microdelay(EEPROM_DELAY);
    parport_writebit (self->pp, EEPROM_NDATAIN, 0);
    microdelay(EEPROM_DELAY);
  }
}

int eeprom_readbit (eeprom_t * self) {
  int i;
  parport_writebit (self->pp, EEPROM_CLOCK, 1);
  microdelay(EEPROM_DELAY);
  i=parport_readbit(self->pp, EEPROM_DATAOUT);
  microdelay(EEPROM_DELAY);
  parport_writebit (self->pp, EEPROM_CLOCK, 0);
  microdelay(EEPROM_DELAY);
  return i;
}

int eeprom_controlbyte (eeprom_t * self, int page, int readmode) {
  eeprom_startbit(self);

  /* device address */
  eeprom_writebit(self, 1);
  eeprom_writebit(self, 0);
  eeprom_writebit(self, 1);
  eeprom_writebit(self, 0);

  /* page number */
  if ((self->inv_page) != 0) {
    eeprom_writebit(self, page & 0x01); /* page P0 */
    eeprom_writebit(self, page & 0x02); /* page P1 */
    eeprom_writebit(self, page & 0x04); /* page P2 */
  } else {
    eeprom_writebit(self, page & 0x04); /* page P2 */
    eeprom_writebit(self, page & 0x02); /* page P1 */
    eeprom_writebit(self, page & 0x01); /* page P0 */
  }

  eeprom_writebit(self, readmode); /* 0 = write, 1 = read */

  /* check ACK */
  if (eeprom_readbit(self)!=0) return 0; /* Failed */
  return 1; /* Success */
}

int eeprom_getbyte (eeprom_t * self) {
  int i=0;
  i|=eeprom_readbit(self)<<7; /* D7 - MSB */
  i|=eeprom_readbit(self)<<6; /* D6 */
  i|=eeprom_readbit(self)<<5; /* D5 */
  i|=eeprom_readbit(self)<<4; /* D4 */
  i|=eeprom_readbit(self)<<3; /* D3 */
  i|=eeprom_readbit(self)<<2; /* D2 */
  i|=eeprom_readbit(self)<<1; /* D1 */
  i|=eeprom_readbit(self)<<0; /* D0 - LSB */
  return i;
}

void eeprom_sendbyte (eeprom_t * self, int value) {
  eeprom_writebit(self, value & 0x80); /* D7 - MSB */
  eeprom_writebit(self, value & 0x40); /* D6 */
  eeprom_writebit(self, value & 0x20); /* D5 */
  eeprom_writebit(self, value & 0x10); /* D4 */
  eeprom_writebit(self, value & 0x08); /* D3 */
  eeprom_writebit(self, value & 0x04); /* D2 */
  eeprom_writebit(self, value & 0x02); /* D1 */
  eeprom_writebit(self, value & 0x01); /* D0 - LSB */
}
