/*
  SFBWatchdog.cpp - Board-specific watchdog reset management routines
  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 "SFBWatchdog.h"
#include "LPC23xx.h"		/* LPC23xx/24xx Peripheral Registers */
#include "irq.h"		/* For suspendInterrupts & restoreInterrupts */
#include "timer.h"		/* For micros() */

#define WDEN    0x01
#define WDRESET 0x02
#define WDTOF   0x04

bool watchdogActive() {
  return WDMOD&WDEN;
}

bool watchdogFeed() {
  if (!watchdogActive())        // If no watchdog..
    return false;               // ..nothing to feed

  ////
  // Doggie diet management
  //
  // According to some LPC2000 forum posts, you're not supposed to
  // feed the watchdog too frequently.  One poster claims a 35usec
  // minimum was mentioned by NxP engineers:
  // http://www.embeddedrelated.com/groups/lpc2000/show/25764.php
  //
  // So, we enforce that.  We'll say 50usec min.

  static u32 lastFeedMicros IN_BODYRAM_BSS = 0;
  u32 nextFeedMicros = lastFeedMicros+50; 
  do {
    lastFeedMicros = micros();  // Remember that he ate.
  } while (IS_EARLIER(lastFeedMicros,nextFeedMicros));

  u32 status;
  suspendInterrupts(status);    // Nice and quiet now..
  WDFEED = 0xAA;		//    Good doggy.
  WDFEED = 0x55;                //    Nice doggy.
  restoreInterrupts(status);    // All right then.

  return true;                  // Now scram.
}

void watchdogSetInterval(u32 feedIntervalMs) {
  WDTC = feedIntervalMs*1000;   // Update timer count
  for (uv32 i = 0; i < 10; ++i) { } // Block to ensure WDCLK domain sees new WDTC
  watchdogFeed();               // Pick up the new timing
}

u32 watchdogGetInterval() {
  return WDTC/1000;
}

bool watchdogStart(u32 feedIntervalMs) {
  if (watchdogActive())         // Dog is already hunting..
    return false;               // ..so you can't mess with it now
  
  WDCLKSEL = 0;                 // Use the internal RC oscillator.  It runs at
                                // 4MHz (nominal), with a fixed /4 prescaler, so
  WDTC = feedIntervalMs*1000;   // 1ms (nominal)==1000 counts

  WDMOD = WDEN | WDRESET;       // Enable and specify resetting if not fed!

  watchdogFeed();               // Feed the dog once to wake him up

  return true;
}

bool watchdogGetTimeoutFlag() {
  return WDMOD&WDTOF;
}

void watchdogClearTimeoutFlag() {
  u8 val = WDMOD;
  val &= ~WDTOF;
  WDMOD = val;
}

#define SUICIDE_CODE 999 // watchdogStart/SetInterval can't produce values < 1000

bool watchdogItWasSuicide() {
  return WDTC==SUICIDE_CODE;
}

void watchdogSuicide() {

  if (!watchdogActive())        // If not already going
    watchdogStart(10);          // start up

  WDTC = SUICIDE_CODE;          // Leave a note for posterity
  for (uv32 i = 0; i < 10; ++i) { } // Block to ensure WDCLK domain sees new WDTC
  watchdogFeed();               // Pick up the new timing

 noreturn: goto noreturn;       // Wait for the end
}

