//
// File .......... zedboard_leds_buttons.c
// Author ........ Steve Haywood
// Version ....... 1.0
// Date .......... 15 January 2022
// Description ...
//   Very simple LEDs & Push Buttons interrupt example design.
//


//
// Includes
//
#include "xparameters.h"
#include "xgpio.h"
#include "xscugic.h"
#include "xil_exception.h"
#include <stdio.h>


//
// Defines
//
#define CHANNEL_LEDS 1
#define CHANNEL_BTNS 2

#define BUTTON_CENTER 0x01
#define BUTTON_DOWN   0x02
#define BUTTON_LEFT   0x04
#define BUTTON_RIGHT  0x08
#define BUTTON_UP     0x10

//
// Global Variables
//
static XScuGic Intc;


//
// Instances
//
XGpio Gpio;


//
// Rotate right
//
u8 ror(u8 num) {
  return (num >> 1) | (num << 7);
}


//
// Rotate left
//
u8 rol(u8 num) {
  return (num << 1) | (num >> 7);
}


//
// Interrupt service routine
//
void GpioIsr(void *InstancePtr)
{
  u32 btns;
  u32 leds;
  u8 width = 0;

  XGpio *GpioPtr = (XGpio *)InstancePtr;

  // Disable button interrupts
  XGpio_InterruptDisable(GpioPtr, XGPIO_IR_CH2_MASK);

  // Only deal with button interrupts
  if ((XGpio_InterruptGetStatus(GpioPtr) & XGPIO_IR_CH2_MASK) != XGPIO_IR_CH2_MASK) {
    return;
  }

  // Obtain register values for LED's & buttons
  leds = XGpio_DiscreteRead(&Gpio, CHANNEL_LEDS);
  btns = XGpio_DiscreteRead(&Gpio, CHANNEL_BTNS);

  // Print button status
  xil_printf("ISR called, button register = 0x%x\n\r", btns);

  // Determine LED bar width
  for (u8 i=0; i<=7; i++) {
    if ((0b00000001 << i) & leds) {
      width++;
    }
  }

  // Handle button functionality
  switch (btns) {

    // Set LED bar width to 4
    case BUTTON_CENTER :
      leds = 0b00111100;
      break;

    // Rotate LED's left
    case BUTTON_LEFT :
      if (width == 0) {
        leds = 0b00000001;
      } else {
        leds = (u8)rol(leds);
      }
      break;

    // Rotate LED's right
    case BUTTON_RIGHT :
      if (width == 0) {
        leds = 0b10000000;
      } else {
        leds = (u8)ror(leds);
      }
      break;

    // Increase illuminated LED's
    case BUTTON_UP :
      if (width == 0) {
    	leds = 0b00001000;
      } else if (width & 1) {
        leds = leds | rol(leds);
      } else {
        leds = leds | ror(leds);
      }
      break;

    // Decrease illuminated LED's
    case BUTTON_DOWN :
      if (width == 8) {
    	leds = 0b01111111;
      } else if (width & 1) {
        leds = leds & rol(leds);
      } else {
        leds = leds & ror(leds);
      }
      break;

    // Do nothing
    default:
      break;
  }

  // Update LED's
  XGpio_DiscreteWrite(&Gpio, CHANNEL_LEDS, leds);

  // Clear pending interrupts
  XGpio_InterruptClear(GpioPtr, XGPIO_IR_CH2_MASK);

  // Enable button interrupts
  XGpio_InterruptEnable(GpioPtr, XGPIO_IR_CH2_MASK);
}


//
// Setup interrupt system
//
int SetupInterruptSystem()
{
  int Result;
  XScuGic *IntcInstancePtr = &Intc;

  XScuGic_Config *IntcConfig;

  // Initialise interrupt controller
  IntcConfig = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
  if (NULL == IntcConfig) {
    return XST_FAILURE;
  }

  // Comment
  Result = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig, IntcConfig->CpuBaseAddress);
  if (Result != XST_SUCCESS) {
    return XST_FAILURE;
  }

  // Comment
  XScuGic_SetPriorityTriggerType(IntcInstancePtr, XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR, 0xA0, 0x3);

  // Connect interrupt handler
  Result = XScuGic_Connect(IntcInstancePtr, XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR, (Xil_ExceptionHandler)GpioIsr, &Gpio);
  if (Result != XST_SUCCESS) {
    return Result;
  }

  // Enable GPIO interrupt
  XScuGic_Enable(IntcInstancePtr, XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR);

  // Enable button interrupts
  XGpio_InterruptEnable(&Gpio, XGPIO_IR_CH2_MASK);

  // Enable GPIO interrupts
  XGpio_InterruptGlobalEnable(&Gpio);

  // Initialise the exception table
  Xil_ExceptionInit();

  // Register interrupt controller handle with exception table
  Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, IntcInstancePtr);

  // Enable non-critical exceptions
  Xil_ExceptionEnable();

  return XST_SUCCESS;
}


//
// Main function
//
int main(void)
{
  int Status;

  // Initialise GPIO driver
  Status = XGpio_Initialize(&Gpio, XPAR_AXI_GPIO_0_DEVICE_ID);
  if (Status != XST_SUCCESS) {
    return XST_FAILURE;
  }

  // Clear LED's
  XGpio_DiscreteWrite(&Gpio, CHANNEL_LEDS, 0);

  // Setup interrupt system
  Status = SetupInterruptSystem();
  if (Status != XST_SUCCESS) {
    return XST_FAILURE;
  }

  // Print header
  print("--== LED & Push Buttons interrupt example ==--\n\n\r");
  print("Use the buttons to control the LED's :-\n\n\r");
  print("Centre - Reset LED's to 00111100\n\r");
  print("Left   - Rotate LED's left\n\r");
  print("Right  - Rotate LED's right\n\r");
  print("Up     - Increase illuminated LED's\n\r");
  print("Down   - Decrease illuminated LED's\n\n\r");
  print("Press enter key to quit application\n\r");

  // Wait for user input
  getc(stdin);

  // Clear LED's
  XGpio_DiscreteWrite(&Gpio, CHANNEL_LEDS, 0);

  // Print footer
  print("All done!\n\n\r");

  return XST_SUCCESS;
}

