/*
 * Interrupts example
 *
 */

#include <stdio.h>
#include "xil_printf.h"
#include "xparameters.h"
#include "xgpio.h"
#include "xtmrctr.h"
#include "xscugic.h"


/*
 * Defines for the constants needed in the code
 *
 */

#define TIMER_COUNTER_0	 0
#define GPIO_CHAN_1 1
#define ASCENDING_MASK 0x1
#define DESCENDING_MASK 0x2
//uncomment ONLY ONE of the options
#define OPTION_A
//#define OPTION_B


/*
 * Type declarations and global variables
 *
 */

typedef struct {
  XTmrCtr *TimerPtr;
  XGpio *GpioLedsPtr;
} TimerHandlerArg_t;

int Step = 0, Ascending = 0, IntFlag = 0;


/*
 * Function prototypes
 *
 */

#ifdef OPTION_A
static void TimerHandler(void *CallBackRef, u8 TmrCtrNumber);
#endif
#ifdef OPTION_B
static void TimerHandler(void *CallBackRef);
#endif
static void GpioButtonsHandler(void *CallBackRef);
int SetupPeripherals(XGpio *GpioButtonsPtr, XGpio *GpioLedsPtr, XTmrCtr *TimerPtr);


/*
 * Timer ISR (Interrupt Service Routine)
 *
 */

#ifdef OPTION_A
static void TimerHandler(void *CallBackRef, u8 TmrCtrNumber) {

  // This variable is not used here, so followed line is commented out
  //XTmrCtr *TimerPtr = (XTmrCtr *) CallBackRef;

  IntFlag = 1;
}
#endif

#ifdef OPTION_B
static void TimerHandler(void *CallBackReg) {

  TimerHandlerArg_t *TimerHandlerArgPtr = (TimerHandlerArg_t *) CallBackReg;
  XTmrCtr *TimerPtr = TimerHandlerArgPtr->TimerPtr;
  XGpio *GpioLedsPtr = TimerHandlerArgPtr->GpioLedsPtr;

  uint32_t csr;

  // Read the control/status register to check interrupt flag bit
  csr = XTmrCtr_ReadReg(TimerPtr->BaseAddress, TIMER_COUNTER_0, XTC_TCSR_OFFSET);

  if (csr & XTC_CSR_INT_OCCURED_MASK) {
    // Interrupt acknowledgement: Clear the timer interrupt
    csr |= XTC_CSR_INT_OCCURED_MASK;
    XTmrCtr_WriteReg(TimerPtr->BaseAddress, TIMER_COUNTER_0, XTC_TCSR_OFFSET, csr);
    // Application specific code

	  if (Ascending) {
	      XGpio_DiscreteWrite(GpioLedsPtr, 1, Step++);
	      if (Step>63) Step=0;
	  } else {
	      XGpio_DiscreteWrite(GpioLedsPtr, 1, Step--);
	      if (Step<0) Step=63;
	  }
	  IntFlag = 0;
  }
}
#endif


/*
 * GPIO ISR
 *
 */

static void GpioButtonsHandler(void *CallBackRef) {

  XGpio *GpioButtonsPtr = (XGpio *) CallBackRef;

  if (XGpio_DiscreteRead(GpioButtonsPtr, GPIO_CHAN_1) & ASCENDING_MASK) Ascending = 1;
  if (XGpio_DiscreteRead(GpioButtonsPtr, GPIO_CHAN_1) & DESCENDING_MASK) Ascending = 0;

  XGpio_InterruptClear(GpioButtonsPtr, XGPIO_IR_CH1_MASK);
}


/*
 * Function to initialize peripherals
 *
 */

int SetupPeripherals(XGpio *GpioButtonsPtr, XGpio *GpioLedsPtr, XTmrCtr *TimerPtr) {

  int Status;

  Status = XGpio_Initialize(GpioButtonsPtr, XPAR_GPIO_0_DEVICE_ID);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Initialization of buttons GPIO failed \n\r");
      return XST_FAILURE;
  }

  Status = XGpio_Initialize(GpioLedsPtr, XPAR_GPIO_1_DEVICE_ID);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Initialization of LEDs GPIO failed \n\r");
      return XST_FAILURE;
  }

  Status = XTmrCtr_Initialize(TimerPtr, XPAR_TMRCTR_0_DEVICE_ID);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Initialization of Timer failed \n\r");
      return XST_FAILURE;
  }

  return XST_SUCCESS;
}


/*
 * Function to initialize interrupt subsystem
 *
 */

#ifdef OPTION_A
int SetupInterrupts(XScuGic *GicPtr,
		    XTmrCtr *TimerPtr, XTmrCtr_Handler TimerHandler,
		    XGpio *GpioButtonsPtr, Xil_ExceptionHandler GpioButtonsHandler,
		    TimerHandlerArg_t *TimerHandlerArgPtr) {
#endif
#ifdef OPTION_B
int SetupInterrupts(XScuGic *GicPtr,
		    XTmrCtr *TimerPtr, Xil_ExceptionHandler TimerHandler,
		    XGpio *GpioButtonsPtr, Xil_ExceptionHandler GpioButtonsHandler,
		    TimerHandlerArg_t *TimerHandlerArgPtr) {
#endif

  int Status;
  XScuGic_Config *GicCfgPtr;

  // GIC initialization
  GicCfgPtr = XScuGic_LookupConfig(XPAR_SCUGIC_0_DEVICE_ID);
  if(GicCfgPtr == NULL) {
      printf("ERROR: GIC configuration not found \n\r");
      return XST_FAILURE;
  }

  Status = XScuGic_CfgInitialize(GicPtr, GicCfgPtr, GicCfgPtr->CpuBaseAddress);
  if (Status != XST_SUCCESS) {
      printf("ERROR: GIC initialization failed \n\r");
      return Status;
  }

  // Register timer interrupts
#ifdef OPTION_A
  XTmrCtr_SetHandler(TimerPtr, TimerHandler, TimerPtr);

  Status = XScuGic_Connect(GicPtr, XPAR_FABRIC_TMRCTR_0_VEC_ID, (Xil_ExceptionHandler) XTmrCtr_InterruptHandler, (void *) TimerPtr);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Could not register Timer ISR in GIC \n\r");
      return Status;
  }
#endif

#ifdef OPTION_B
  Status = XScuGic_Connect(GicPtr, XPAR_FABRIC_TMRCTR_0_VEC_ID, (Xil_ExceptionHandler) TimerHandler, (void *) TimerHandlerArgPtr);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Could not register Timer ISR in GIC \n\r");
      return Status;
  }
#endif

  // Register GPIO interrupts
  Status = XScuGic_Connect(GicPtr, XPAR_FABRIC_GPIO_0_VEC_ID, (Xil_ExceptionHandler) GpioButtonsHandler, (void *) GpioButtonsPtr);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Could not register GPIO ISR in GIC \n\r");
      return Status;
  }

  // Initialize exception system
  Xil_ExceptionInit();
  Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_IRQ_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, GicPtr);

  // Enable interrupts at GIC
  XScuGic_Enable(GicPtr, XPAR_FABRIC_TMRCTR_0_VEC_ID);
  XScuGic_Enable(GicPtr, XPAR_FABRIC_GPIO_0_VEC_ID);

  return XST_SUCCESS;
}


/*
 * main
 *
 */

int main()
{
  XGpio GpioLeds, GpioButtons;
  XTmrCtr Timer;
  XScuGic Gic;
  TimerHandlerArg_t TimerHandlerArg;
  int Status;

  printf("Interrupt Example \n\r");

  // Set up peripherals
  Status = SetupPeripherals(&GpioButtons, &GpioLeds, &Timer);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Set up of peripherals failed \n\r");
      return XST_FAILURE;
  }

  // Set up interrupts
  TimerHandlerArg.TimerPtr = &Timer;
  TimerHandlerArg.GpioLedsPtr = &GpioLeds;
  Status = SetupInterrupts(&Gic, &Timer, TimerHandler, &GpioButtons, GpioButtonsHandler, &TimerHandlerArg);
  if (Status != XST_SUCCESS) {
      printf("ERROR: Set up of interrupts subsystem failed \n\r");
      return XST_FAILURE;
  }

  // Configure timer
  XTmrCtr_SetResetValue(&Timer, TIMER_COUNTER_0, 500000);
  XTmrCtr_SetOptions(&Timer, TIMER_COUNTER_0, XTC_INT_MODE_OPTION | XTC_DOWN_COUNT_OPTION | XTC_AUTO_RELOAD_OPTION);

  // Configure GPIO interrupts
  XGpio_InterruptEnable(&GpioButtons, XGPIO_IR_CH1_MASK);
  XGpio_InterruptGlobalEnable(&GpioButtons);

  // Global interrupt enable
  Xil_ExceptionEnable();

  // Start timer
  XTmrCtr_Start(&Timer, TIMER_COUNTER_0);

  // Main loop
  while(1) {
#ifdef OPTION_A
      if (IntFlag) {
	  if (Ascending) {
	      XGpio_DiscreteWrite(&GpioLeds, 1, Step++);
	      if (Step>63) Step=0;
	  } else {
	      XGpio_DiscreteWrite(&GpioLeds, 1, Step--);
	      if (Step<0) Step=63;
	  }
	  IntFlag = 0;
      }
#endif
  }

  // Exit (never reached)
  return 0;
}
