/*******************************************************************************
 * (c) Copyright 2010 Microsemi Corporation.  All rights reserved.
 *
 * File:main.c
 * File history:
 *      Revision: 1.0 Date: January 7th, 2010
 *
 *"SmartFusion: System Power Optimization using low power modes" application example code.
 *
 * Description:
 *
 * SmartFusion Power modes demonstration using RTC. Press any key to set time or for system power management.
 * Please note that you must execute this sample project from eNVM with JTAG debug probes disconnected.
 * This application does not put memory controllers in reset mode.
 * The VJTAG jumper and VPUMP jumper must be removed on SmartFusion Development Kit (A2F500-DEV-KIT) for actual
 * power consumption measurement.
 *
 * Author: Corporate Applications Engineering
 *
 */

/*******************************************************************************
 Including Directories
********************************************************************************/

#include "drivers/mss_rtc/mss_rtc.h"
#include "drivers/mss_uart/mss_uart.h"
#include "drivers/mss_watchdog/mss_watchdog.h"
#include "application_files/control_functions.h"
#include "enable_disable.h"
#include <stdio.h>

/*------------------------------------------------------------------------------
  System registers bit masks.
 */
#define VRPSM_CR_MSSVRON_MASK   0x01u
#define CLR_MSS_RTC_MATCH_EVENT 0x01u
#define temp 0x00002000

/*------------------------------------------------------------------------------
  Command line interface defines.
 */
#define INVALID_USER_INPUT  -1
#define ENTER   0x0D

/*------------------------------------------------------------------------------
  Local variables.
 */
void display_greeting( void );
void config_smartfusion( void );
void display_time( uint32_t seconds_count );
void top_level_menu( void );
void set_time_menu( void );
void standbymode_menu( void );
void timekeeping_mode(void);

int32_t get_number_from_user( void );

/*------------------------------------------------------------------------------
  Main function.
 */
int main()
{
    uint32_t seconds_count;
    uint32_t previous_count = 0xFFFFFFFFuL;
    size_t rx_size;
    uint8_t rx_buff[1];
    
    /* Disabling Watchdog Timer */
    MSS_WD_disable();
    
    MSS_RTC_init();
    
    /* Initialize and configure UART0. */
    MSS_UART_init
        (
            &g_mss_uart0,
            MSS_UART_57600_BAUD,
            MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT
        );
    
    /* Configuring RTC */
    MSS_RTC_configure( MSS_RTC_NO_COUNTER_RESET | MSS_RTC_ENABLE_VOLTAGE_REGULATOR_ON_MATCH );
    
    MSS_RTC_start();
    
    /* Display greeting message. */
    display_greeting();
    
    /* Firmware Control of SmartFusion functional blocks */
    config_smartfusion();

    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\rThe Device is in SOC-MODE\n\r" );

    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Press any key to set the time or to enter low power modes .\n\r" );

    /* Display time over UART. */
    for(;;)
    {
    	/* Update displayed time if value read from RTC changed since last read. */
        seconds_count = MSS_RTC_get_seconds_count();
        
        if ( previous_count != seconds_count )
        {
            display_time( seconds_count );
        }
        previous_count = seconds_count;
        
        /* Show menu if any key is pressed. */
        rx_size = MSS_UART_get_rx ( &g_mss_uart0, rx_buff, sizeof(rx_buff) );
        if ( rx_size > 0 )
        {
            top_level_menu();
        }
    }
    return 0;
}

/*------------------------------------------------------------------------------
  Display greeting message when application is started.
 */
void display_greeting( void )
{
    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\r\n\r****************************************************************************\n\r" );
    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"****** SmartFusion : Power Optimization using low power modes Example ******\n\r" );
    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"****************************************************************************\n\r" );
    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"This Application sets the RTC time & reads while switching between power modes.\n\r" );
    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Measure Current at JP2 (A2F500-DEV-KIT) for 3.3 V rail.\n\r" );
    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Measure Current at JP6 (A2F500-DEV-KIT) for 1.5 V rail.\n\r" );
}

/*------------------------------------------------------------------------------
  SmartFusion Functional Blocks Enabling/Disabling
 */
void config_smartfusion(void)
{
	uint8_t input_selection;
	uint8_t Analog_Pwr_Selection, scb_Reset, ADC0_Pwr_Selection, ADC1_Pwr_Selection, ADC2_Pwr_Selection;

	MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Do you want to Overwrite MSS Configuration?\n\r" );
	MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Enter(y) to Overwrite MSS settings:		\n\r" );

	/* This allows individual peripheral turn on/off */
	input_selection = WaitForOneKeyEntered();
	if (input_selection == 'y')
	{
		 enable_disable_peripherals(1);
	}

	/* Managing the analog portion of SmartFusion based ACE condition */

	/* Checking ACE status */
	if (((SYSREG->SOFT_RST_CR) & 0x00002000) != 0)
	{
		MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Analog block is powered off because of ACE reset\n\r" );
	}
	else
	{
		/* Entire AnalogBlock PowerDown*/
		MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Would you like to PowerDown Entire Analog Block?\n\r" );
		MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"AB:Enter '1' to PowerDown, '0' to PowerUp*:     \n\r" );
		Analog_Pwr_Selection	= WaitForOneKeyEntered();
		AnalogPowerDown(Analog_Pwr_Selection);

		/* Enabling/Disabling Analog blocks individually */

		if (Analog_Pwr_Selection == '0')
		{
			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Would you like to PowerDown ADC0?\n\r" );
			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"ADC0:Enter '1' to PowerDown, '0' to PowerUp:    \n\r" );
			ADC0_Pwr_Selection	= WaitForOneKeyEntered();

			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Would you like to PowerDown ADC1?\n\r" );
			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"ADC1:Enter '1' to PowerDown, '0' to PowerUp:    \n\r" );
			ADC1_Pwr_Selection	= WaitForOneKeyEntered();

			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Would you like to PowerDown ADC2?\n\r" );
			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"ADC2:Enter '1' to PowerDown, '0' to PowerUp:    \n\r" );
			ADC2_Pwr_Selection	= WaitForOneKeyEntered();

			/* ADCs Power control */
			ADC_control(ADC0_Pwr_Selection, ADC1_Pwr_Selection, ADC2_Pwr_Selection);

			/* Signal Conditioning Block Power control */
			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Would you like to PowerDown SCB?\n\r" );
			MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"SCB:Enter '1' to PowerDown, '0' to PowerUp:    \n\r" );
			scb_Reset 	= WaitForOneKeyEntered();
			SCBReset(scb_Reset);
		}

	}

	/* Enabling/Disabling Internal Voltage regulator */
	MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Would you like to PowerDown Internal Voltage regulator?\n\r" );
	MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Enter (Y) to PowerDown VR or else press (N)    \n\r" );
	MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"Internal Voltage Regulator can be turned on by pressing PU_N/PUB Switch \n\r" );

	input_selection = WaitForOneKeyEntered();
	if (input_selection == 'y')
	{
		/* RTC_MATCH signal must be low in order to turn off the VR */
	    /* Clear RTC match event */
	    SYSREG->CLR_MSS_SR |= CLR_MSS_RTC_MATCH_EVENT;

	    /* Toggle the MSSVRON bit to power off. */
	    SYSREG->VRPSM_CR &= ~VRPSM_CR_MSSVRON_MASK;
	    SYSREG->VRPSM_CR |=  VRPSM_CR_MSSVRON_MASK;
	    SYSREG->VRPSM_CR &= ~VRPSM_CR_MSSVRON_MASK;

	}
}

/*------------------------------------------------------------------------------
  Display time.
 */
void display_time( uint32_t seconds_count )
{
    uint32_t seconds;
    uint32_t minutes;
    uint32_t hours;
    uint8_t display_buffer[32];
    
    seconds = seconds_count % 60u;
    minutes = (seconds_count % 3600u) / 60u;
    hours = (seconds_count % 86400u) / 3600u;
    
    snprintf( (char *)display_buffer, sizeof(display_buffer), "\r    %d:%02d:%02d ", (int)hours, (int)minutes, (int)seconds );
    MSS_UART_polled_tx_string( &g_mss_uart0, display_buffer );
}

/*------------------------------------------------------------------------------
  Top level menu.
 */
void top_level_menu( void )
{
    size_t rx_size;
    uint8_t rx_key;
    
    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\r\n\rPress '1' to set clock time."
    		"\n\rPress '2' to Configure SmartFusion blocks."
    		"\n\rPress '3' to Enter Standby Mode Menu."
    		"\n\rPress '4' to Enter Time Keeping Mode.\n\r" );

    do {
        rx_size = MSS_UART_get_rx ( &g_mss_uart0, &rx_key, sizeof(rx_key) );
        if ( rx_size > 0 )
        {
            switch( rx_key )
            {
				case '1':
					set_time_menu();
					break;

				case '2':
					config_smartfusion();
                    break;

				case '3':
                    standbymode_menu();
                    break;

				case '4':
                    timekeeping_mode();
                    break;
                    
                default:
                    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\r\n\rCommand not recognized.\n\r" );
                    break;
            }
        }
    } while ( 0 == rx_size );
}

/*------------------------------------------------------------------------------
  Menu for setting the time.
 */
void set_time_menu( void )
{
    int32_t hours;
    int32_t minutes;
    int32_t seconds;
    
    MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\rChange time.\n\r Hours: " );
    hours = get_number_from_user();
    if ( ( INVALID_USER_INPUT != hours ) && ( hours< 24 ) )
    {
        MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r Minutes: " );
        minutes = get_number_from_user();
        if ( ( INVALID_USER_INPUT != minutes ) && ( minutes < 60 ) )
        {
            MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r Seconds: " );
            seconds = get_number_from_user();
            if ( ( INVALID_USER_INPUT != seconds ) && ( seconds < 60 ) )
            {
                uint32_t rtc_seconds;
                
                rtc_seconds = seconds + (minutes * 60) + (hours * 3600);
                MSS_RTC_set_seconds_count( rtc_seconds );
            }
        }
    }
    MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\r" );
}

/*------------------------------------------------------------------------------
  Function to switch the MSS clock source to low power 32 KHz oscillator.
 */
void SwitchMSSClockto32KHz (void)
{

	/* Need to enable 32KHz crystal oscillator if not enabled already*/
#if 0
	uint32_t i;

	/* Enable 32 KHz crystal oscillator by setting XTAL_EN bit of CTRL_STAT_REG */
	(*((uint32_t volatile *)(0x40014160))) = (*((uint32_t volatile *)(0x40014160))) | 0x00000001;

	/* Delay */
	for (i=0; i<=10000; i++);
#endif

	/* Set 32 KHz Oscillator as CLKC source by setting CLKC control bits in MSS_CCC_MUX_CR */
	/* Set RXCSEL */
	(*((uint32_t volatile *)(0xE0042050))) = (*((uint32_t volatile *)(0xE0042050))) | 0x00020000;
	/* Set DYNCSEL */
	(*((uint32_t volatile *)(0xE0042050))) = (*((uint32_t volatile *)(0xE0042050))) | 0x00040000;

	/* Bypass CLKC output divider by setting BYPASSC bit in MSS_CCC_MUX_CR */
	(*((uint32_t volatile *)(0xE0042050))) = (*((uint32_t volatile *)(0xE0042050))) | 0x00400000;

	/* Configure Glitch-less MUX to select GLC as MSS clock */
	/* Clear GLMUXCFG */
	(*((uint32_t volatile *)(0xE0042050))) = (*((uint32_t volatile *)(0xE0042050))) & 0xF3FFFFFF;
	/* Set GLMUXSEL */
	(*((uint32_t volatile *)(0xE0042050))) = (*((uint32_t volatile *)(0xE0042050))) | 0x01000000;

	/* Power-down the PLL by clearing PLLEN bit in MSS_CCC_PLL_CR */
	(*((uint32_t volatile *)(0xE0042054))) = (*((uint32_t volatile *)( 0xE0042054))) & 0x7FFFFFFF;
}

/*------------------------------------------------------------------------------
  Function to switch the MSS clock source back to GLA.
 */
void SwitchMSSClocktoGLA (void)
{
	uint32_t i;

	/* Enable the PLL by clearing PLLEN bit in MSS_CCC_PLL_CR */
	(*((uint32_t volatile *)(0xE0042054))) = (*((uint32_t volatile *)( 0xE0042054))) | 0x80000000;

	/* Delay */
	for (i=0; i<=10000; i++);

	/* Configure Glitch-less MUX to select GLA as MSS clock */
	/* Clear GLMUXCFG */
	(*((uint32_t volatile *)(0xE0042050))) = (*((uint32_t volatile *)(0xE0042050))) & 0xF3FFFFFF;
	/* Clear GLMUXSEL */
	(*((uint32_t volatile *)(0xE0042050))) = (*((uint32_t volatile *)(0xE0042050))) & 0xFCFFFFFF;

	/* Disable the 32KHz crystal oscillator if not required. In this application RTC requires 32KHz oscillator so it is not disabled. */
#if 0
	/* Disable 32 KHz crystal oscillator by clearing XTAL_EN bit of CTRL_STAT_REG */
	(*((uint32_t volatile *)(0x40014160))) = (*((uint32_t volatile *)(0x40014160))) & 0xFFFFFFFE;
#endif

}

/*------------------------------------------------------------------------------
  Standby mode.
  Peripherals configuration in standby mode
 */
void standbymode_menu( void )
{
	uint8_t input_selection;
	int32_t seconds;
    uint64_t match;

    /* Get number of seconds to sleep for from user. */
    MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\renter number of seconds to sleep for: " );
    seconds = get_number_from_user();
    
    if ( (INVALID_USER_INPUT != seconds) && (0 != seconds) )
    {
        /* Standby mode */
        MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\rStarting Standby mode.\n\r" );

        /* Standby Mode peripherals Configuration */
        /* Putting MSS_UART_0 in reset mode if not required in standby mode */
        MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\rWould you like to put MSS_UART_0 in reset mode during Standby Mode?" );
        MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\rPress 'Y' to put into reset else 'N'" );
    	input_selection = WaitForOneKeyEntered();
    	if (input_selection == 'y')
    	{
    		peripheral_control(0, UART_0_SR_ENABLE, UART_0_SR_DISABLE);
    	}

    	/* setting RTC MATCH Value and enabling the interrupt */
        MSS_RTC_disable_irq();
    	match = MSS_RTC_get_rtc_count();
        match = match + ((uint64_t)seconds << 8);
        MSS_RTC_set_rtc_match( match );
        MSS_RTC_enable_irq();

        /* Clear RTC match event */
        SYSREG->CLR_MSS_SR |= CLR_MSS_RTC_MATCH_EVENT;

        /* Switch the system frequency to 32KHz */
        SwitchMSSClockto32KHz();

        /*CM3 is put into sleep using(WFI) Waiting for Interrupt*/
		/*This must be last instruction*/
		PutCM3ToSleep();

		/* Switch back to original system frequency set by the user*/
		SwitchMSSClocktoGLA();

		/* Instruction to the user after exiting standby mode */
		MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\r\n\rExiting Standby mode\n\r" );
    }
    else
    {
        MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\r\n\rInvalid value.\n\r" );
    }
}

/*------------------------------------------------------------------------------
  Time Keeping mode
 */
void timekeeping_mode(void)
{
    size_t rx_size;
    uint8_t rx_key;

	MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\rIs the Lithium ion battery connected to VBAT rail? " );
	MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\rPress 'Y' if battery is connected else press 'N'" );

	do {
	        rx_size = MSS_UART_get_rx ( &g_mss_uart0, &rx_key, sizeof(rx_key) );
	        if ( rx_size > 0 )
	        {
	            switch( rx_key )
	            {
					case 'y':
					case 'Y':
						MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\rSwitch off the main supply using switch SW6" );
						MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\rRTC in this mode will keep running while the battery is still charged" );
						MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\rObserve the updated time after restarting the supply" );
						while (1)
						{

						}
						break;

					case 'n':
					case 'N':
						MSS_UART_polled_tx_string( &g_mss_uart0, (const uint8_t *)"\n\r\n\rFor Entering into this mode backup battery must be connected on board" );
	                    break;

					default:
	                    MSS_UART_polled_tx_string( &g_mss_uart0,(const uint8_t*)"\n\r\n\rCommand not recognized.\n\r" );
	                    break;
	            }
	        }
	    } while ( 0 == rx_size );
}

/*------------------------------------------------------------------------------
  Retrieve a number typed by the user.
 */
int32_t get_number_from_user( void )
{
    int32_t user_input = 0;
    uint8_t rx_buff[1];
    uint8_t complete = 0;
    size_t rx_size;
    
    while ( !complete )
    {
        rx_size = MSS_UART_get_rx ( &g_mss_uart0, rx_buff, sizeof(rx_buff) );
        if ( rx_size > 0 )
        {
            MSS_UART_polled_tx( &g_mss_uart0, rx_buff, sizeof(rx_buff) );
            if ( ENTER == rx_buff[0] )
            {
                complete = 1;
            }
            else if ( (rx_buff[0] >= '0') && (rx_buff[0] <= '9') )
            {
                user_input = (user_input * 10) + (rx_buff[0] - '0');
            }
            else
            {
                user_input = INVALID_USER_INPUT;
                complete = 1;
            }
        }
    }
    return user_input;
}

