/*
  SFBWiring.c - The 'Arduinoesque API' provided by the SFB.
  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 "SFBWiring.h" /* our declarations */
#include "SFBReflex.h" /* for reflex_maybe_dispatch() */
#include "SFBErrors.h" /* BUG_UNIMPLEMENTED */
#include "SFBAssert.h" /* API_ASSERT_* */

#include "lpc/LPC23xx.h" /* get the hardware definitions */
#include "lpc/fio.h" /* direct pin access */
#include "lpc/timer.h" /* for timer0_1msec_counter */
#include "lpc/irq.h" /* for disableInterrupts */

void ledToggle(int sfbPin) {
  ledSet(sfbPin,!ledIsOn(sfbPin));
}

void digitalWrite(int sfbPin, int value) {
  struct PinInfo_s info;
  u32 port, mask;
 
  API_ASSERT_VALID_PIN(sfbPin);

  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask);

  if (port==NOT_A_PORT) return;

  SET_SKETCH_FLAG(sfbPin);         /* Remember the sketch has messed with this bit */

  if ((port&~1UL)==0) {            /* i.e., port==0 || port==1 */
    SCS |= GPIOM;                  /* Ensure GPIOx set for fio on legacy regs */        
  }

  /* We have to know whether we're writing on an input or an output
   * pin, because the Arduino semantics overloaded digitalWrite to do
   * both output level setting and input pull-up enabling
   */

  if (SFB_PORT_DIR_REGISTER(port)&mask) {  /* Then it's currently an output */

    SFB_PORT_WRITE_BIT_MASK(port,mask,value);  /* So just write its value */

  } else {                                 /* else it's currently an input */

    reflexSetPinResistor(sfbPin, value?0x0:0x2); /* High value - pullup, low - none */

  }
}

int digitalRead(int sfbPin) {
  struct PinInfo_s info;
  u32 port, mask;

  API_ASSERT_VALID_PIN(sfbPin);

  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask);

  if (port==NOT_A_PORT)  /* hmm? what what? */
    return LOW;

  if (SFB_PORT_GET_BIT_MASK(port,mask))
    return HIGH;
  else
    return LOW;
}

void pinMode(int sfbPin, int mode) {
  struct PinInfo_s info;
  u32 port, mask;

  API_ASSERT_VALID_PIN(sfbPin);

  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask);

  if (port==NOT_A_PORT)
    return;

  /* Do we really need to do this SCS check every time?  We can't do
   * it once and set a flag to remember?  I guess that would hardly
   * be faster.  Being able to assume it had been done would be faster.
   */
  if ((port&~1)==0) {            /* i.e., port==0 || port==1 */
    SCS |= GPIOM;               /* Ensure GPIOx set for fio on legacy regs */        
  }

  if (mode == REFLEX_MODE) {    /* If they're giving control to us.. */
    CLR_SKETCH_FLAG(sfbPin);    /* ..remember that.. */
    mode = info.aliasMode;      /* ..and pick up the reflex mode for that pin */
  } else {
    SET_SKETCH_FLAG(sfbPin);

  }

  switch (mode) {
  case INPUT:  

    setPinSelFunction(port,info.bitNumber,0x0); /* Ensure it's GPIO, and*/ 
    SFB_PORT_DIR_REGISTER(port) &= ~mask;  /* ..clear associated direction.. */
    break;

  case OUTPUT: 
    setPinSelFunction(port,info.bitNumber,0x0); /* Ensure it's GPIO, and*/ 
    SFB_PORT_DIR_REGISTER(port) |= mask;   /* ..set associated direction */
    break;

  case HW_MODE:
    return;                    /* Do not mess with this pin */
  }
}

void delay(u32 ms) {
  unsigned long end = millis()+ms;
	
  while (IS_EARLIER(millis(),end)) {
    reflex_maybe_dispatch(end);
  }

}

/* XXX TODO - NOT REALLY CALIBRATED! STILL WANTS A REAL TUNING! */
#define LOOPS_PER_US (72/4)      // Looks like 4 cycles around the loop, so..
#define LOOP_INIT_OFFSET (-7)    // Totally empirical..
void delayMicroseconds(u32 uSeconds) {
  if (uSeconds == 0)
    return;
  u32 loopCount = uSeconds*LOOPS_PER_US+LOOP_INIT_OFFSET;
  u32 status;
  suspendInterrupts(status);
  __asm__ __volatile__ (
                        "1: subs %0,%0,#1\n\t"
                        "   bpl 1b"
                        : "=r" (loopCount)
                        : "0" (loopCount)
                        : "cc"
                        );
  restoreInterrupts(status);
}

void delayMicrosecondsApproximate(u32 uSeconds) {
  u32 end = micros()+uSeconds;
  while (IS_EARLIER(micros(),end)) { /* spin */ }
}

u32 pulseIn(int sfbPin, int state, u32 timeout) {

  API_ASSERT_VALID_PIN(sfbPin);

  // cache the port and bit of the pin in order to speed up the
  // pulse width measuring loop and achieve finer resolution. calling
  // digitalRead() instead yields much coarser resolution.
  PinInfo_s info;
  u32 port, mask;
  SFB_GET_PIN_PORT_AND_MASK(sfbPin,info,port,mask);

  u32 stateMask = (state ? mask : 0);

  u32 quitTime = micros()+timeout;

  // wait for any previous pulse to end
  while (SFB_PORT_GET_BIT_MASK(port,mask) == stateMask) {
    if (IS_EARLIER(quitTime,micros()))
      return 0;
  }

  // wait for the pulse to start
  while (SFB_PORT_GET_BIT_MASK(port,mask) != stateMask) {
    if (IS_EARLIER(quitTime,micros()))
      return 0;
  }

  // capture start time
  u32 start = micros();

  // wait for the pulse to stop
  while (SFB_PORT_GET_BIT_MASK(port,mask) == stateMask) ;

  return micros()-start;

}

void shiftOut(int dataPin, int clockPin, int bitOrder, u8 val) {
  int i;

  for (i = 0; i < 8; i++) {
    if (bitOrder == LSBFIRST)
      digitalWrite(dataPin, !!(val & (1 << i)));
    else
      digitalWrite(dataPin, !!(val & (1 << (7 - i))));

    digitalWrite(clockPin, HIGH);
    digitalWrite(clockPin, LOW);
  }
}

