/*                                       -*- mode:C++; fill-column: 100 -*-
  SFBHWPinsHost.h Host-specific shims around pin definitions
  Copyright (C) 2008 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$
*/
/**
  \file SFBHWPinsHost.h Host-specific shims around pin definitions
  \author David H. Ackley.  
  \date (C) 2008 All rights reserved.
  \lgpl
 */

#ifdef SFBHWPINSBOARD_H
#error SFBHWPinsHost.h and SFBHWPinsBoard.h must not be #included together!
#endif

#ifndef SFBHWPINSHOST_H
#define SFBHWPINSHOST_H

#include "SFBTypes.h"
#include "SFBAssert.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * An incomplete cut at host-side simulation of the SFB hardware
 * registers.  
 */
struct SFBHWHostRegister {
  SFBHWHostRegister(u32 address, u32 initVal = 0, u32 (*toRead)(u32,u32) = 0, u32 (*toWrite)(u32,u32) = 0) :
    reg(initVal),
    addr(address),
    doRead(toRead),
    doWrite(toWrite)
  { }

  operator u32() const { 
    return doRead?doRead(addr,reg):reg; 
  }
  SFBHWHostRegister & operator=(const u32 val) {
    if (doWrite) reg = doWrite(addr,val);
    else reg = val;
    return *this;
  }
  SFBHWHostRegister & operator&=(const u32 val) {
    if (doWrite) reg = doWrite(addr,reg&val);
    else reg &= val;
    return *this;
  }
  SFBHWHostRegister & operator|=(const u32 val) {
    if (doWrite) reg = doWrite(addr,reg|val);
    else  reg |= val;
    return *this;
  }

  u32 reg;
  u32 addr;
  u32 (*doRead)(u32 addr, u32 oldval);
  u32 (*doWrite)(u32 addr, u32 newval);
};

/**
 * An incomplete cut at host-side simulation of the SFB hardware
 * registers.  
 */
class SFBHWHostRegister ; // Forward
class SFBHWHostRegisterAccess ; // Forward
class SFBHWHostRegisterMap {
  void * vmap;
public:
  SFBHWHostRegisterMap() ;
  ~SFBHWHostRegisterMap() ;
  void addRegister(u32 address, u32 initVal = 0, u32 (*toRead)(u32,u32) = 0, u32 (*toWrite)(u32,u32) = 0) ;
  SFBHWHostRegisterAccess registerAt(u32 address) const ;
  SFBHWHostRegisterAccess registerAtU16(u32 address) const ;
  SFBHWHostRegisterAccess registerAtU8(u32 address) const ;

  SFBHWHostRegister & getRegister(u32 address) ;
  void setMap(void * vmap) ;
  enum { PINMODE_REGISTER_PORT = 0 };
};
extern SFBHWHostRegisterMap hostRegisters;


class SFBHWHostRegisterAccess {
  const u32 addr;
  const u8 size;
  const u8 phase;
public:
  SFBHWHostRegisterAccess(u32 a, u8 s = 4) : addr(a&~3), size(s), phase(a&3) { 
    if (s==4) API_ASSERT_TRUE(phase==0);
    else if (s==2) API_ASSERT_TRUE(phase==0 || phase==2);
    else API_ASSERT_TRUE(s==1);
  }

  operator u32() const { 
    SFBHWHostRegister & reg = hostRegisters.getRegister(addr);
    u32 r = reg;
    switch (size) {
    case 4: 
      return r;
    case 2: 
      switch (phase) {
      case 0: return (r>>0)&0xffff;
      case 2: return (r>>16)&0xffff;
      }
      break;
    case 1: 
      switch (phase) {
      case 0: return (r>>0)&0xff;
      case 1: return (r>>8)&0xff;
      case 2: return (r>>16)&0xff;
      case 3: return (r>>24)&0xff;
      }
      break;
    }
   API_BUG(E_BUG_INCONSISTENT_STATE); 
  }
  //  operator u16() const { return (u16) (u32) *this; }
  //  operator u8() const { return (u8) (u32) *this; }

  SFBHWHostRegisterAccess & operator=(const u32 val) {
    *((const SFBHWHostRegisterAccess*) this) = val;
    return *this;
  }

  const SFBHWHostRegisterAccess & operator=(const u32 val) const {
    SFBHWHostRegister & reg = hostRegisters.getRegister(addr);
    u32 r;
    switch (size) {
    case 4: 
      r = val;
      break;
    case 2: 
      r = (u32) reg;
      switch (phase) {
      case 0: r = (r&0xffff0000)|(((u32)val)<<0);
      case 2: r = (r&0x0000ffff)|(((u32)val)<<16);
      default: API_BUG(E_BUG_INCONSISTENT_STATE); 
      }
      break;
    case 1: 
      r = (u32) reg;
      switch (phase) {
      case 0: r = (r&0xffffff00)|(((u32)val)<<0);
      case 1: r = (r&0xffff00ff)|(((u32)val)<<8);
      case 2: r = (r&0xff00ffff)|(((u32)val)<<16);
      case 3: r = (r&0x00ffffff)|(((u32)val)<<24);
      default: API_BUG(E_BUG_INCONSISTENT_STATE); 
      }
      break;
    default: API_BUG(E_BUG_INCONSISTENT_STATE); 
    }
    reg = r;
    return *this;
  }

  const SFBHWHostRegisterAccess & operator&=(const u32 val) const {
    SFBHWHostRegister & reg = hostRegisters.getRegister(addr);
    u32 r = (u32) reg;
    switch (size) {
    case 4: 
      r &= val;
      break;
    case 2: 
      switch (phase) {
      case 0: r = r&(0xffff0000|((val&0xffff)<<0));
      case 2: r = r&(0x0000ffff|((val&0xffff)<<16));
      default: API_BUG(E_BUG_INCONSISTENT_STATE); 
      }
      break;
    case 1: 
      switch (phase) {
      case 0: r = r&(0xffffff00|((val&0xff)<<0));
      case 1: r = r&(0xffff00ff|((val&0xff)<<8));
      case 2: r = r&(0xff00ffff|((val&0xff)<<16));
      case 3: r = r&(0x00ffffff|((val&0xff)<<24));
      default: API_BUG(E_BUG_INCONSISTENT_STATE); 
      }
      break;
    default: API_BUG(E_BUG_INCONSISTENT_STATE); 
    }
    reg = r;
    return *this;
  }

  const SFBHWHostRegisterAccess & operator|=(const u32 val) const {
    SFBHWHostRegister & reg = hostRegisters.getRegister(addr);
    u32 r = (u32) reg;
    switch (size) {
    case 4: 
      r |= val;
      break;
    case 2: 
      switch (phase) {
      case 0: r = r|((val&0xffff)<<0);
      case 2: r = r|((val&0xffff)<<16);
      default: API_BUG(E_BUG_INCONSISTENT_STATE); 
      }
      break;
    case 1: 
      switch (phase) {
      case 0: r = r|((val&0xff)<<0);
      case 1: r = r|((val&0xff)<<8);
      case 2: r = r|((val&0xff)<<16);
      case 3: r = r|((val&0xff)<<24);
      default: API_BUG(E_BUG_INCONSISTENT_STATE); 
      }
      break;
    default: API_BUG(E_BUG_INCONSISTENT_STATE); 
    }
    reg = r;
    return *this;
  }
};

/** \cond */

#define SFB_PORT_IN_MAP(offset, port) hostRegisters.registerAt(((FIO_BASE_ADDR)+(offset))+(port)*0x20)

#define SFB_PORT_PINMODE_REGISTER(offset) hostRegisters.registerAt((PINMODE_BASE_ADDR)+(offset))

#define SFB_PINSEL_REGISTER(port,pinNumber) hostRegisters.registerAt((PINSEL_BASE_ADDR)+((port)*8)+(pinNumber>15?4:0))

#define REFLEX_RGB_RED_ON BUG_UNIMPLEMENTED()
#define REFLEX_RGB_RED_OFF BUG_UNIMPLEMENTED()
#define REFLEX_RGB_GREEN_ON BUG_UNIMPLEMENTED()
#define REFLEX_RGB_GREEN_OFF BUG_UNIMPLEMENTED()
#define REFLEX_RGB_BLUE_ON BUG_UNIMPLEMENTED()
#define REFLEX_RGB_BLUE_OFF BUG_UNIMPLEMENTED()

#define SFB_PORT_DIR_REGISTER(port)  SFB_PORT_IN_MAP(0x00,port)
#define SFB_PORT_MASK_REGISTER(port) SFB_PORT_IN_MAP(0x10,port)
#define SFB_PORT_PIN_REGISTER(port)  SFB_PORT_IN_MAP(0x14,port)
#define SFB_PORT_SET_REGISTER(port)  SFB_PORT_IN_MAP(0x18,port)
#define SFB_PORT_CLR_REGISTER(port)  SFB_PORT_IN_MAP(0x1C,port)

#define SFB_PORT_GET_BIT_MASK(port,mask) (SFB_PORT_PIN_REGISTER(port)&(mask))

#define SFB_PORT_SET_BIT_MASK(port,mask) (SFB_PORT_SET_REGISTER(port) = (mask))
#define SFB_PORT_CLR_BIT_MASK(port,mask) (SFB_PORT_CLR_REGISTER(port) = (mask))
#define SFB_PORT_WRITE_BIT_MASK(port,mask,value) \
  ((value==LOW)?SFB_PORT_CLR_BIT_MASK(port,mask):SFB_PORT_SET_BIT_MASK(port,mask))

extern void setPinSelFunction(u32 port,u32 pinNumber,u32 function);

extern void reenterBootloader() __attribute__ ((noreturn));

extern void reenterBrainstem() __attribute__ ((noreturn));

extern void hwpins_startup_initialization();

/* Low-level last ditch routine to set the rbg leds, bypassing (and
   thereby breaking) all the normal pin management stuff.  So don't
   use this unless the situation is dire enough that you don't case
   about that. */
extern void debugRGB(int rgbBits, int wait);

extern void debugRGBNum(int num, int bits, int loops);

extern void _dieOnBoard_(u32 blinkCode, const char * file, int lineno) __attribute__ ((noreturn));

/** \endcond */

/** 
The number of microseconds of uptime.  Rolls over after about 70 minutes. 
  \ingroup basica
*/
extern u32 micros();

/** The number of milliseconds of uptime.  Rolls over after about 49 days.  #millis() is much faster
    than #milliseconds(), but is not as accurate; #millis() may miss ticks due to interrupt load
  \ingroup basica
 */
extern u32 millis();

/** The number of microseconds of uptime.  Rolls over after about a half a million years.
    #microseconds() is quite fast, except for the overhead of dealing in \c u64 size, and its value
    is exact, unaffected by interrupt load.
  \ingroup basics
  */
extern u64 microseconds();

/** The number of milliseconds of uptime.  Rolls over after about 180 years. #milliseconds() is
    somewhat slow compared to #millis(), because it performs a non-power-of-2 division, but its
    value is exact, unaffected by interrupt load.
  \ingroup basics
 */
extern u32 milliseconds();

/** The number of seconds of uptime.  Rolls over after about 180 years. #seconds is somewhat slow
    (requires a division) but its value is exact, unaffected by interrupt load.
  \ingroup basics
 */
extern u32 seconds();

#ifdef __cplusplus
}
#endif

#endif  /* SFBHWPINSHOST_H */
