/*******************************************************************************
 * (c) Copyright 2010-2012 Actel Corporation.  All rights reserved.
 *
 *  SmartFusion MPM reference design sample application. 
 *
 *  SVN $Revision: 13755 $
 *  SVN $Date: 2010-07-15 14:43:58 +0100 (Thu, 15 Jul 2010) $
 *
 */

#include <stdio.h>
#include <string.h>

#include "mpm/mpm.h"
#include "oled/oled.h"
#include "drivers/mss_watchdog/mss_watchdog.h"
#include "drivers/mss_uart/mss_uart.h"
#include "drivers/mss_gpio/mss_gpio.h"
#include "tiny_printf/printf.h"

/*
 * Change this to assist with identifying the version of MPM used. Ensure
 * that the string is exactly 19 characters long padded with spaces if
 * required.
 */
#define MPM_VERSION_INFO "v4.0.112-20120529  "

/*
 * CLR_MSS_SR (Clear Microcontroller Subsystem Status Register)
 * brownout clearance bitmasks. See SmartFusion Microcontroller
 * Subsystem User's Guide
 */
#define CLR_MSS_SR_CLRBROWNOUT3_3VINT ((uint32_t)(0x00000008))
#define CLR_MSS_SR_CLRBROWNOUT1_5VINT ((uint32_t)(0x00000004))
#define CLR_MSS_SR_CLRBROWNOUTS ((uint32_t)(CLR_MSS_SR_CLRBROWNOUT3_3VINT | CLR_MSS_SR_CLRBROWNOUT1_5VINT))

/* 
 * A2F-DEV-KIT/A2F-EVAL-KIT OLED is 2 lines x 19 characters 
 */
#define OLED_MAX_LEN (19)

/* 
 * Watchdog load value for causing a reset after 2 seconds.
 */
#define WATCHDOG_LOAD_VALUE	(0x0BEBC200)

/*
 * Debounced read of pushbuttons - based on example code here:
 * http://www.embedded.com/columns/breakpoint/22100235
 */
#define SW1_POWER_ONOFF_MASK 		(0x10000000)
#define SW2_NEXT_OLED_PAGE_MASK 	(0x20000000)

static uint32_t read_pushbuttons()
{
	static uint16_t sw1_debounced_state = 0;
	static uint16_t sw2_debounced_state = 0;

	/* Read raw pushbutton states */
	uint32_t sw_state = MSS_GPIO_get_inputs();
	sw_state &= (SW1_POWER_ONOFF_MASK | SW2_NEXT_OLED_PAGE_MASK);

	/* Debounce processing */
	sw1_debounced_state =
		(sw1_debounced_state << 1) |
		((sw_state & SW1_POWER_ONOFF_MASK) ? 0 : 1) |
		0xe000;
	sw2_debounced_state =
		(sw2_debounced_state << 1) |
		((sw_state & SW2_NEXT_OLED_PAGE_MASK) ? 0 : 1) |
		0xe000;

	/* Return debounced state */
	sw_state =
		(((0xf000 == sw1_debounced_state) ? SW1_POWER_ONOFF_MASK : 0) |
		 ((0xf000 == sw2_debounced_state) ? SW2_NEXT_OLED_PAGE_MASK : 0));

	return sw_state;
}

/*
 * Format 16 bits for display on OLED
 * 1 bits displayed as 'o' and 0 bits displayed as '.'
 * bits[15:0] displayed left to right.
 * E.g. 0x1234 is displayed as "...o..o...oo.o.."
 */
static void format_16bits_for_oled(char *psz_buf, uint16_t bitfield)
{
	int8_t bitnum;

	*psz_buf = '\0';

	/* For bitfield[15] down to bitfield[0] */
	for (bitnum = 15; bitnum >= 0; bitnum--)
	{
		strcat(psz_buf, ((bitfield >> bitnum) & 1) ? "o" : ".");
	}

	/* Pad out to 19 characters for OLED */
	strcat(psz_buf, "   ");
}

/*
 * OLED "page" identifiers
 */

typedef enum oled_page
{
	oled_help,
	oled_version,
	oled_status,
	oled_channel,
	oled_outputs_1,
	oled_outputs_2,
	oled_inputs_1,
	oled_inputs_2,
	oled_regulator_enables_1,
	oled_regulator_enables_2,
	oled_num_pages
} oled_page_t;

static void display_oled_page(
	oled_page_t oled_page,
	mpm_state_t mpm_state,
	uint32_t mpm_inputs,
	uint32_t mpm_outputs,
	uint32_t mpm_regulator_enables,
	mpm_channel_number_t channel_number,
	mpm_channel_state_t channel_state,
	int32_t channel_voltage_mv)
{
	char sz_oled_line1[OLED_MAX_LEN + 1];
	char sz_oled_line2[OLED_MAX_LEN + 1];

	switch (oled_page)
	{
		case oled_help:
		{
			strcpy(sz_oled_line1, "SW1:power up/down  ");
			strcpy(sz_oled_line2, "SW2:next OLED page ");
		}
		break;

		case oled_version:
		{
			strcpy(sz_oled_line1, "Version Information");
			strcpy(sz_oled_line2, MPM_VERSION_INFO);
		}
		break;

		case oled_status:
		{
			strcpy(sz_oled_line1, "SmartFusion MPM    ");

			strcpy(sz_oled_line2, "Status: ");
			switch (mpm_state)
			{
			case mpm_is_starting:
				strcat(sz_oled_line2, "Starting   ");
				break;

			case mpm_is_started:
				strcat(sz_oled_line2, "Started    ");
				break;

			case mpm_is_stopping:
				strcat(sz_oled_line2, "Stopping   ");
				break;

			case mpm_is_stopped:
				strcat(sz_oled_line2, "Stopped    ");
				break;

			default:
				break;
			}
		}
		break;

		case oled_channel:
		{
			/* Channel number, polarity & voltage */
			tfp_sprintf(
				sz_oled_line1,
				"Channel %2d %5dmV",
				channel_number,
				channel_voltage_mv);

			/* Channel state */
			if (mpm_is_stopped == mpm_state)
			{
				/* MPM is stopped so threshold detection inactive -
				 * even though the channel threshold can still be read
				 */
				strcpy(sz_oled_line2, " -  -  -  -  -  -  ");
			}
			else
			{
				if (channel_voltage_mv >= 0)
				{
					/* Positive polarity channels have the following states
					 * 0V < OFF < UV1 < UV2 < NOM < OV1 < OV2
					 */
					tfp_sprintf(
						sz_oled_line2,
						"%s%s%s%s%s%s ",
						(mpm_channel_is_off == channel_state) ? "OFF" : " - ",
						(mpm_channel_is_uv2 == channel_state) ? "UV2" : " - ",
						(mpm_channel_is_uv1 == channel_state) ? "UV1" : " - ",
						(mpm_channel_is_nominal == channel_state) ? "NOM" : " - ",
						(mpm_channel_is_ov1 == channel_state) ? "OV1" : " - ",
						(mpm_channel_is_ov2 == channel_state) ? "OV2" : " - ");
				}
				else
				{
					/* Negative polarity channels have the following states
					 * 0V > OFF > OV2 > OV1 > NOM > UV1 > UV2
					 */
					tfp_sprintf(
						sz_oled_line2,
						"%s%s%s%s%s%s ",
						(mpm_channel_is_off == channel_state) ? "OFF" : " - ",
						(mpm_channel_is_ov2 == channel_state) ? "OV2" : " - ",
						(mpm_channel_is_ov1 == channel_state) ? "OV1" : " - ",
						(mpm_channel_is_nominal == channel_state) ? "NOM" : " - ",
						(mpm_channel_is_uv1 == channel_state) ? "UV1" : " - ",
						(mpm_channel_is_uv2 == channel_state) ? "UV2" : " - ");
				}
			}
		}
		break;

		case oled_outputs_1:
		case oled_outputs_2:
		{
			tfp_sprintf(
				sz_oled_line1,
				"Outputs%s     ",
				(oled_outputs_1 == oled_page) ? "[15:0] " : "[31:16]");

			format_16bits_for_oled(
				sz_oled_line2,
				(oled_outputs_1 == oled_page) ?
					(mpm_outputs & 0xFFFF) : ((mpm_outputs >> 16) & 0xFFFF));
		}
		break;

		case oled_inputs_1:
		case oled_inputs_2:
		{
			tfp_sprintf(
				sz_oled_line1,
				"Inputs%s      ",
				(oled_inputs_1 == oled_page) ? "[15:0] " : "[31:16]");

			format_16bits_for_oled(
				sz_oled_line2,
				(oled_inputs_1 == oled_page) ?
					(mpm_inputs & 0xFFFF) : ((mpm_inputs >> 16) & 0xFFFF));
		}
		break;

		case oled_regulator_enables_1:
		case oled_regulator_enables_2:
		{
			tfp_sprintf(
				sz_oled_line1,
				"Reg. Enables%s",
				(oled_regulator_enables_1 == oled_page) ?
					"[15:0] " : "[31:16]");

			format_16bits_for_oled(
				sz_oled_line2,
				(oled_regulator_enables_1 == oled_page) ?
					(mpm_regulator_enables & 0xFFFF) :
					((mpm_regulator_enables >> 16) & 0xFFFF));
		}
		break;

		default:
		break;
	}

	/* Display OLED "page" */
	OLED_set_cursor(FIRST_LINE, FIRST_CHARACTER);
	OLED_write_string(sz_oled_line1);
	OLED_set_cursor(SECOND_LINE, FIRST_CHARACTER);
	OLED_write_string(sz_oled_line2);
}

/*-------------------------------------------------------------------------*//**
 * main() function.
 */

int main()
{
	oled_page_t oled_page;
	mpm_channel_number_t current_channel_number;
	mpm_channel_state_t current_channel_state;
	int32_t current_channel_voltage_mv;
	uint32_t pushbuttons;
	uint32_t test_var;
	char print_buf[20];

	oled_page = 0;
	current_channel_number = MPM_INVALID_CHANNEL_NUMBER;
	current_channel_state = mpm_channel_is_off;
	current_channel_voltage_mv = 0;
	pushbuttons = 0;


	/*
	 * Enable CMSIS 1.5V brownout handler having first cleared any
	 * outstanding IRQ at source and then at the NVIC
	 */
	SYSREG->CLR_MSS_SR = CLR_MSS_SR_CLRBROWNOUTS;
	NVIC_ClearPendingIRQ(BrownOut_1_5V_IRQn);
	NVIC_EnableIRQ(BrownOut_1_5V_IRQn);

	/*
	 * Initialize 2 second watchdog
	 */
    MSS_WD_init(
    	WATCHDOG_LOAD_VALUE,
    	MSS_WDOG_NO_WINDOW,
    	MSS_WDOG_RESET_ON_TIMEOUT_MODE);

    MSS_UART_init
    	    (
    	        &g_mss_uart0,
    	        MSS_UART_57600_BAUD,
    	        MSS_UART_DATA_8_BITS | MSS_UART_NO_PARITY | MSS_UART_ONE_STOP_BIT
    	    );

	 /* Initialize OLED */

	OLED_init();

	/*
	 * Initialize MSS GPIO
	 */

	MSS_GPIO_init();

	/*
	 * Configure MSS GPIOs
	 * MSS_GPIO_28 : Pushbutton SW1 (MPM power up/down)
	 * MSS_GPIO_29 : Pushbutton SW2 (next OLED "page")
	 */
	MSS_GPIO_config(MSS_GPIO_28, MSS_GPIO_INPUT_MODE);
	MSS_GPIO_config(MSS_GPIO_29, MSS_GPIO_INPUT_MODE);

	/*
	 * Initialize MPM
	 */
	mpm_init();

	/*
	 * Infinite loop.
	 */
	for (;;)
	{
        MSS_WD_reload();
#if 0   // this part shifted to timer1 handler for every 500 milliseconds
#ifdef MPM_SHUTDOWN_IF_ANY_CHANNEL_CRITICAL

		/* Example code to illustrate how to shutdown MPM if of the first 5
		 * channels enters a critical under/over voltage state (i.e. OFF, UV2
		 * or OV2).
		 * If MPM shuts down immediately after power on sequencing has
		 * completed then ensure that all channels are entering NOMINAL
		 * state on power up and not overshooting to OV2 - e.g. by adjusting
		 * the APOL channel pot thumbwheels appropriately.
		 * this part shifted to timer1 handler for every 500 milliseconds
		 */
		#define MPM_NUM_CHANNELS_TO_CHECK 5

		if (mpm_is_started == mpm_get_state())
		{
			mpm_channel_number_t chan_num;

			for (chan_num = 1;
			     chan_num <= MPM_NUM_CHANNELS_TO_CHECK;
			     chan_num++)
			{
				if (mpm_is_valid_channel(chan_num))
				{
					mpm_channel_state_t chan_state;
					chan_state = mpm_get_channel_state(chan_num);

					if ((mpm_channel_is_off == chan_state) ||
						(mpm_channel_is_uv2 == chan_state) ||
						(mpm_channel_is_ov2 == chan_state))
					{
						mpm_stop();
						break;
					}
				}
			}
		}
#endif
#endif /* MPM_SHUTDOWN_IF_ANY_CHANNEL_CRITICAL*/

		/* Read pushbuttons */
		pushbuttons = read_pushbuttons();

		/* Is pushbutton for MPM start/stop pressed? */
		if (pushbuttons & SW1_POWER_ONOFF_MASK)
		{
			switch (mpm_get_state())
			{
			case mpm_is_stopped:
				/* Call mpm_init() again here in case MPM has been reconfigured
				 * via I2C.
				 */
				mpm_init();
				mpm_start();
				break;

			case mpm_is_started:
				mpm_stop();
				break;

			default:
				break;
			}
		}

		/* Is pushbutton for next OLED "page" pressed? */
		if (pushbuttons & SW2_NEXT_OLED_PAGE_MASK)
		{
			if (oled_page != oled_channel)
			{
				/* Next OLED "page" */
				oled_page = (oled_page + 1) % oled_num_pages;

				/*
				 * Display help & version pages once & thereafter skip
				 * directly to status page
				 */
				if (oled_help == oled_page)
				{
					oled_page += oled_status;
				}
			}

			if (oled_channel == oled_page)
			{
				/* Current OLED "page" is for channel details so
				 * try to move to next valid channel
				 */
				do
				{
					current_channel_number++;
				} while (!mpm_is_valid_channel(current_channel_number) &&
						(current_channel_number <= MPM_MAX_NUMBER_OF_CHANNELS));

				/* Have we reached end of channel list? */
				if (current_channel_number > MPM_MAX_NUMBER_OF_CHANNELS)
				{
					/* Yes - on to next OLED "page" */
					current_channel_number = MPM_INVALID_CHANNEL_NUMBER;
					oled_page = (oled_page + 1) % oled_num_pages;
				}
			}
		}

		/* Get channel voltage and state if possible */
		if (mpm_is_valid_channel(current_channel_number))
		{
			current_channel_state =
				mpm_get_channel_state(current_channel_number);
			current_channel_voltage_mv =
				mpm_get_channel_voltage_mv(current_channel_number);
		}

		/* Display OLED "page" */
		display_oled_page(
			oled_page,
			mpm_get_state(),
			mpm_get_digital_inputs(),
			mpm_get_digital_outputs(),
			mpm_get_regulator_enable_outputs(),
			current_channel_number,
			current_channel_state,
			current_channel_voltage_mv);
	}

	return 0;
}
