/*
Copyright 2007, Freie Universitaet Berlin. All rights reserved.

These sources were developed at the Freie Universität Berlin, Computer
Systems and Telematics group.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

- Neither the name of Freie Universitaet Berlin (FUB) nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

This software is provided by FUB and the contributors on an "as is"
basis, without any representations or warranties of any kind, express
or implied including, but not limited to, representations or
warranties of non-infringement, merchantability or fitness for a
particular purpose. In no event shall FUB or contributors be liable
for any direct, indirect, incidental, special, exemplary, or
consequential damages (including, but not limited to, procurement of
substitute goods or services; loss of use, data, or profits; or
business interruption) however caused and on any theory of liability,
whether in contract, strict liability, or tort (including negligence
or otherwise) arising in any way out of the use of this software, even
if advised of the possibility of such damage.

This implementation was developed by the CST group at the FUB.

For documentation and questions please use the web site
http://scatterweb.mi.fu-berlin.de and the mailinglist
scatterweb@lists.spline.inf.fu-berlin.de (subscription via the Website).
Berlin, 2007
*/

/**
 * @file		ScatterWeb.System.c
	 * @ingroup		kernel
 * @brief		system kernel
 * @version		$Revision: 518 $
 *
 * $Id: ScatterWeb.System.c 518 2008-02-06 15:13:39Z hutta@PCPOOL.MI.FU-BERLIN.DE $
 */

/**
 * @addtogroup		kernel
 * @{
 */

#include "ScatterWeb.System.h"

#define DELTA 1100                   // Target DCO = DELTA(1100)*(4096Hz) , DELTA=550 => 4505600 Hz  */
#define UP 1                         // Temporary.
#define DOWN 2                       // Temporary.
#define DCOLOW  (1)                  // Symbolic name, DCO was last measure too low.
#define DCOHIGH (2)                  // Symbolic name, DCO was last measure too high.
#define DCODIFF (3)                  // Difference of dcoDiff to DELTA when DCO measure is taken as too high/low.

/**
 * @name Watchdog and startup control
 * @{
 */

/**
 * @brief Start the watchdog
 */
inline void startWatchdog() { WDTCTL = WDT_ARST_1000; }

/**
 * @brief Stop the watchdog
 */
inline void stopWatchdog()  { WDTCTL = WDTPW + WDTHOLD; }

bool isWatchdogOn;
/** @} */
uint32_t mclock;
volatile uint16_t dcoCap;        ///< Contains always last TBCCR6 Value (Capture on ACLK)
volatile uint16_t dcoDiff;       ///< Everytime dcoDiff==0 TIMER_B1 interrupt writes a new DCO diff measure into it.
volatile uint8_t  checkDCOState; ///< DCO will only be changed if two times low or high in a row to avoid peaks. Implemented with this variable.

volatile enum module_flags runModule;

fp_vp callbacks[NUMBER_CALLBACKS];

// DCO Checker. Interrupt occurs every ~250us. Continously updates ::dcoDiff.
ISR( Timer_B1 )
{
	dcoDiff = TBCCR6 - dcoCap;
	dcoCap = TBCCR6;
	TBCCTL6 &= ~( CCIFG );
}

// Subroutine to check and readjust the dco.
//
// This routine will run whenevery the Timer_B1 interrupt has left a new value in dcoDiff.
// The algorithm checks if the measured frequency is more than DCODIFF steps off the
// desired DELTA frequency. If that is the case two times in row the algorithm
// lowers/highers the dco frequency. Set bit DEBUG_DCO in Configuration.debugFlags to see
// readjustments of the dco.
//
// If stable, diff will always oscillate between two numbers, for example 1097 and 1098.
// At theses frequency (~4,5 Mhz) one step of DCO means 4 measured diffDCO steps,
// for example one DCO step up with the previous example will produce 1101/1102.
// To avoid constant changing of DCO frequency and proviode some hysteresis DCODIFF
// should be at least 3 (19200 rf and 115200 baud serial comm get only lost at diffs greater 50).
void _System_checkDCO()
{
	if ( dcoDiff < ( DELTA - DCODIFF ) ) {      // frequency too low
		if ( checkDCOState != DCOLOW ) checkDCOState = DCOLOW;
		else {
#if DEBUGLOG_LEVEL >= 5
			LOG_HARDWARE( DEBUG_DCO, "DCO++", NULL );
#endif
			DCOCTL++;
			if ( DCOCTL == 0x00 ) {
				if (( BCSCTL1 & 0x07 ) == 0x07 ) {
#if DEBUGLOG_LEVEL >= 5
					LOG_HARDWARE( DEBUG_DCO, "Freq too high!", NULL );
#endif
					WDTCTL = 0xDEAD;
				}
				BCSCTL1++;      // Did DCO role over? // -> Select next higher RSEL
			}
#if DEBUGLOG_LEVEL >= 5
			LOG_HARDWARE( DEBUG_DCO, "DCO %b %b >%i", DCOCTL, BCSCTL1, dcoDiff );
#endif
		}
	} else {
		if ( dcoDiff > ( DELTA + DCODIFF ) ) {      // frequency too high
			if ( checkDCOState != DCOHIGH ) {
				checkDCOState = DCOHIGH;
			} else {
#if DEBUGLOG_LEVEL >= 5
				LOG_HARDWARE( DEBUG_DCO, "DCO--", NULL );
#endif
				DCOCTL--;
				if ( DCOCTL == 0xFF ) {
					if ( !(( BCSCTL1 & 0x07 ) == 0x00 ) ) {
#if DEBUGLOG_LEVEL >= 5
						LOG_HARDWARE( DEBUG_DCO, "Freq too low!", NULL );
#endif
						WDTCTL = 0xDEAD;
					}
					BCSCTL1--;      // Did DCO role under? -> Select next lower RSEL
				}
#if DEBUGLOG_LEVEL >= 5
				LOG_HARDWARE( DEBUG_DCO, "DCO %b %b >%i\r\n", DCOCTL, BCSCTL1, dcoDiff );
#endif
			}
		} else checkDCOState = 0; // freqency was ok
	}
	Timers_add( 100, _System_checkDCO, NULL );
}

// If the A/D-Converter is enabled via _System_enableADC, the values of the analog
// inputs are continously converted
void _System_initADC()
{
	//Setup ADC12, ref., sampling time, REFON for DAC!!
	ADC12CTL0 = SHT0_15 + SHT1_15 + MSC;
	ADC12CTL1 = SHP + CONSEQ_3 + CSTARTADD_0;   // Use sampling timer, repeat-sequence-of-channels
	ADC12MCTL0 = ( INCH_0 + SREF_0 );           // P60: AIN0
	ADC12MCTL1 = ( INCH_0 + SREF_0 );           // P60: AIN0
	ADC12MCTL2 = ( INCH_0 + SREF_0 );           // P60: AIN0
	ADC12MCTL3 = ( INCH_1 + SREF_0 );           // P61: AIN1
	ADC12MCTL4 = ( INCH_1 + SREF_0 );           // P61: AIN1
	ADC12MCTL5 = ( INCH_1 + SREF_0 );           // P61: AIN1
	ADC12MCTL6 = ( INCH_2 + SREF_0 );           // P62: AIN2
	ADC12MCTL7 = ( INCH_2 + SREF_0 );           // P62: AIN2
	ADC12MCTL8 = ( INCH_2 + SREF_0 );           // P62: AIN2
	ADC12MCTL9 = ( INCH_2 + SREF_0 );           // P62: AIN2
	ADC12MCTL10 = ( INCH_3 + SREF_0 );          // P63: AIN2
	ADC12MCTL11 = ( INCH_4 + SREF_0 );          // P64: AIN3
	ADC12MCTL12 = ( INCH_5 + SREF_0 );          // P65: AIN4
	ADC12MCTL13 = ( INCH_10 + SREF_0 ) + EOS;   // Temp diode
}

// Enables and starts ADC.
// Enables the A/D-Converter, uses ~1 mA.
bool System_enableADC()
{
	_System_initADC();
	ADC12CTL0 |= ADC12ON;
	System_wait( 600 );   // Delay for reference start-up
	ADC12CTL0 |= ENC;     // enable conversion
	ADC12CTL0 |= ADC12SC; // Start conversion
	return True;
}

int System_random() {
	return rand_r(ADC12MEM + TAR);
}

// Stops and disables ADC.
// Disables the A/D-Converter.
bool System_disableADC()
{
	ADC12CTL0 &= ~ENC;       // Stop conversion
	System_wait( 600 );      // Wait, TODO exchange with test for ADC12BUSY &test
	ADC12CTL0 &= ~( ADC12ON ); // turn off ADC12
	return True;
}

fp_t System_registerCallback( enum callback_types type, fp_t fp )
{
	fp_t oldfp = callbacks[type];
	callbacks[type] = fp;

	return oldfp;
}

bool System_delay( unsigned int ms )
{
	uint16_t tar = TAR;
	ms <<= 2;     // Wait with ACLK, take ms * 4 to get roughly ms (1024 hz)
	while ( TAR - tar < ms ) _NOP();
	return True;
}

bool System_wait( uint32_t w )
{
	volatile uint32_t i;
	for ( i = 0;i < w;i++ ) nop();
	return True;
}

/*
 *   brief CRC generating lookup table.
 *   This mysterious table is just the CRC of each possible byte.  It can be
 *   computed using the standard bit-at-a-time methods.  The polynomial can
 *   be seen in entry 128, 0x8408.  This corresponds to x^0 + x^5 + x^12.
 *   Add the implicit x^16, and you have the standard CRC-CCITT.
 */
static uint16_t const ccitt_crc16_table[256] = { 0x0000, 0x1189, 0x2312, 0x329b, 0x4624,
		0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5,
		0xe97e, 0xf8f7, 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7,
		0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
		0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a,
		0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, 0x3183, 0x200a,
		0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9,
		0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, 0x4204, 0x538d, 0x6116, 0x709f,
		0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868,
		0x99e1, 0xab7a, 0xbaf3, 0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528,
		0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb,
		0xaa72, 0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
		0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, 0x7387,
		0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46,
		0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, 0x8408, 0x9581, 0xa71a,
		0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb,
		0x4e64, 0x5fed, 0x6d76, 0x7cff, 0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad,
		0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c,
		0x7df7, 0x6c7e, 0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c,
		0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
		0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3,
		0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, 0xc60c, 0xd785,
		0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956,
		0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, 0xd68d, 0xc704, 0xf59f, 0xe416,
		0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1,
		0x0d68, 0x3ff3, 0x2e7a, 0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3,
		0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70,
		0x1ff9, 0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
		0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 };

uint16_t System_ccittFcs(uint16_t fcs, uint8_t c)
{
	return (((fcs) >> 8) ^ ccitt_crc16_table[((fcs) ^ (c)) & 0xff]);
}

bool System_setDCO()
{
	uint16_t Compare, Oldcapture = 0, direction = 0;
	
	//for ( i = 0xFFFF; i > 0; i-- );   // Delay for XTAL to settle
	
	CCTL2 = CCIS0 + CM0 + CAP;        // Define CCR2, CAP, ACLK
	TACTL = TASSEL1 + TACLR + MC1;    // SMCLK, continous mode
	while ( 1 ) {
		CCTL2 &= ~( CCIFG + COV );      // clear IFG and possible overflow
		// 1. capture
		while ( !( CCTL2 & CCIFG ) );           // Wait until capture occured!
		CCTL2 &= ~CCIFG;                        // Capture occured, clear flag
		Oldcapture = CCR2;                      // Save current captured SMCLK
		// 2. - 5. capture, "software divider" so aclk stays
		// untouched and 2400 bps are working!
		while ( !( CCTL2 & CCIFG ) );         // Wait until capture occured!
		CCTL2 &= ~CCIFG;                      // Capture occured, clear flag
		Compare = CCR2;
		Compare = Compare - Oldcapture;         // SMCLK difference
		if ( DELTA == Compare ) break;          // if equal, leave "while(1)"
		else {
			if ( DELTA < Compare ) {           // DCO is too fast, slow it down
				if ( direction == UP ) break;    // stop if we were going up (jumped over exact delta)
				if ( !direction ) direction = DOWN; // if starting set direction
				DCOCTL--;
				if ( DCOCTL == 0xFF ) {
					if ( !(( BCSCTL1 & 0x07 ) == 0x00 ) ) WDTCTL = 0xDEAD;
					BCSCTL1--;            // Did DCO role under? -> Select next lower RSEL
				}
			} else {
				if ( direction == DOWN ) break;   // stop if we were going down (jumped over exact delta)
				if ( !direction ) direction = UP; // if starting set direction
				DCOCTL++;
				if ( DCOCTL == 0x00 ) {
					if (( BCSCTL1 & 0x07 ) == 0x07 ) WDTCTL = 0xDEAD;
					BCSCTL1++;            // Did DCO role over? // -> Select next higher RSEL
				}
			}
		}
	}
	CCTL2 = 0;      // Stop CCR2 function
	TACTL = 0;      // Stop Timer_A
	dcoDiff = 0;
	checkDCOState = 0;
	return True;
}

void System_memoryDump(void* from, void* to) {
	uint8_t *pointer = (uint8_t*)from;
	uint16_t lbytes = (uint16_t)pointer % 16;

	if( lbytes )
		pointer -= lbytes;

	for( ; pointer <= (uint8_t*)to; pointer++) {
		if( ((uint16_t)pointer % 16) == 0)
			printf("\r\n%p: ", pointer);
		else if( ((uint16_t)pointer %  4) == 0)
			putchar(' ');

		if( pointer >= (uint8_t*)from )
			printf("%.2X", *pointer);
		else
			printf("  ");
	}
	printf("\r\n");
}

bool System_startWatchdog()
{
	isWatchdogOn = True;
	startWatchdog();
	return True;
}

bool System_stopWatchdog()
{
	isWatchdogOn = False;
	stopWatchdog();
	return True;
}

//  endless loop, calling callbacks and doing internal stuff.
ISR(System_superloop)
{
	extern void Net_rxHandler();
	extern void Net_txHandler();

loopstart:
	CACTL1 &= ~CAIE;
	eint();
	if ( isWatchdogOn ) startWatchdog();
	if ( runModule & MF_TIMER ) Timers_eventHandler();
	if ( runModule & MF_RADIO_RX ) Net_rxHandler();
	if ( runModule & MF_RADIO_TX ) Net_txHandler();
	if ( runModule & MF_SERIAL ) {
		extern volatile uint8_t* serial_line;
		if ( serial_line != 0 ) {
			if ( callbacks[C_SERIAL] ) callbacks[C_SERIAL](( void* )serial_line );
		}
	}
	if ( runModule & MF_SENSOR ) {
		if ( callbacks[C_SENSOR] ) callbacks[C_SENSOR](( void* ) &Data_sensorEventValue );
		runModule &= ~MF_SENSOR;
	}
	dint(); _NOP();
	if ( runModule == 0 ) {
		stopWatchdog();
		CACTL1 = CAIE; // clears also ifg
		return;
	} else {
		goto loopstart;
	}
}

__attribute__(( section( ".eos_header" ) ) )
APPHEADER(
    firmwareHeader,
    ( APPH_OS | APPH_VERSION ),
    __PLATFORM,
    __PLATFORM_REV,
    "MSBEOS", __EOSVER_MAJOR, __EOSVER_MINOR,
    NULL
);
/** @} */
