/** @brief Implements watchdog functionality
 *
 * The watchdog is initialised and setup to reset the processor if it is
 * not serviced within the timeout.
 *
 * It is up to the main processor to add a mechanism to service the watchdog.
 *
 * An interrupt is set to be raised a fixed time before the watchdog expires.
 *
 * @copyright 2020 Remsdaq Ltd.
 *
 * @file c4_watchdog.c
 *
 * @date: 23 Jul 2020
 * @author: Dan Smithers <dwhs@remsdaq.com>
 */





/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "c4_watchdog_types.h"

#include "c4_watchdog.h"
#include "bl_api.h"
#include "fsl_wdog.h"
#include <stdbool.h>

/*******************************************************************************
 * Defines
 ******************************************************************************/

/** @brief Maximum watchdog timeout in deciseconds (ds). */
static const uint16_t WDOG_MAX_TO = 1280U;

/** @brief Minimum watchdog timeout in deciseconds (ds). */
static const uint16_t WDOG_MIN_TO = 5U;

/** @brief Maximum watchdog interrupt delay in deciseconds (ds). */
static const uint16_t WDOG_MAX_INT = 1275U;

/** @brief Minimum timeout in deciseconds (ds). */
static const uint16_t WDOG_MIN_INT = 0U;

/** @brief Macro to print specified bit as boolean.
 *
 * The macro expands a bit-field variable that is overlaid on a register and prints on the
 * output.
 *
 * @param base Register to print. This is a bit-field on a register.
 * @param reg  Name of bit-field entry to print.
 */
#define PRINT_BIT(base, reg) \
	printf("\t "#base"."#reg" : %s\n", (base.B.reg ? "TRUE" : "false"))

/*******************************************************************************
 * Structures
 ******************************************************************************/

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

/** @brief Watchdog interrupt handler function.
 *
 * This over-rides the WEAK function defined in startup_mimxrt1062.h
 *
 * The interrupt also needs to be enabled before it is active using
 * \code
 * NVIC_EnableIRQ(WDOG1_IRQn);
 * \endcode
 *
 */
void WDOG1_IRQHandler(void);

/*******************************************************************************
 * Variables
 ******************************************************************************/

/** @brief Watchdog configuration structures. */
wdog_config_t sWdogConfig[C4_NUM_WDOG];

/** @brief Watchdog control registers.
 *
 * This is equivalent to the WDOG_Type representation. */
c4_WDOG_reg_t * const sWdogRegisters[C4_NUM_WDOG] = \
		{(c4_WDOG_reg_t *)WDOG1_BASE,
	     (c4_WDOG_reg_t *)WDOG2_BASE};

/** @brief Flag to prevent reinitialisation of watchdogs. */
static bool WDOG_INITIALISED = false;

/*******************************************************************************
 * Public Functions
 ******************************************************************************/

/** Timeout for Watchdog is specified in deci-seconds in half-second increments.
 *
 * The register timeout range is 0x00 - 0xFF which is 0.5 - 128 s  or 50 - 1280 ds.
 *
 * The watchdog timeout can be changed at any point and will take effect the next
 * time the watchdog is reset.
 *
 * Interrupt time ahead of expiry for watchdog is in deci-seconds in half-second increments.
 *
 * The register interrupt ante-delay range is 0x00 - 0xFF which is 0 - 127.5 s  or 0 - 1275 ds.
 *
 * @note The interrupt value can be set once once only and cannot be changed until the next
 * system reset.
 */
exit_code_t c4_initWatchdog(c4_wdog_t wdog, uint16_t to, uint16_t ito)
{
	exit_code_t rc = C4_ERR_OK;
    if (false == WDOG_INITIALISED)
    {
        bl_api_init();
        WDOG_INITIALISED = true;
    }

	if ((C4_WDOG1 > wdog) || (C4_WDOG2 < wdog))
	{
		rc = C4_ERR_RANGE;
	}
	else
	{
		/* The following sequence should be performed for WDOG initialization.
		 * • PDE bit of Watchdog Miscellaneous Control (WMCR) should be cleared to disable
		 * the power down counter.
		 * • WT field of Watchdog Control (WCR) should be programmed for sufficient timeout
		 * value.
		 * • WDOG should be enabled by setting WDE bit of Watchdog Control (WCR) so that
		 * the timeout counter loads the WT field value of Watchdog Control (WCR) and starts
		 * counting.
		 */
		WDOG_GetDefaultConfig(sWdogConfig+wdog);

		/* Timeout value is specified in the register in half-seconds and 0 represents a half-second
		 * timeout.
		 *
		 * The timeout is specified in deci-seconds, so divide by five and subtract one to
		 * convert to register setting.
		 *
		 * e.g. if the timeout is set to 0xF, the watchdog will expire after (0xF+1)/2 = 8 sec. */
		sWdogConfig[wdog].timeoutValue       = to/5U - 1U;

		// Set interrupt ante-delay if it is less than the timeout value.
		if (ito <= to)
		{
			c4_setWdogInterrupt(wdog, ito);
		}

		/* Cast from our bitfield representation of WDOG registers to shift and mask used in FreeRTOS functions. */
		WDOG_Init((WDOG_Type *)sWdogRegisters[wdog], sWdogConfig+wdog);

	}
    return rc;
}

/*******************************************************************************/

exit_code_t c4_disableWdog(c4_wdog_t wdog)
{
	exit_code_t rc = C4_ERR_OK;

	if ((C4_WDOG1 > wdog) || (C4_WDOG2 < wdog))
	{
		rc = C4_ERR_RANGE;
	}
	else
	{
		sWdogRegisters[wdog]->WCR.B.WDE = false;
	}
	return rc;
}

/*******************************************************************************/

exit_code_t c4_enableWdog(c4_wdog_t wdog)
{
	exit_code_t rc = C4_ERR_OK;

	if ((C4_WDOG1 > wdog) || (C4_WDOG2 < wdog))
	{
		rc = C4_ERR_RANGE;
	}
	else
	{
		sWdogRegisters[wdog]->WCR.B.WDE = true;
	}
	return rc;
}

/*******************************************************************************/

exit_code_t c4_setWdogTimeout(c4_wdog_t wdog, uint16_t to)
{
	exit_code_t rc = C4_ERR_OK;

	if ((C4_WDOG1 > wdog) || (C4_WDOG2 < wdog) || (to > WDOG_MAX_TO) || (to < WDOG_MIN_TO))
	{
		rc = C4_ERR_RANGE;
	}
	else
	{
		sWdogRegisters[wdog]->WCR.B.WT = (to/5 - 1);
	}
	return rc;
}

/*******************************************************************************/

/** It appears that this can only be set before the watchdog is enabled, and that the interrupt
 * time and enable bit must be set together.
 */
exit_code_t c4_setWdogInterrupt(c4_wdog_t wdog, uint16_t to)
{
	exit_code_t rc = C4_ERR_OK;

	if ((C4_WDOG1 > wdog) || (C4_WDOG2 < wdog) || (to > WDOG_MAX_INT) || (to < WDOG_MIN_INT))
		rc = C4_ERR_RANGE;
	// After reset WIE is 0 (interrupt disabled) and WICT is 4 (2 seconds)
	else if ((sWdogRegisters[wdog]->WICR.B.WIE) ||
			 (4 != sWdogRegisters[wdog]->WICR.B.WICT))
		rc = C4_ERR_ALREADY_SET;
	else if (sWdogRegisters[wdog]->WCR.B.WDE)
		rc = C4_ERR_ALREADY_SET;
	else
	{
		switch (wdog)
		{
		case C4_WDOG1:
			NVIC_EnableIRQ(WDOG1_IRQn);
			break;
		case C4_WDOG2:
			NVIC_EnableIRQ(WDOG2_IRQn);
			break;
		default:
			rc = C4_ERR_RANGE;
		}
		if (C4_ERR_OK == rc)
		{
			// Set up a local copy of the register.
			union c4_WDOG_wicr wicr;
			wicr.R = sWdogRegisters[wdog]->WICR.R;

			wicr.B.WIE = true;
			/* Set interrupt to occur before the interrupt expires. Specify in half-seconds.
			 * e.g. if interrupt is set to 0x4, the interrupt will occur (0x4)/2 = 2 sec before WDOG timeout. */
			wicr.B.WICT = to/5U;

			// now copy it into the real register
			sWdogRegisters[wdog]->WICR.R = wicr.R;
		}
	}
	return rc;
}

/*******************************************************************************/

exit_code_t c4_getResetHistory()
{
	/* inspect the SCR.SRSR and the WDOG1.WRSR registers and reset them.
	 */
	if (false == WDOG_INITIALISED)
	{
		printf("Watchdog uninitialised!!\n");
	}
	else
	{
		printf("Watchdog1 : WRSRS 0x%08x\n", sWdogRegisters[C4_WDOG1]->WRSR.R);
		PRINT_BIT(sWdogRegisters[C4_WDOG1]->WRSR, SFTW);
		PRINT_BIT(sWdogRegisters[C4_WDOG1]->WRSR, TOUT);
		PRINT_BIT(sWdogRegisters[C4_WDOG1]->WRSR, POR);

		printf("Watchdog2 : WRSRS 0x%08x\n", sWdogRegisters[C4_WDOG2]->WRSR.R);
		PRINT_BIT(sWdogRegisters[C4_WDOG2]->WRSR, SFTW);
		PRINT_BIT(sWdogRegisters[C4_WDOG2]->WRSR, TOUT);
		PRINT_BIT(sWdogRegisters[C4_WDOG2]->WRSR, POR);

		c4_SRC_reg_t *SRC_reg = (c4_SRC_reg_t *)SRC_BASE;
		printf("SCR : SRSR 0x%08lx\n", SRC_reg->SRSR.R);
		PRINT_BIT(SRC_reg->SRSR, ippResetB);
		PRINT_BIT(SRC_reg->SRSR, lockupSysResetReq);
		PRINT_BIT(SRC_reg->SRSR, csuResetB);
		PRINT_BIT(SRC_reg->SRSR, ippUserResetB);
		PRINT_BIT(SRC_reg->SRSR, wdogRstB);
		PRINT_BIT(SRC_reg->SRSR, jtagRstB);
		PRINT_BIT(SRC_reg->SRSR, jtagSwRst);
		PRINT_BIT(SRC_reg->SRSR, wdog3RstB);
		PRINT_BIT(SRC_reg->SRSR, tempSenseRstB);
		// Clear all bits.
		SRC_reg->SRSR.R = SRC_reg->SRSR.R;
		printf("SCR : SRSR 0x%08lx\n", SRC_reg->SRSR.R);

	}
	return C4_ERR_OK;
}

/*******************************************************************************
 * Internal functions
 ******************************************************************************/

void WDOG1_IRQHandler(void)
{
	printf("%s", __FUNCTION__);
	/*
	 * WDOG_WCR_WDE_MASK   0x0004U Watchdog Enable
     * WDOG_WRSR_POR_MASK  0x0010U Power-On Reset
     * WDOG_WRSR_TOUT_MASK 0x0002U Time-OUT reset
     * WDOG_WRSR_SFTW_MASK 0x0001U SoftWare Reset
     * WDOG_WICR_WTIS_MASK 0x4000U Interrupt has occurred
	 */
    /* Cast from our bitfield representation of WDOG registers to shift and mask used in FreeRTOS functions. */
	uint16_t status = WDOG_GetStatusFlags((WDOG_Type *)sWdogRegisters[C4_WDOG1]);
	printf(" statusflags 0x%04x\n", status);

    /* Cast from our bitfield representation of WDOG registers to shift and mask used in FreeRTOS functions. */
	WDOG_ClearInterruptStatus((WDOG_Type *)sWdogRegisters[C4_WDOG1], kWDOG_InterruptFlag);
	/* User code. User can do urgent case before timeout reset.
	 * IE. user can backup the ram data or ram log to flash.
	 * the period is set by config.interruptTimeValue, user need to
	 * check the period between interrupt and timeout.
	 */
}
