/* Medium Level TDC V1190 Driver - Original driver from JFC@IPN-CNRS for the V767 - MG xx/08/2005 @ LIXAM-CNRS*/
/* xx/02/2006 : Improvement of called functions for higher level programming */

/* ============================================================================================================================ */
/* INCLUDE */
#include "tdc1190_driver_medium.h"

/* ============================================================================================================================ */
/* DEFINES */

#define READ_16                     0
#define WRITE_16                    1
#define READ_WRITE_16               2
#define READ_32                     3
#define WRITE_32                    4
#define READ_WRITE_32               5

#define NOT_A_WRITABLE_REGISTER     TDC_ERROR|0X01
#define NOT_A_READABLE_REGISTER     TDC_ERROR|0X02
#define NOT_A_VALID_REGISTER        TDC_ERROR|0X03
#define WRITE_REGISTER_ERROR        TDC_ERROR|0X04
#define READ_REGISTER_ERROR         TDC_ERROR|0X05

#define NOT_A_WRITABLE_OPCODE       TDC_ERROR|0X06
#define NOT_A_READABLE_OPCODE       TDC_ERROR|0X07
#define NOT_A_VALID_OPCODE          TDC_ERROR|0X08
#define WRITE_OPCODE_ERROR          TDC_ERROR|0X09
#define READ_OPCODE_ERROR           TDC_ERROR|0x0A
#define NOT_VALID_HANDSHAKE         TDC_ERROR|0x0B
#define CANNOT_WRITE_OPCODE_DATA    TDC_ERROR|0x0C
#define CANNOT_READ_OPCODE_DATA     TDC_ERROR|0x0D
#define CANNOT_READ_OUTPUT_REGISTER TDC_ERROR|0x0E
#define CANNOT_ACCESS_MMAP_OUTPUT   TDC_ERROR|0x0F

#define MICRO_READY_TO_WRITE        0x1
#define MICRO_READY_TO_READ         0x2


/* --------------------------------------------------------------------------------------------------------------------- */
/* CONSTANT */

unsigned int Global_Header[8]  ={0xF8000000,0x1B,0x07FFFFE0,0x05,0x0000001F,0x00,0x00000000,0x0};
unsigned int Global_Trailer[8] ={0xF8000000,0x1B,0x07000000,0x18,0x001FFFE0,0x05,0x0000001F,0x0};
unsigned int TDC_Measurement[8]={0xF8000000,0x1B,0x04000000,0x1A,0x03F80000,0x13,0x0007FFFF,0x0};
unsigned int TDC_Header[8]     ={0xF8000000,0x1B,0x03000000,0x18,0x00FFFF00,0x08,0x000000FF,0x0};
unsigned int TDC_Trailer[8]    ={0xF8000000,0x1B,0x03000000,0x18,0x00FFF000,0x0C,0x00000FFF,0x0};
unsigned int TDC_Error[8]      ={0xF8000000,0x1B,0x03000000,0x18,0x00007FFF,0x00,0x00000000,0x0};
unsigned int Trigger_Time[8]   ={0xF8000000,0x1B,0x07FFFFFF,0x00,0x00000000,0x00,0x00000000,0x0};


/* ============================================================================================================================ */
/* LOCAL PROTOTYPES */
static int test_register(int);
static int test_opcode(opcode *op);
static int wait_micro(vme_access *tdc_access, unsigned int mask);

/* ============================================================================================================================ */
/* Test the nature of the register that the user try to access.
 
input: register type (CAEN doc. Chap. 6, see also tdc1190_driver.h)
ouput: READ_16, WRITE_16,READ_WRITE_16,...., NOT_A_VALID_REGISTER
*/ 
static int test_register(int reg)
{
  int type=NO_TDC_ERROR;
  switch (reg) 
    {
    case OUTPUT_REGISTER:        type=READ_32;
      break;
    case CONTROL_REGISTER:       type=READ_WRITE_16;
      break;      
    case STATUS_REGISTER:	 type=READ_16;
      break;
    case INTERRUPT_LEVEL:        type=READ_WRITE_16;
      break;
    case INTERRUPT_VECTOR:       type=READ_WRITE_16; 
      break; 
    case GEO_ADDRESS_REGISTER:   type=READ_WRITE_16;
      break;
    case MCST_BASE_ADDRESS:      type=READ_WRITE_16;
      break;
    case MCST_CONTROL:           type=READ_WRITE_16;
      break;
    case MODULE_RESET:           type=WRITE_16;
      break;
    case SOFTWARE_CLEAR:         type=WRITE_16;
      break;
    case SOFTWARE_EVENT_RESET:   type=WRITE_16;
      break;
    case SOFTWARE_TRIGGER:       type=WRITE_16;
      break;
    case EVENT_COUNTER:          type=READ_32;
      break;
    case EVENT_STORED:           type=READ_16;
      break;
    case ALMOST_FULL_EVENT:      type=READ_WRITE_16;
      break;      
    case BTL_EVENT_NUMBER:       type=READ_WRITE_16;
      break;
    case FIRMWARE_VERSION:       type=READ_16;
      break;
    case TEST_REG:               type=READ_WRITE_32;   
      break;  
    case OUTPUT_PROG_CONTROL:    type=READ_WRITE_16;
      break;	
    case MICRO:                  type=READ_WRITE_16;   
      break;   
    case MICRO_HANDSHAKE:        type=READ_16;
      break;
    case SELECT_FLASH:           type=READ_WRITE_16;
      break;	
    case FLASH_MEMORY:           type=READ_WRITE_16;
      break;
    case SRAM_PAGE:              type=READ_WRITE_16;
      break;
    case EVENT_FIFO:             type=READ_32;
      break;	
    case EVENT_FIFO_STORED:      type=READ_16;	
      break;
    case EVENT_FIFO_STATUS:      type=READ_16;
      break;	
    case DUMMY32:                type=READ_WRITE_32;
      break;	
    case DUMMY16:                type=READ_WRITE_16;
      break;
    case CONFIGURATION_ROM:      type=READ_32;
      break;		
    case CONFIGURATION_SRAM:     type=READ_32;
      break;
    default:                     type=NOT_A_VALID_REGISTER;
      break;
    }
  return type;
}

/* ============================================================================================================================ */
/* Test the operating command of that the user try to pass and define the number of bytes to read or write, as well 
as the amount of significant bits
 
input: operating code (CAEN doc. Chap. 5, see also tdc1190_driver.h)
ouput: An error NOT_A_VALID_OPCODE, or no error with the opcode structure "initialize" to send/get data.
*/
static int test_opcode(opcode *op)
{
  /*Init the default values */
  op->nW=0;
  op->nR=0;
  op->nbit=0;

  /* Command Analysis */
  switch (op->command) {
  case TRG_MATCH:                                          break;
  case CONT_STOR:                                          break;
  case READ_ACQ_MOD:                   op->nR=1;op->nbit=1;break;
  case SET_KEEP_TOKEN:                                     break;	
  case CLEAR_KEEP_TOKEN:                                   break;
  case LOAD_DEF_CONFIG:                                    break;
  case SAVE_USER_CONFIG:                                   break;
  case LOAD_USER_CONFIG:                                   break;
  case AUTOLOAD_USER_CONF:                                 break;
  case AUTOLOAD_DEF_CONF:                                  break;
    
  case SET_WIN_WIDTH:                 op->nW=1;op->nbit=12;break;
  case SET_WIN_OFFS:                  op->nW=1;op->nbit=12;break;
  case SET_SW_MARGIN:                 op->nW=1;op->nbit=12;break;
  case SET_REJ_MARGIN:                op->nW=1;op->nbit=12;break;
  case EN_SUB_TRG:                                         break;
  case DIS_SUB_TRG:                                        break;
  case READ_TRG_CONF:                 op->nR=5;op->nbit=49;break;

  case SET_DETECTION:                  op->nW=1;op->nbit=2;break;
  case READ_DETECTION:                 op->nR=1;op->nbit=2;break;
  case SET_TR_LEAD_LSB:                op->nW=1;op->nbit=2;break;
  case SET_PAIR_RES:                  op->nW=1;op->nbit=16;break;
  case READ_RES:                      op->nR=1;op->nbit=16;break;
  case SET_DEAD_TIME:                  op->nW=1;op->nbit=2;break;
  case READ_DEAD_TIME:                 op->nR=1;op->nbit=2;break;

  case EN_HEAD_TRAILER:                                    break;
  case DIS_HEAD_TRAILER:                                   break;
  case READ_HEAD_TRAILER:              op->nR=1;op->nbit=1;break;
  case SET_EVENT_SIZE:                            op->nW=1;break;
  case READ_EVENT_SIZE:                op->nR=1;op->nbit=1;break;
  case EN_ERROR_MARK:                                      break;

  case DIS_ERROR_MARK:                                     break;
  case EN_ERROR_BYPASS:                                    break;
  case DIS_ERROR_BYPASS:                                   break;
  case SET_ERROR_TYPES:               op->nW=1;op->nbit=11;break;
  case READ_ERROR_TYPES:              op->nR=1;op->nbit=11;break;
  case SET_FIFO_SIZE:                  op->nW=1;op->nbit=3;break;
  case READ_FIFO_SIZE:                 op->nR=1;op->nbit=3;break;
    
  case EN_CHANNEL:                                         break;
  case DIS_CHANNEL:                                        break;
  case EN_ALL_CH:                                          break;
  case DIS_ALL_CH:                                         break;
  case WRITE_EN_PATTERN:             op->nW=8;op->nbit=128;break;
  case READ_EN_PATTERN:              op->nR=8;op->nbit=128;break;
  case WRITE_EN_PATTERN32:            op->nW=2;op->nbit=32;break;
  case READ_EN_PATTERN32:             op->nR=2;op->nbit=32;break;
    
  case SET_GLOB_OFFS:                 op->nW=2;op->nbit=17;break;
  case READ_GLOB_OFFS:                op->nR=2;op->nbit=17;break;
  case SET_ADJUST_CH:                  op->nW=1;op->nbit=8;break;
  case READ_ADJUST_CH:                 op->nR=1;op->nbit=8;break;
  case SET_RC_ADJ:                    op->nW=1;op->nbit=12;break;
  case READ_RC_ADJ:                   op->nR=1;op->nbit=12;break;
  case SAVE_RC_ADJ:                                        break;

  case READ_TDC_ID:                   op->nR=2;op->nbit=32;break;
  case READ_MICRO_REV:                 op->nR=1;op->nbit=8;break;
  case RESET_DLL_PLL:                                      break;

  case WRITE_SETUP_REG:               op->nW=1;op->nbit=16;break;
  case READ_SETUP_REG:                op->nR=1;op->nbit=16;break;
  case UPDATE_SETUP_REG:                                   break;
  case DEFAULT_SETUP_REG:                                  break;
  case READ_ERROR_STATUS:             op->nR=1;op->nbit=11;break;
  case READ_DLL_LOCK:                  op->nR=1;op->nbit=1;break;
  case READ_STATUS_STREAM:            op->nR=4;op->nbit=62;break;       
   
  case WRITE_EEPROM:                  op->nW=2;op->nbit=16;break;
  case READ_EEPROM:          op->nR=1;op->nW=1;op->nbit=8 ;break; /*Sepcial....do not work ?*/
  case REVISION_DATE_MICRO_FIRMWARE:  op->nR=4;op->nbit=64;break;
  case WRITE_SPARE:                   op->nW=1;op->nbit=16;break;
  case READ_SPARE:                    op->nR=1;op->nbit=16;break;
  case ENABLE_TEST_MODE:              op->nW=2;op->nbit=24;break;
  case DISABLE_TEST_MODE:                                  break;
  case SET_TDC_TEST_OUTPUT:            op->nW=1;op->nbit=4;break;
  case SET_DLL_CLOCK:                  op->nW=1;op->nbit=2;break;
  case READ_TDC_SETUP_SCAN_PATH:    op->nR=41;op->nbit=646;break;
  default:                      return NOT_A_VALID_OPCODE; break;
  }
  return NO_TDC_ERROR;
}

/* ============================================================================================================================ */
/* Give the status of the micro controller using the MICRO_HANDSHAKE register.

inputs: the physical address of the TDC board - (unsigned int)
        a pointer to get the status - (unsigned int *)
output: Error code or NOT_TDC_ERROR with the status (1 ready to read; 2 ready to write)       
 */
int status_micro(vme_access *tdc_access,unsigned int *status )
{ 
  int res=0;
  res=read_register(tdc_access,MICRO_HANDSHAKE, status);
  if (res) return res;
  return NO_TDC_ERROR;
}

/* ============================================================================================================================ */
/* Wait for the micro controller to be abble to read or write.
inputs: the physical address of the TDC board - (unsigned int)
        a mask to define what is the wanted status of the micro-controller,i.e. read or write - (unsigned int)
output: Error code or NOT_TDC_ERROR  
 */
static int wait_micro(vme_access *tdc_access, unsigned int mask)
{

#ifdef ON_BOARD
  int res=0;
  unsigned int handshake=0;
  do {
    res=status_micro(tdc_access,&handshake);
    if (res) return res;
    handshake=handshake & mask;
  } while (handshake!=mask);
  /* usleep(10000); Do not work here ?? */
#endif
  return NO_TDC_ERROR;
}

/* ============================================================================================================================ */
/* Write parameter into the TDC register.

inputs: the physical address of the TDC board - (unsigned int)
        register address to write the data -(int)
	data value  - (unsigned int)
output: Error code or NO__TDC_ERROR
*/
int write_register(vme_access *tdc_access, int reg, unsigned int data)
{
  int type;
  /* Check the register */
  type=test_register(reg);
#ifdef ON_BOARD 
   switch (type)
    {
    case WRITE_16:
    case READ_WRITE_16: write_vme(tdc_access, A24,D16SP,reg,data);
      break;
    case WRITE_32:
    case READ_WRITE_32: write_vme(tdc_access, A32,D32SP,reg,data);
      break;
    default: return NOT_A_WRITABLE_REGISTER;
    }
#endif
  return NO_TDC_ERROR;
}

/* ============================================================================================================================ */
/* Read parameter from the TDC register.

inputs: the physical address of the TDC board - (unsigned int)
        register address to write the data -(int)
	pointer to the data variable  - (unsigned short int)
output: Error code or NO_TDC_ERROR
*/

int read_register(vme_access *tdc_access, int reg, unsigned int *data)
{
  int type;
  /* Check the register */
  type=test_register(reg);

#ifdef ON_BOARD
  switch (type)
    {
    case READ_16:
    case READ_WRITE_16: read_vme(tdc_access,A24,D16SP,reg,data);
      break;
    case READ_32:
    case READ_WRITE_32: read_vme(tdc_access,A32,D32SP,reg,data);
      break;
    default: return NOT_A_WRITABLE_REGISTER;
    }
#endif
  return NO_TDC_ERROR;
}


/* ============================================================================================================================ */
/* Write to the micro controller commands (called "Operating Code"). In order to access to 
the micro-controller, two registers are used:
- MICRO : Read/Write register (16 bits)
- MICRO_HANDSHAKE: to give the status of the micro-controller. (see "wait_micro" "status_micro")
A maximum number of 42 words (16 bits) can be written using the structure (opcode) and the table (operand).

inputs: the physical address of the TDC board - (unsigned int)
        a pointer to an operating code structure, i.e. command + object + data  - (opcode)
output: Error code or NOT_TDC_ERROR       
 */
int write_micro(vme_access *tdc_access, opcode *op)
{
  int i;
  /* Test the input get the number of bit to write*/
  if (test_opcode(op)) return NOT_A_VALID_OPCODE;
  if (op->nR!=0) return NOT_A_WRITABLE_OPCODE; /* do not work for the Read eeprom */
  
  /* Send the opcode: concat command+object, wait handshake, send data with a loop if required...*/
  op->command=op->command<<8|op->object;

  if(wait_micro(tdc_access,MICRO_READY_TO_WRITE)) return NOT_VALID_HANDSHAKE;
  if(write_register(tdc_access,MICRO,op->command)) return WRITE_OPCODE_ERROR;

  if(op->nW>0)
    for(i=0;i<op->nW;i++)
      {
	if (wait_micro(tdc_access,MICRO_READY_TO_WRITE)) return NOT_VALID_HANDSHAKE;
	if (write_register(tdc_access,MICRO,op->operand[i])) return CANNOT_WRITE_OPCODE_DATA;
      };
  return NO_TDC_ERROR;
}

/* ============================================================================================================================ */
/* Read the micro controller data after a read command. A maximum number of 42 words (16 bits) can be read.

inputs: the physical address of the TDC board - (unsigned int)
        a pointer to an operating code structure, i.e. command + object + data  - (opcode)
output: Error code or NOT_TDC_ERROR, the data are store in the structure       
 */
int read_micro(vme_access *tdc_access, opcode *op)
{
  int i;
  /* Test the input get the number of bit to read*/
  if (test_opcode(op)) return NOT_A_VALID_OPCODE;
  if (op->nW!=0) return NOT_A_WRITABLE_OPCODE; /* do not work for the Read eeprom */
  
  /* Send the opcode: concat command+object, wait handshake, send data with a loop if required...*/
  op->command=op->command<<8|op->object;

  if(wait_micro(tdc_access,MICRO_READY_TO_WRITE)) return NOT_VALID_HANDSHAKE;
  if(write_register(tdc_access,MICRO,op->command)) return READ_OPCODE_ERROR;

  if(op->nR>0)
    for(i=0;i<op->nR;i++)
      {
	if (wait_micro(tdc_access,MICRO_READY_TO_READ)) return NOT_VALID_HANDSHAKE;
	if (read_register(tdc_access,MICRO,&op->operand[i])) return CANNOT_READ_OPCODE_DATA;
      };
  return NO_TDC_ERROR;
}
/* ============================================================================================================================ */
/* Read status register with interuption 
 inputs : structure to access to the tdc (see "open_vme" in vme_driver.c) - (vme_access)
          pointer to an integer to store the status in a formated form -(unsigned int *status see CAEN doc.Chap 6.4)
output : error message, or no error message with the formated tdc status 
*/
int read_status(vme_access *tdc_access, unsigned int *status)
{
  int res=0;
  res=read_register(tdc_access,STATUS_REGISTER,status);
  return res;
}
/* ============================================================================================================================ */
/* Read tdc output register with interruption
 inputs : structure to access to the tdc (see "open_vme" in vme_driver.c) - (vme_access)
          pointer to an array of 5 integers to store unformated output -(unsigned int *out <-> unsigned int out[])
output : error message, or no error message with the unformated output 
         out[0]: type of data (header, trailer, measurement, error...
	 out[1...n] : data 
	 out[n+1] : end character (-1 <-> &FFFFFFFF) 
*/
int read_output(vme_access *tdc_access, long *out)
{
  unsigned int value;
  if (read_register(tdc_access,OUTPUT_REGISTER ,&value)) return CANNOT_READ_OUTPUT_REGISTER;
  convert_output(value,out); /* convert the formated output in an array -> unformated output */ 
  return NO_TDC_ERROR;
}

/* ============================================================================================================================ */
/* Read tdc output register using a DMA - it is about 4 times faster for a VME2700@200MHz-
 inputs : a structure to access to the tdc (see "open_vme" in vme_driver.c) - (vme_access)
         a pointer to an array of 5 integers to store unformated output -(unsigned int *out <-> unsigned int out[])
output : error message, or no error message with the unformated output 
         out[0]: type of data (header, trailer, measurement, error...
	 out[1...n] : data 
	 out[n+1] : end character (-1 <-> &FFFFFFFF) 
*/
int read_MMAPoutput(vme_access *tdc_access, long *out)
{
  unsigned int value;
#ifdef ON_BOARD
  if(tdc_access->mmap_address==NULL)
      if (init_mmap(tdc_access,A32)) return CANNOT_ACCESS_MMAP_OUTPUT;
  value=read_mmap(tdc_access,OUTPUT_REGISTER,D32);
#endif
  convert_output(value,out); /* convert the formated output in an array -> unformated output */ 
  return NO_TDC_ERROR;
}

/* ============================================================================================================================ */
/* Convert the formated ouptut register into unformated format
inputs: formated value - (unsigned int)
        a pointer to an array of 5 integers to store unformated output -(unsigned int *out <-> unsigned int out[])
output : if there is no error, the unformated output is
           out[0]: type of data (header, trailer, measurement, error...
	   out[1...n] : data 
	   out[n+1] : end character (-1 <-> &FFFFFFFF)
	 if there is an error, the output is 
	   out[1]: raw data
	   out[2]= end character (-1 <-> &FFFFFFFF)
*/
void convert_output(unsigned int value, long *out)
{
  long *table;
  table=out;
  *(table++)=(value&0xF8000000)>>0x1B; /* Get word type: header, trailer, measurement... See CAEN doc.Chap 6.2 */

  switch (*out) /* Convert into an array */
    {
    case 0: /* Measurement */ 
      {
	/*printf("Value from tdc=%x\n",value);*/
	*(table++)=(value&0x04000000)>>0x1A;
	*(table++)=(value&0x03F80000)>>0x13;
	*(table++)=value&0x0007FFFF;
	*(table++)=-1;
	break;
      }

    case 1: /* TDC Header */
      {
	*(table++)=(value&0x03000000)>>0x18;
	*(table++)=(value&0x00FFFF00)>>0x08;
	*(table++)= value&0x000000FF;
	*(table++)=-1;
	break;
      }
    case 3: /* TDC Tailer*/
      {
	*(table++)=(value&0x03000000)>>0x18;
	*(table++)=(value&0x00FFF000)>>0x0C;
	*(table++)=value&0x00000FFF;
	*(table++)=-1;
	break;
      }
    case 4: /* TDC Error ? */
      {
	*(table++)=(value&0x03000000)>>0x18;
	*(table++)=value&0x00007FFF;
	*(table++)=-1;;
	break;
      }
    case 8: /* Global Header */
      {
	*(table++)=(value&0x07FFFFE0)>>0x05;
	*(table++)=value&0x0000001F;
	*(table++)=-1;
	break;
      }
    case 16: /*Global Trailer*/
      {
	*(table++)=(value&0x07000000)>>0x18;
	*(table++)=(value&0x001FFFE0)>>0x05;
	*(table++)=value&0x0000001F;
	*(table++)=-1;
	break;
      }
    case 17: /*Trigger Time*/
      {
	*(table++)=value&0x07FFFFFF;
	*(table++)=-1;
	break;
      }
    case 24:/* Filler*/
      {
	*(table++)=value&0x07FFFFFF;
	*(table++)=-1;
	break;
      }
    default :
      {
	printf("Strange Data !!!\n");
	*(table++)=value&0xFFFFFFFF;
	*(table++)=-1;
	break;
      }
    }
}

/* ============================================================================================================================ */
/* Print the output register 
input : a pointer to an array of 5 integers where the unformated output is store
*/
void print_output(long *out)
{
  long *table;
  int i=0;

  table=out;
  printf("Event - Type : %lu Values:",*(table++));
  do
  {
    printf("%lu  /  ",*(table++));
    i+=1;
  } while ((*(table)!=-1)&(i<4));
  printf("\n");
}
