/*
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.Boot.c
 * @ingroup		kernel
 * @brief		system kernel boot sequence
 * @version		$Revision: 507 $
 * @since		1.1
 *
 * Handles the system boot sequence and the virtual interrupt table.
 * The default boot sequence which is run before main is described in
 * http://mspgcc.sourceforge.net/manual/x1127.html .
 *
 * $Id$
 */

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

#include <string.h>
#include "ScatterWeb.System.h"

/**
 * @brief	Define an interrupt vector table entry
 * @internal
 */
#define IVT_ISR(isr,a,func,address)	\
						void func();							\
						SECTION( ".int"#isr )						\
						void (* const func##_ptr)() = &func;	\
						ISR(func) {									\
							__asm__ __volatile__( "br &"#address :: );	\
						}

#define LED_ON			P5OUT &=~0x80
#define LED_OFF			P5OUT |= 0x80
#define LED_TOGGLE		P5OUT ^= 0x80

extern int main( void );
extern _RESET();
extern ISR(System_superloop);
extern void Process_init();
extern bool isWatchdogOn;

__inline void _System_clearPanic();

SECTION( ".wdstate" )
static uint16_t	WDSTATE;				///< Watchdog reset state

enum startup_reason	system_startupInfo;		///< reason for last reset

SECTION( ".noinit" )
uint16_t	system_reset_count;				///< Software reset counter

/// StartupInfo stringtable (corresponds to enum startupInfo)
const char startupInfo_stringtable[] =	"POR\0"			"SWRESET\0"	"REPROGRAM\0"
										"KERNEL\0"	"RADIO\0"
										"WD\0"	"KEYV\0"	"?\0" "\3";

/**
 * @name	virtual interrupt table setup
 *
 * Access interrupts vectors using GET_ISR and SET_ISR. Example: SET_ISR(ADC, my_adc_handler).
 *
 * \note Initialisation is done by CRT to _undefined_1_ which calls _unexpected_.
 * @{
 */
IVT_ISR( 00, DACDMA,	dacdma_isr, 0x1100 );	///< 0xFFE0 DAC12/DMA
IVT_ISR( 01, PORT2,		port2_isr,	0x1102 );	///< 0xFFE2 Port 2
IVT_ISR( 02, USART1TX,	u1tx_isr,	0x1104 );	///< 0xFFF4 USART 1 Transmit
IVT_ISR( 03, USART1RX,	u1rx_isr,	0x1106 );	///< 0xFFF6 USART 1 Receive
IVT_ISR( 04, PORT1,		port1_isr,	0x1108 );	///< 0xFFE8 Port 1
IVT_ISR( 05, TIMERA1,	timera1_isr,0x110A );	///< 0xFFEA Timer A CC1-2, TA
IVT_ISR( 06, TIMERA0,	timera0_isr,0x110C );	///< 0xFFEC Timer A CC0
IVT_ISR( 07, ADC,		adc_isr,	0x110E );	///< 0xFFEE ADC
IVT_ISR( 08, USART0TX,	u0tx_isr,	0x1110 );	///< 0xFFF0 USART 0 Transmit
IVT_ISR( 09, USART0RX,	u0rx_isr,	0x1112 );	///< 0xFFF2 USART 0 Receive, I2C tx/rx
IVT_ISR( 10, WDT,		wdt_isr,	0x1114 );	///< 0xFFF4 Watchdog Timer
IVT_ISR( 11, COMPARATORA, compa_isr,0x1116 );	///< 0xFFF6 Comparator A
IVT_ISR( 12, TIMERB1,	timerb1_isr,0x1118 );	///< 0xFFF8 Timer B CC1-2, TB
IVT_ISR( 13, TIMERB0,	timerb0_isr,0x111A );	///< 0xFFFA Timer B CC0
IVT_ISR( 14, NMI,		nmi_isr,	0x111C );	///< 0xFFFC Non-maskable
/** @} */

SECTION( ".reset" )
void (* const reset_isr_ptr)(void) = &_reset_vector__;	///< 0xFFFE Reset

/**
 * @brief Handler for undefined interrupts
 *
 * This is invoked by the CRT function _unexpected_1_ which is
 * the default interrupt handler routine.
 */
ISR(_unexpected_)
{
	uint16_t* sp = ( uint16_t* )READ_SP;
	uint16_t sr, pc;

	_DINT();
	// read SR and PC from stack
	sp--;
	sr = *sp; sp--;
	pc = *sp;			// PC is set to after the branch operation
	pc -= 7;			// set to start address of function

	/*
	To check which interrupt invoked this routine look in the map file generated by the linker.
	There you find the positions of the interrupt routines which are defined below. The PC value
	corresponds to one of these.
	*/

	printf( "\nUNDEF ISR @SR:%p PC:%p\n", (void*)sr, (void*)pc );
	System_printStack();
  _unexpected_reset:
	System_reset(WDS_KERNELPANIC);
	goto _unexpected_reset;
}

#if defined(__MSP430GCC__)
__attribute__(( naked, noreturn ) )
#elif defined(__CCE__)
#pragma FUNC_NEVER_RETURNS
#endif
void System_reset(const enum reset_reason reason)
{
	WDSTATE = reason;
	_System_clearPanic();
  reset:
	WDTCTL = 0xDEAD;
	goto reset;
}

#if defined(__MSP430GCC__)
__attribute__(( naked, noreturn ) )
#elif defined(__CCE__)
#pragma FUNC_NEVER_RETURNS
#endif
void System_panic(const enum reset_reason reason, char* msg) {

	WDSTATE = reason;

	if( msg != NULL && *msg != '\0' ) {
		// write as much as of msg as fits to infomem
		uint16_t size = strlen(msg) + 1;

		if( size > INFOMEM_PANIC_SIZE )
			size = INFOMEM_PANIC_SIZE;
		size--;
		Infomem_write(INFOMEM_PANIC, 2, msg, size, "", 1);
	}
	printf("\r\n#PANIC %u\r\n", reason);
	System_printStack();
  panic_reset:
	WDTCTL = 0xDEAD;
	goto panic_reset;
}

const char* System_getStartupInfoString() {
	const char* info;
	info = String_stringtableGet(startupInfo_stringtable, system_startupInfo);
	return info;
}

void System_printStack() {
	extern int __stack;

	System_memoryDump((void*)READ_SP, &__stack);
}

/**
 * @brief	Analyse system flags and notes to find the reason for the last reset
 *
 * Technical details:
 * <p>
 * The MSP internally has two reset signals (slau049.pdf, Chapter 3):
 * - POR (power-on reset): This is a device reset. Set by Power-Up or a low RST line
 *   when configured in reset mode (standard).
 * - PUC (power-up clear); A PUC follows every POR. But it can occur also without
 *   because of watdchdog timer expiry or flash control register key violation.
 * <p>
 * The SFRs are initialized on resets and some bits get only set or reset,
 * when a POR occured, not on a PUC only (these in braces, see slau049.pdf, page 3-4).
 * <p>
 * This feature is used with the register TACTL to detect a
 * POR since it will only get reset (complete zero) if a POR occured.
 * <p>
 * If a PUC only occured it was a warm start. The function looks further
 * wether it was the WDT, Flash key violation or an unknown source.
 * If it was a WDT it is checked with var WDSTATE
 * wether it was wanted by software or a real watchdog reset.
 *
 * @see reset_reason, System_reset
 */
__inline void _System_getStartupInfo()
{
	if ( TACTL == 0x0000 ) {
		// Power On Reset
		system_startupInfo = STARTUP_POR;
		system_reset_count = 0;
	} else {
		// Power Up Clear
		system_reset_count++;
		system_startupInfo = WDSTATE & 0x00FF;
		if ( FCTL3 & KEYV ) {
			// flash key violation
			FCTL3 = 0xA500;			// Clear KEYV Bit in FCTL3
			system_startupInfo = STARTUP_KEYV;
		} else if ( IFG1 & WDTIFG ) {
			// watchdog
			IFG1 &= ~WDTIFG;		// Clear WDTIFG Bit.
			if( system_startupInfo == 0 )
				system_startupInfo = STARTUP_WATCHDOG;
		} else {
			// PUC only but no watchdog?
			if( system_startupInfo == 0 )
				system_startupInfo = STARTUP_UNKNOWN;
		}
	}
	WDSTATE = WDS_RUNNING;
}

__inline void _System_dumpPanicMessage() {
	if( system_startupInfo > STARTUP_SWRESET ) {
		const char* panic = System_getStartupInfoString();
		char* msg = (char*)INFOMEM_START + INFOMEM_PANIC;
		printf("# %s panic: %s\n", panic, msg);
	}
}

__inline void _System_clearPanic() {
	uint8_t*	p = (uint8_t*)(INFOMEM_START + INFOMEM_PANIC);
	if( *p != 0 ) {
		Infomem_write(INFOMEM_PANIC, 1, "", 1);
	}
}

/**
 * @brief	Port initialisation
 *
 * Sets SELECTION, DIRECTION and OUT registers for P1 to P6, see code for details.
 */
__inline void _System_initPorts()
{
	P1SEL = 0x00;
	P1OUT = 0x00;
	P1DIR = 0x00;
	P2SEL = 0x00;
	P2OUT = 0x18;
	P2DIR = 0x1A;
	P3SEL = 0x00;
	P3OUT = 0x09;
	P3DIR = 0x21;
	P4SEL = 0x00;
	P4OUT = 0x00;
	P4DIR = 0x00;
	P5SEL = 0x0E;
	P5OUT = 0xF9;
	P5DIR = 0xFD;
	P6SEL = 0x07;
	P6OUT = 0x00;
	P6DIR = 0xC8;
}

void libscatterweb_Init() {
	// adc
	System_enableADC();

	// clock
	System_setDCO();	
	
	// init random seed
	rand_r(ADC12MEM + TAR);

	// node configuration
	Configuration_init();

	// time
	Timers_init();
	Time_init();

	// configure uart and activate rs232
	uart_init();
	Comm_init();
	uart_lock( UART_MODE_RS232 );
	uart_unlock( UART_MODE_RS232 );
	_DINT();

#if DEBUGLOG_LEVEL >= NONE
	// enable logging and output through rs232
	Debuglog_setHandler( Debuglog_serial );
#endif
}

/**
 * @brief	ScatterWeb boot sequence
 */
#if defined(__MSP430GCC__)
__attribute__(( noreturn ))
#endif
int main()
{
	_DINT();							// disable interrupts
	DMA0CTL &= ~( DMAIFG + DMAIE );		// disable DMA
	_System_getStartupInfo();
	WDTCTL = WDTPW | WDTHOLD;

	// set initial speed
	// ACLK is devided by 8 = 4096 Hz.
	// RSEL=6 no division for MCLK and SSMCLK. XT2 is off.
	BCSCTL1 = 0xB7;
	BCSCTL2 = 0x00;
	DCOCTL = 0xFF;

	_System_initPorts();
	LED_ON;
	CACTL1 = 0x0000;
	runModule = 0;
	mclock = 4505600;

	libscatterweb_Init();

	extern const fp_t	library_init[];
	int i;
	for( i = 0; i < library_count; i++ ) {
		library_init[i]();
		LOG_VERBOSE( "(%s) init", library_names[i] );
	}

	// adc
	System_enableADC();

	// enable interrupts
	_EINT();
	
	// network
	Net_init();
	
	// sensors
	Data_init( 0 );
	
	// system init done
	Configuration_print();
	_System_dumpPanicMessage();
	isWatchdogOn = true;

	// Activate Radio
	Net_on( NET_TXMODE_BACKOFF );

	// Command engine
	Cmd_on();

	// finished
	LED_OFF;
	_EINT();
	
	// application
	Process_init();

	// system service
	SET_ISR( COMPARATORA_VECTOR, System_superloop );
	_NOP(); _NOP();
	CACTL1 = CAIE;

	// switch to low power mode
sleepforever:
	LPM1;
	goto sleepforever;
}

/** @} */
