/**********************************************************************/
/*                                                                    */
/* File name: driver.c                                                */
/*                                                                    */
/* Since:     2002/12/03                                              */
/*                                                                    */
/* Version:   1.02                                                    */
/*                                                                    */
/* Author:    MONTAGNE Xavier [XM] {link xavier.montagne@wanadoo.fr}  */
/*                                                                    */
/* Purpose: Offer low level functions for basic PIC programming       */
/*          procedures. Have a look at the Microchip specifications   */
/*          for further details.                                      */
/*                                                                    */
/* Distribution: This file is part of PP18.                           */
/*               PP18 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, or (at your option)    */
/*               any later version.                                   */
/*                                                                    */
/*               PP18 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 PP18; see the file         */
/*               COPYING.txt. If not, write to the Free Software      */
/*               Foundation, 59 Temple Place - Suite 330,             */
/*               Boston, MA 02111-1307, USA.                          */
/*                                                                    */
/* History:                                                           */
/*      2002/12/03  [XM] Create this file                             */
/*      2003/06/26  [XM] Added the Delay_Prog management.             */
/*                                                                    */
/**********************************************************************/

/***********************************************************************
 * INCLUDES
 **********************************************************************/
#include "driver.h"

/***********************************************************************
 * DEFINES.
 **********************************************************************/
#define MOVLW                                  0x0E00
#define MOVWF                                  0x6E00
#define TBLPTRL_ad                             0xF6
#define TBLPTRH_ad                             0xF7
#define TBLPTRU_ad                             0xF8

/*
 * As specified in PIC18C ICSP specification.
 */
#define NB_BIT_PER_INST                        4
#define NB_BIT_PER_DATA                        16
#define NB_BIT_PER_HALF_DATA                   8

/***********************************************************************
 * MACROS
 **********************************************************************/
#define WriteOpNOP()                           WriteOp(0x00)
#define WriteOpTRNoChange()                    WriteOp(0x08)
#define WriteOpTRPostInc()                     WriteOp(0x09)
#define WriteOpTRPostDec()                     WriteOp(0x0a)
#define WriteOpTRPreInc()                      WriteOp(0x0b)
#define WriteOpTWNoChange()                    WriteOp(0x0c)
#define WriteOpTWPostInc()                     WriteOp(0x0d)
#define WriteOpTWPostDec()                     WriteOp(0x0e)
#define WriteOpTWPreInc()                      WriteOp(0x0f)

#ifdef LINUX
#include <sys/io.h>
#define pport_in(value, port_adr)              value = inb(port_adr)
#define pport_out(value, port_adr)             outb(value, port_adr)
#else
#ifdef WITHOUT_PIC
#include <stdlib.h>
#include <stdio.h>
#define pport_in(value, port_adr)	           value = (rand())%0xFF;
#define pport_out(value, port_adr)          
#else
#define pport_in(value, port_adr)              __asm  mov dx, port_adr ;\
                                               __asm  in  al, dx ;\
                                               __asm  mov value, al

#define pport_out(value, port_adr)             __asm  mov dx, port_adr ;\
                                               __asm  mov al, value ;\
                                               __asm  out dx, al
#endif /* WITHOUT_PIC */
#endif /* LINUX */


/***********************************************************************
 * Function prototypes.
 **********************************************************************/
static void                WriteRB6_RB7(unsigned char);
static void                WriteRB6_RB7Delay(unsigned char val);

static void                WriteOp(unsigned char TBLWT);
static void                WriteOpDelay(unsigned char TBLWT);

static void                WriteData(unsigned short data);
static void                WriteDataAtTBLPTR(unsigned short data, ACTION action);

static void                ReadDataAtTBLPTR(unsigned char *data, ACTION action);
static unsigned char       ReadData(void);

static void                SetTBLPTR(unsigned int location);
static void                SelectBit(pr_Signal *ptr_bit, PIN_LABEL name);


/***********************************************************************
 * Global variables.
 **********************************************************************/
pr_ValSignal CLOCK         = { PL_CLOCK, RB6_MASK, NON_INVERTED };
pr_ValSignal DATA_TO_PIC   = { PL_DATA_TO_PIC, RB7_MASK, NON_INVERTED };
pr_ValSignal DATA_FROM_PIC = { PL_DATA_FROM_PIC, BUSY_MASK, INVERTED };
pr_ValSignal MCLR_VPP      = { PL_VPP, MCLR_MASK, INVERTED };
pr_ValSignal VCC_ON        = { PL_VCC, VCC_ON_MASK, INVERTED };

/***** Value in data parallele port register. *************************/
unsigned char data_bus;
unsigned short LPT_adr_out = PPORT_OUT_ADR;
unsigned short LPT_adr_in  = PPORT_IN_ADR;
unsigned int delay_IO      = 400;
unsigned int delay_Prog    = 5;


/***********************************************************************
 * Stuck at 0 all of the signals (logical 0, not necessarily physical 0).
 *
 * @param  void
 * @return void
 **********************************************************************/
void _InitHardware(void)
{
  ClearBit(PL_CLOCK);
  ClearBit(PL_DATA_TO_PIC);
  ClearBit(PL_VPP);
  ClearBit(PL_VCC);
}

/***********************************************************************
 * Set VCC_ON bit physicaly at 1.
 *
 * @param  void
 * @return void 
 **********************************************************************/ 
void _PowerOn(void)
{
  unsigned int j;

  /* 10 ms delay */
  /* LPT port transfer rate = 150 ko/s */
  for (j = 0; j < 1500; j++)
    SetBit(PL_VCC);
}

/***********************************************************************
 * Clear VCC_ON bit physicaly at 0.
 *
 * @param  void
 * @return void 
 **********************************************************************/ 
void _PowerOff(void)
{
  ClearBit(PL_VCC);
}

/***********************************************************************
 * Write a buffer into the memory of the PIC.
 * A programmable delay exists to avoid any transfert problem with 
 * low speed communication PIC. Full speed is default speed.
 *
 * @param  u_int  adr            IN  Start address in the PIC address
 * @param  u_short *block        IN  Buffer start address
 * @param  u_int size            IN  Number of SHORT to write
 * @param  u_int *try_done       IN  For compatibility with JW version
 * @param  u_int word_programmed IN  Number of SHORT programmed  
 * @return void
 **********************************************************************/ 
void ProgramBlock(unsigned int adr, unsigned short *block, unsigned int size, unsigned int *try_done, unsigned int *word_programmed)
{
  unsigned int i, j, word_prg;
  unsigned short value;

  for (j = 0; j < 1500; j++)
    SetBit(PL_VPP);

  /* Precharge phase */
  SetTBLPTR(0x3C0006);
  WriteDataAtTBLPTR(0x00, NO_CHANGE);
  WriteOpNOP();
  WriteData((unsigned short)(0x8EA6)); 
  WriteOpNOP();
  WriteData((unsigned short)(0x9CA6)); 
  /* End fo Precharge phase */

  word_prg  = 0;
  SetTBLPTR(adr);

  for (i = 0; i < size; i = i + 1)
  {
	value = *block;

	for (j = 0; (j < 3) & (size > 1); j++)
	{
		WriteDataAtTBLPTR(value, POST_INC);
		i++;	
		block++;
		word_prg++;
		value = *block;
	}
	WriteDataAtTBLPTR(value, PRE_INC);
	WriteOpDelay(0x00);
	for (j = 0; j < delay_IO; j++)
		SetBit(PL_CLOCK);
	ClearBit(PL_CLOCK);
	WriteData(0x0000);

	WriteDataAtTBLPTR(0x0000, POST_INC);

	block++;
	word_prg++;
  }
  
  *try_done = i;
  *word_programmed = word_prg;

  ClearBit(PL_VPP);
}

/***********************************************************************
 * Write a buffer into the configuration field of the PIC.
 * A programmable delay exists to avoid any transfert problem with 
 * low speed communication PIC. Full speed is default speed.
 *
 * @param  u_int  adr            IN  Start address in the PIC address
 * @param  u_short *block        IN  Buffer start address
 * @param  u_int size            IN  Number of SHORT to write
 * @param  u_int *try_done       IN  For compatibility with JW version
 * @param  u_int word_programmed IN  Number of SHORT programmed  
 * @return void
 **********************************************************************/ 
void ProgramBlockCfg(unsigned int adr, unsigned short *block, unsigned int size, unsigned int *try_done, unsigned int *word_programmed)
{
  unsigned int i, j, word_prg;
  unsigned short value;

  for (j = 0; j < 1500; j++)
    SetBit(PL_VPP);

  /* Precharge phase */
  SetTBLPTR(0x3C0006);
  WriteDataAtTBLPTR(0x00, NO_CHANGE);
  WriteOpNOP();
  WriteData((unsigned short)(0x8EA6));
  WriteOpNOP();
  WriteData((unsigned short)(0x8CA6));
  /* End of precharge phase */

  word_prg  = 0;
  SetTBLPTR(adr);

  for (i = 0; i < size; i = i + 1)
  {
	value = *block;

	for (j = 0; (j < 3) & (size > 1); j++)
	{
		WriteDataAtTBLPTR(value, POST_INC);
		i++;	
		block++;
		word_prg++;
		value = *block;
	}
	WriteDataAtTBLPTR(value, PRE_INC);
	WriteOpDelay(0x00);
	for (j = 0; j < delay_IO; j++)
		SetBit(PL_CLOCK);
	ClearBit(PL_CLOCK);
	WriteData(0x0000);

	WriteDataAtTBLPTR(0x0000, POST_INC);

	block++;
	word_prg++;
  }

  *try_done = i;
  *word_programmed = word_prg;

  ClearBit(PL_VPP);
}


/***********************************************************************
 * Read the memory of the PIC and store the result into a buffer.
 * A programmable delay exists to avoid any transfert problem with
 * low speed communication PIC. Full speed is default speed.
 *
 * @param  u_int  adr            IN  Start address in the PIC address
 * @param  u_short *block        IN  Buffer address
 * @param  u_int size            IN  Number of SHORT to write
 * @return void
 **********************************************************************/
void ReadBlock(unsigned int adr, unsigned short *block, unsigned int size)
{
  unsigned int i, j;
  unsigned char result_H, result_L;
  unsigned short result;

  for (j = 0; j < 1500; j++)
    SetBit(PL_VPP);

  SetTBLPTR(adr);

  for (i = 0; i < size; i = i + 1)
  {
	ReadDataAtTBLPTR(&result_L, POST_INC);
	ReadDataAtTBLPTR(&result_H, POST_INC);
	result = (unsigned short)(result_L + (unsigned short)(result_H << 8));

	*block = result;
	block++;
    for (j = 0; j < delay_IO; j++)
      ClearBit(PL_CLOCK);
  }

  ClearBit(PL_VPP);
}

/***********************************************************************
 * Erase the entier memory of the PIC.
 * A programmable delay exists to avoid any transfert problem with 
 * low speed communication PIC. Full speed is default speed. 
 *
 * @param  void
 * @return void
 **********************************************************************/ 
void BulkErase(void)
{
  unsigned int j;

  for (j = 0; j < 1500; j++)
    SetBit(PL_VPP);

  SetTBLPTR(0x3C0004);
  WriteDataAtTBLPTR(0x83, NO_CHANGE);
  WriteOpNOP();
  WriteData(0x0000);

  WriteOpNOP();
  for (j = 0; j < 5000; j++)
    ClearBit(PL_CLOCK);
  WriteData(0x0000);

  ClearBit(PL_VPP);
  SetBit(PL_VPP);

  SetTBLPTR(0x3C0004);
  WriteDataAtTBLPTR(0x80, NO_CHANGE);
  WriteOpNOP();
  WriteData(0x0000);

  WriteOpNOP();
  for (j = 0; j < 5000; j++)
    ClearBit(PL_CLOCK);
  WriteData(0x0000);

  ClearBit(PL_VPP);

  return;
}

/***********************************************************************
 * Update the TBLPTR. It's necessary to do so before programming the 
 * PIC. This is the START address of the read/write sequences.
 *
 * @param  u_int  location       IN  Value of the TBLPTR register
 * @return void
 **********************************************************************/ 
static void SetTBLPTR(unsigned int location)
{
  unsigned char TBLPTRL, TBLPTRH, TBLPTRU;

  TBLPTRL = (unsigned char)(location & 0xff);
  TBLPTRH = (unsigned char)((location >> 8) & 0xff);
  TBLPTRU = (unsigned char)((location >> 16) & 0xff);

  /* TBLPTRL */
  WriteOpNOP();
  WriteData((unsigned short)(MOVLW + TBLPTRL)); 
  WriteOpNOP();
  WriteData((unsigned short)(MOVWF + TBLPTRL_ad));
  
  /* TBLPTRH */
  WriteOpNOP();
  WriteData((unsigned short)(MOVLW + TBLPTRH));  
  WriteOpNOP();
  WriteData((unsigned short)(MOVWF + TBLPTRH_ad));

  /* TBLPTRU */
  WriteOpNOP();
  WriteData((unsigned short)(MOVLW + TBLPTRU));
  WriteOpNOP();
  WriteData((unsigned short)(MOVWF + TBLPTRU_ad));
}

/***********************************************************************
 * Write any 16 bits word into the PIC memory at the address pointed by
 * TBLPTR.
 *
 * @param  u_short  data         IN  Data to write at TBLPTR
 * @param  ACTION action         IN  Post-inc, pre-inc,... 
 * @return void
 **********************************************************************/ 
static void WriteDataAtTBLPTR(unsigned short data, ACTION action)
{
  switch (action)
    {
    case NO_CHANGE :
      WriteOpTWNoChange();
      break;
    case POST_INC :
      WriteOpTWPostInc();
      break;
    case POST_DEC :
      WriteOpTWPostDec();
      break;
    case PRE_INC :
      WriteOpTWPreInc();
      break;
    default :
      break;
    };

  WriteData(data);
}

/***********************************************************************
 * Read any 16 bits word into the PIC memory at the address pointed by
 * TBLPTR.
 *
 * @param  u_short  *data        IN  Data to read at TBLPTR
 * @param  ACTION action         IN  Post-inc, pre-inc,... 
 * @return void
 **********************************************************************/ 
static void ReadDataAtTBLPTR(unsigned char *data, ACTION action)
{
  switch (action)
    {
    case NO_CHANGE :
      WriteOpTRNoChange();
      break;
    case POST_INC :
      WriteOpTRPostInc();
      break;
    case POST_DEC :
      WriteOpTRPostDec();
      break;
    case PRE_INC :
      WriteOpTRPreInc();
      break;
    default :
      break;
    };

  *data = ReadData();
}

/***********************************************************************
 * Send any 8 bits command word to the PIC. The bits are sent one by 
 * one to the PIC via the RB6 & RB7 pins.
 *
 * @param  u_char  CMD           IN  Command to send to the PIC
 * @return void
 **********************************************************************/ 
static void WriteOp(unsigned char CMD)
{
  unsigned char i;

  for (i = 0; i < NB_BIT_PER_INST; i++)
    WriteRB6_RB7((unsigned char)((CMD >> i) & 1));

}

/***********************************************************************
 * Send any 8 bits command word to the PIC. The bits are sent one by 
 * one to the PIC via the RB6 & RB7 pins.
 * Delay for special operation is added as specified in the Microchip 
 * specification.
 *
 * @param  u_char  CMD           IN  Command to send to the PIC
 * @return void
 **********************************************************************/ 
static void WriteOpDelay(unsigned char CMD)
{
  unsigned char i;

  for (i = 0; i < NB_BIT_PER_INST-1; i++)
    WriteRB6_RB7((unsigned char)((CMD >> i) & 1));

  WriteRB6_RB7Delay((unsigned char)((CMD >> NB_BIT_PER_INST) & 1));
}

/***********************************************************************
 * Send any 16 bits data word to the PIC. The bits are sent one by 
 * one to the PIC via the RB6 & RB7 pins.
 *
 * @param  u_short  data         IN  Data to send to the PIC
 * @return void
 **********************************************************************/ 
static void WriteData(unsigned short data)
{
  unsigned char i;

  for (i = 0; i < NB_BIT_PER_DATA; i++)
    WriteRB6_RB7((unsigned char)((data >> i) & 1));
}

/***********************************************************************
 * Read a 16 bits data word from the PIC. The bits are read one by 
 * one to the PIC via the RB6 & RB7 pins.
 *
 * @param  void
 * @return u_short                   Data read from the PIC
 **********************************************************************/ 
static unsigned char ReadData(void)
{
  unsigned char i, data, val;
  data = 0;

  for (i = 0; i < NB_BIT_PER_HALF_DATA; i++)
    WriteRB6_RB7((unsigned char)((data >> i) & 1));

  SetBit(PL_DATA_TO_PIC);

  for (i = 0; i < NB_BIT_PER_HALF_DATA; i++)
  {
    val = ReadBit(PL_DATA_FROM_PIC);
    data |= (unsigned char)(((val) ? 1: 0)  << i);
  }

  ClearBit(PL_DATA_TO_PIC);
  return (data);
}

/***********************************************************************
 * Read one bit from the PIC. The bit is one of the 5 signals of the 
 * programmer.
 *
 * @param  PIN_LABEL name        IN  Signal name 
 * @return u_char                    LPT data value masked by the signal
 **********************************************************************/ 
unsigned char ReadBit(PIN_LABEL name)
{
  unsigned char data;
  unsigned int j;
  pr_Signal the_bit;

  SelectBit(&the_bit, name);

  for (j = 0; j < delay_Prog; j++)
    SetBit(PL_CLOCK);
  for (j = 0; j < delay_Prog; j++)
    ClearBit(PL_CLOCK);

  pport_in(data, LPT_adr_in);

  if (the_bit->status & INVERTED)
    data = (unsigned char)((~data) & the_bit->mask);
  else
    data = (unsigned char)(data & the_bit->mask);

  return (data);
}

/***********************************************************************
 * Write one bit on one signal from the 5 signals of the programmer.
 *
 * @param  u_char   val          IN  Boolean value to set on the signal
 * @return void
 **********************************************************************/
static void WriteRB6_RB7(unsigned char val)
{
  unsigned int j;

  for (j = 0; j < delay_Prog; j++)
    SetBit(PL_CLOCK);
  for (j = 0; j < delay_Prog; j++)
    (val & 1) ? SetBit(PL_DATA_TO_PIC) : ClearBit(PL_DATA_TO_PIC);
  for (j = 0; j < delay_Prog; j++)
    ClearBit(PL_CLOCK);
  for (j = 0; j < delay_Prog; j++)
    ClearBit(PL_DATA_TO_PIC);
}

/***********************************************************************
 * Write one bit on one signal from the 5 signals of the programmer.
 * Special delay is added as specified by the Microchip specification.
 *
 * @param  u_char   val          IN  Boolean value to set on the signal
 * @return void
 **********************************************************************/
static void WriteRB6_RB7Delay(unsigned char val)
{
  SetBit(PL_CLOCK);
  (val & 1) ? SetBit(PL_DATA_TO_PIC) : ClearBit(PL_DATA_TO_PIC);
}


/***********************************************************************
 * Stuck the signal at 1 (logical 1, not physical 1).
 *
 * @param  PIN_LABEL name        IN  Signal name
 * @return void
 **********************************************************************/
void SetBit(PIN_LABEL name)
{
  pr_Signal the_bit;

  SelectBit(&the_bit, name);

  if (the_bit->status & INVERTED)
    data_bus &= (unsigned char)~the_bit->mask;
  else
    data_bus |= the_bit->mask;
  pport_out(data_bus, PPORT_OUT_ADR);
}

/***********************************************************************
 * Stuck the signal at 0 (logical 0, not physical 0).
 *
 * @param  PIN_LABEL name        IN  Signal name
 * @return void
 **********************************************************************/
void ClearBit(PIN_LABEL name)
{
  pr_Signal the_bit;

  SelectBit(&the_bit, name);

  if (the_bit->status & INVERTED)
    data_bus |= the_bit->mask;
  else
    data_bus &= (unsigned char)~the_bit->mask;
  pport_out(data_bus, PPORT_OUT_ADR);
}

/***********************************************************************
 * Setup a signal.
 *
 * @param  PIN_LABEL name        IN  Signal name
 * @param  u_char mask           IN  Mask value of the signal
 * @param  u_char status         IN  INVERTED or NOT_INVERTED  
 * @return void
 **********************************************************************/
void ConfigBit(PIN_LABEL name, unsigned char mask, unsigned char status)
{
  pr_Signal the_bit;

  SelectBit(&the_bit, name);

  if (mask)
    the_bit->mask = mask;
  the_bit->status = status;
}

/***********************************************************************
 * Select a signal.
 *
 * @param  pr_Signal *ptr_bit    IN  Signal pointer 
 * @param  PIN_LABEL name        IN  Signal name
 * @return void
 **********************************************************************/
static void SelectBit(pr_Signal *ptr_bit, PIN_LABEL name)
{
  switch (name)
  {
  case PL_DATA_TO_PIC:
	*ptr_bit = &DATA_TO_PIC;
	break;
  case PL_DATA_FROM_PIC:
	*ptr_bit = &DATA_FROM_PIC;
	break;
  case PL_CLOCK:
	*ptr_bit = &CLOCK;
	break;
  case PL_VPP:
	*ptr_bit = &MCLR_VPP;
	break;
  case PL_VCC:
	*ptr_bit = &VCC_ON;
	break;
  }
}

/***********************************************************************
 * Get the mask value of a signal.
 *
 * @param  PIN_LABEL name        IN  Signal name
 * @return void
 **********************************************************************/
unsigned char GetMask(PIN_LABEL name)
{
  pr_Signal the_bit;
  SelectBit(&the_bit, name);
  return (the_bit->mask);
}

/***********************************************************************
 * Get the Status value of a signal.
 *
 * @param  PIN_LABEL name        IN  Signal name
 * @return void
 **********************************************************************/
unsigned char GetStatus(PIN_LABEL name)
{
  pr_Signal the_bit;
  SelectBit(&the_bit, name);
  return (the_bit->status);
}

/* End of file */

