/*                                       -*- mode:C++; fill-column: 100 -*-
  SFBInterrupts.c - Support for user-level interrupt handling
  Copyright (C) 2009 The Regents of the University of New Mexico.  All rights reserved.

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

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

  You should have received a copy of the GNU General Public License
  along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
  USA

  $Id$
*/

#include "SFBInterrupts.h" /* our declarations */
#include "SFBWiring.h" /* for pinMode_* */
#include "SFBAssert.h" /* API_ASSERT_* */

#include "lpc/LPC23xx.h" /* get the hardware definitions */
#include "lpc/irq.h" /* for disableInterrupts */

#define MAX_GPIO_INTERRUPTS 8

/**
 * Information about an external interrupt handler attached to a GPIO
 * pin.
 */
struct GPIOInterruptHandlerInfo {
  u8 sfbPin;
  u8 port;          // 0->port0, 1->port2
  u8 pinIndex;      // 0..31
  u8 rfe;           // 1->rise,2->fall,3->either
  IRQHandler * handlerFunction;
} gpioDescriptors[MAX_GPIO_INTERRUPTS] IN_BODYRAM;
u8 gpioIntInUse IN_BODYRAM = 0;

// port: 0 or 2; fall: 0 (rise) or 1 (fall)
#define GPIO_INT_STATUS(port,fall) \
  (*(volatile unsigned long *)((GPIO_BASE_ADDR + 0x84)+(fall)*4+((port)*0x10)))
#define GPIO_INT_CLEAR(port) \
  (*(volatile unsigned long *)((GPIO_BASE_ADDR + 0x8C)+((port)*0x10)))
#define GPIO_INT_ENABLE(port,fall) \
  (*(volatile unsigned long *)((GPIO_BASE_ADDR + 0x90)+(fall)*4+((port)*0x10)))

static void enableGpio(u32 port, u32 pinIndex, u32 mode) {
  u32 mask = 1<<pinIndex;
  if (mode&RISING)  GPIO_INT_ENABLE(port,0) |= mask;
  else              GPIO_INT_ENABLE(port,0) &= ~mask;
  if (mode&FALLING) GPIO_INT_ENABLE(port,1) |= mask;
  else              GPIO_INT_ENABLE(port,1) &= ~mask;
}

static void disableGpio(u32 port, u32 pinIndex) {
  enableGpio(port,pinIndex,0);
}

static void EINT3Dispatcher(void) {

  if (IO_INT_STAT & ((1<<0)|(1<<2))) { // if any interrupts pending

    for (u32 i = 0; i < gpioIntInUse; ++i) {
      GPIOInterruptHandlerInfo & ihi = gpioDescriptors[i];
      const u32 mask = 1<<ihi.pinIndex;

      if (((ihi.rfe&RISING)  && (GPIO_INT_STATUS(ihi.port,0)&mask)) ||
          ((ihi.rfe&FALLING) && (GPIO_INT_STATUS(ihi.port,1)&mask))) {

        ihi.handlerFunction();

      }
      GPIO_INT_CLEAR(ihi.port) = mask;
    }
  }
  EXTINT = 1<<3;  // Clear EINT3
}

static void startGpioInterrupts() {

  u32 status;
  suspendInterrupts(status);

  VICIntEnClr = 1 << EINT3_INT;	/* Disable interrupt */

  EXTINT = 1<<3;                /* Clear EINT3 defensively */
  install_irq(EINT3_INT,EINT3Dispatcher,MEDIUM_PRIORITY); /* Install handler and enable interrupt */

  restoreInterrupts(status);
}

static void stopGpioInterrupts() {

  u32 status;
  suspendInterrupts(status);

  uninstall_irq(EINT3_INT);     /* Install handler and enable interrupt */
  EXTINT = 1<<3;                /* Clear EINT3 defensively (not needed?) */

  restoreInterrupts(status);
}

void attachInterrupt(u32 sfbPin, IRQHandler handler, u32 mode) {
  API_ASSERT_VALID_PIN(sfbPin);
  API_ASSERT_NONNULL(handler);
  API_ASSERT(mode==RISING||mode==FALLING||mode==CHANGING,E_API_ARGUMENT);

  struct PinInfo_s info;
  u32 port, mask;
 
  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask); // May modify sfbPin!

  API_ASSERT(port==0 || port==2, E_API_BAD_INTERRUPT_PIN);

  pinMode(sfbPin,HW_MODE);      // Take full control of the pin 
  implPinMode(sfbPin,INPUT);    // And make sure it's an input

  if (gpioIntInUse==0)          // First time through,
    startGpioInterrupts();      // fire up the infrastructure

  int i;
  for (i = 0; i < gpioIntInUse; ++i) {
    if (gpioDescriptors[i].sfbPin == sfbPin) 
      break;                    // Found it in use, update
  }

  if (i==gpioIntInUse) {           // Didn't find it
    API_ASSERT_MAX_CODE(gpioIntInUse,MAX_GPIO_INTERRUPTS,E_API_NO_MORE_INTERRUPTS);
    ++gpioIntInUse;
  }

  // Now i is the guy to create/update
  gpioDescriptors[i].sfbPin = sfbPin;
  gpioDescriptors[i].port = port;
  gpioDescriptors[i].pinIndex = info.bitNumber;
  gpioDescriptors[i].rfe = mode;
  gpioDescriptors[i].handlerFunction = handler;

  enableGpio(gpioDescriptors[i].port,gpioDescriptors[i].pinIndex,gpioDescriptors[i].rfe);
}

bool canAttachInterrupt(u32 sfbPin) {
  API_ASSERT_VALID_PIN(sfbPin);
  struct PinInfo_s info;
  u32 port, mask;
  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask); // May modify sfbPin!

  return port==0 || port==2;
}

bool hasAttachedInterrupt(u32 sfbPin) {
  API_ASSERT_VALID_PIN(sfbPin);
  struct PinInfo_s info;
  u32 port, mask;
  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask); // May modify sfbPin!

  if (!(port==0 || port==2))
    return false;

  // OK, now see if we've got anything on that pin..
  int i;
  for (i = 0; i < gpioIntInUse; ++i) {
    if (gpioDescriptors[i].sfbPin == sfbPin)
      return true;
  }
  return false;
}

void detachInterrupt(u32 sfbPin) {
  API_ASSERT_VALID_PIN(sfbPin);
  struct PinInfo_s info;
  u32 port, mask;
  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask); // May modify sfbPin!

  API_ASSERT(port==0 || port==2, E_API_BAD_INTERRUPT_PIN);

  // OK, now see if we've got anything on that pin..
  int i;
  for (i = 0; i < gpioIntInUse; ++i) {
    if (gpioDescriptors[i].sfbPin == sfbPin)   // We do, nuke it
      break;                    // Found it in use, update
  }
  if (i==gpioIntInUse)          // Um, we're not on that pin..
    return;

  disableGpio(gpioDescriptors[i].port,gpioDescriptors[i].pinIndex);
  
  gpioDescriptors[i] = gpioDescriptors[--gpioIntInUse];  // Slot last guy into our spot

  pinMode(sfbPin,REFLEX_MODE);  // Pinback to normal (snap out of it, bomb) 

  if (gpioIntInUse==0)          // If none left
    stopGpioInterrupts();       // give up control

}

