/*
 *  Copyright (c) 1996-2007 Digi International Inc., All Rights Reserved
 *
 *  This software contains proprietary and confidential information of Digi
 *  International Inc.  By accepting transfer of this copy, Recipient agrees
 *  to retain this software in confidence, to prevent disclosure to others,
 *  and to make no use of this software other than that for which it was
 *  delivered.  This is an unpublished copyrighted work of Digi International
 *  Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
 *  prohibited.
 *
 *  Restricted Rights Legend
 *
 *  Use, duplication, or disclosure by the Government is subject to
 *  restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
 *  Technical Data and Computer Software clause at DFARS 252.227-7031 or
 *  subparagraphs (c)(1) and (2) of the Commercial Computer Software -
 *  Restricted Rights at 48 CFR 52.227-19, as applicable.
 *
 *  Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
 *
 * =======================================================================
 *  The routines that control the internal watchdog chip are implemented 
 *  here.  The watchdog is idle after power up or a reset.  Is not  
 *  enabled until after we call watchdogServicePeriodically().  
 *  Once enabled, ThreadX will hit a timer to refresh the watchdog at a 
 *  rate based on the user defined WATCHDOG_RECOMMENDED_SERVICE_RATE.  
 *  The watchdog itself will expire (and thus reset) after no resfreshes
 *  occur for BSP_WATCHDOG_TIMEOUT_IN_SECONDS seconds (defined in bsp.h).
 *
 *
 * Edit Date/Ver   Edit Description
 * ==============  =======================================================
 *
 *
 */

#include <sysClock.h>
#include <reg_def.h>
#include <mcreg_def.h>
#include <tx_api.h>
#include <watchdog.h>
#include "bsp_sys.h"
#include <NA_ISR.h>

/*
 * Dummy value for parameter passed to watchdog service function.
 */
#define NOT_USED                                0


/*
 * This constant reflects the recommended service rate for the watchdog, 
 * in terms of the WATCHDOG_TIMEOUT_IN_SECONDS.  This value represents 
 * the expected number of watchdog clocks that should occur during an 
 * average watchdog timeout interval.  For example, with a 
 * WATCHDOG_TIMEOUT_IN_SECONDS of 1.6 and a WATCHDOG_RECOMMENDED_SERVICE_RATE
 * of 10, the watchdog should be incremented every 0.16 sec.  If the 
 * WATCHDOG_RECOMMENDED_SERVICE_RATE was 4, the watchdog would be 
 * clocked every 0.4 sec.
 *
 * Digi International recommends a value of 5.
 */
#define WATCHDOG_RECOMMENDED_SERVICE_RATE       5


/*
 * The watchdog front end has a divider that effects the CPU Clock
 * by a factor of 2 to 64.  Using 64 will provide the highest possible
 * watchdog interval.
 */
#define WATCHDOG_FRONT_END_DIVIDE_DOWN_BY_2     1
#define WATCHDOG_FRONT_END_DIVIDE_DOWN_BY_4     2
#define WATCHDOG_FRONT_END_DIVIDE_DOWN_BY_8     3
#define WATCHDOG_FRONT_END_DIVIDE_DOWN_BY_16    4
#define WATCHDOG_FRONT_END_DIVIDE_DOWN_BY_32    5
#define WATCHDOG_FRONT_END_DIVIDE_DOWN_BY_64    6
#define WATCHDOG_FRONT_END_DIVIDE_DOWN          WATCHDOG_FRONT_END_DIVIDE_DOWN_BY_64


/*ad*
 * The Watchdog API services the NET+ARM watchdog.  Call watchdogServicePeriodically()
 * to activate the H/W watchdog and begin servicing it.  Once the H/W watchdog timer
 * has been activated, it must be periodically serviced by software.  If software
 * stops servicing it, the watchdog will reset the chip.  The
 * watchdogServicePeriodically() function sets up a timer task to service the
 * watchdog.  This task will service it as long as the ThreadX kernel is 
 * operational, and, therefore, cause the system to reset if the kernel ever
 * stops running.
 * 
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
 * @since 6.0
 * @include 		"watchdog.h"
 * @platform 		all
 * @library 		bsp
 * @overview Watchdog Driver
 *
*ad*/

/*ad*
 * This function returns the length of the watchdog timeout in milliseconds 
 * set by the
 * @link watchdogServiceOnce and
 * @link service_watchdog functions.
 *
 * This function is useful on NS7520 platforms where the timeout is NOT set by
 * BSP_WATCHDOG_TIMEOUT_IN_SECONDS.  On the NS7520, the timeout value is
 * determined by the frequency of FXTALE.  Because of this, the hardware design 
 * can have a significant effect on the watchdog timeout.  
 * Some typical values are 4.35 seconds on hardware that uses the internal PLL 
 * with a 3.6864 MHz crystal, or 1.525 seconds on hardware that uses a
 * 55 MHz external oscillator.
 * 
 *
 * @note    Use the 
 *          @link BSP_WATCHDOG_TIMEOUT_IN_SECONDS to set the watchdog timeout 
 *          on ARM9 based platforms.
 *
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
 * @since 7.4
 * @include 		"watchdog.h"
 * @platform 		all
 * @library 		bsp
 *
 * @return timeout      watchdog timeout value set by the watchdogServiceOnce
 *                      and service_watchdog functions.
 *
*ad*/
unsigned watchdogTimeoutInMilliseconds(void)
{
    /*
     * This function was added to be consistent with the same function
     * on NS7520 platforms.  However, since you can use 
     * BSP_WATCHDOG_TIMEOUT_IN_SECONDS to set the timeout on ARM9 based
     * platforms, this function really isn't useful for ARM9 platforms.
     */
    #define MILLISECONDS_IN_A_SECOND        (1000)
    
    return MILLISECONDS_IN_A_SECOND * BSP_WATCHDOG_TIMEOUT_IN_SECONDS;
}


/*
 * When setup, the watchdog will always be enabled and setup to 
 * reset the unit if triggered.
 *
 */
#define WATCHDOG_INITIAL_CONFIGURATION          (BIT_4 + BIT_7)

/*
 * ThreadX Timer structure used to create periodic service function.
 */
static TX_TIMER watchdogTimer;



/*
 * This flag is set if the watdog timer service function has been
 * registered with ThreadX.
 */
static int serviceFunctionRegistered = FALSE;


/*
 * This is the value written to the watchdog counter each time it is 
 * serviced.
 */
static unsigned int timeoutSize = 0xFFFFFF;
static unsigned int watchdogIsrInstalled = FALSE;


/*ad*
 * This routine is called to retirgger the watchdog.  The watchdog timer
 * mechanism must already be set up via a call to watchdogServicePeriodically().  
 * This function is called periodically by a timer setup when calling that
 * function to refresh the watchdog to it's original watchdog value.
 * 
 * @since 6.0
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
*ad*/
void watchdogServiceOnce(void)
{
    *NA_SCM_SWTR_ADDRESS = timeoutSize;
}



/*ad*
 *
 * This function is registered with the ThreadX timer service to be 
 * called periodically.  It calls watchdogServiceOnce() to service
 * the watchdog.  This function exists because ThreadX timer
 * functions are passed a parameter.  It makes no sense to make
 * watchdogServiceOnce() accept this parameter, so this function
 * is provided as a shell around watchdogServiceOnce().
 *
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
 * @since 6.0
 * @include 		"watchdog.h"
 * @platform 		all
 * @library 		bsp
 *
 * @param	notUsed     parameter from Thread timer service which we don't
 *                      need or want
 *
*ad*/
static void service_watchdog(ULONG notUsed)
{
    notUsed++;                      /* suppress compiler warning*/

    watchdogServiceOnce();
}



static int naWatchdogIsr(void *context)
{
    (void) context;

    /* clear interrupt */
    narm_write_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, clearInterrupt, 1);
    narm_write_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, clearInterrupt, 0);

    watchdogServiceOnce();

    return 0;
}

/*ad*
 * This routine registers timer event with the ThreadX kernel.
 * The event will service the watchdog as long as the kernel
 * is still functioning.  The watchdog will reset the processor
 * if it stops being serviced (if the kernel locks up).
 *
 * The service interval will be 50 milliseconds.
 *
 * If the timer event is successfully registered, then this function
 * will call watchdogServiceOnce() to wake up the watchdog chip.
 *
 * @since 6.0
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
 *
 * @return WATCHDOG_SUCCESS                       timer event is successfully created.
 * @return WATCHDOG_ALREADY_SERVICING_WATCHDOG	  timer event is already created.
 * @return WATCHDOG_ERROR_CREATING_TIMER_TASK     error creating timer event.
 *
 * @see @link watchdog_api_errors
*ad*/

WATCHDOG_CCODE watchdogServicePeriodically(void)
{
    WATCHDOG_CCODE result = WATCHDOG_SUCCESS;

    if (serviceFunctionRegistered)          /* if we already did this*/
    {
        result = WATCHDOG_ALREADY_SERVICING_WATCHDOG;
    }
    else
    {
        static unsigned int serviceRate;

        /* clear any pending interrupts, need to call twice */
        naWatchdogIsr(NULL);
        naWatchdogIsr(NULL);

        /*  Make this value approximately 1.6 seconds to match prior 
         *  NET+OS functionality.
         */
#ifdef __SEAMLESS__
        timeoutSize = (NAgetCpuClkFreq () >> WATCHDOG_FRONT_END_DIVIDE_DOWN) >> 6;
        serviceRate = 1;
#else
#if (PROCESSOR == ns9215) || (PROCESSOR == ns9210)
        timeoutSize = BSP_WATCHDOG_TIMEOUT_IN_SECONDS * ((2*NAgetAhbClkFreq()) >> WATCHDOG_FRONT_END_DIVIDE_DOWN);
#else
        timeoutSize = BSP_WATCHDOG_TIMEOUT_IN_SECONDS * (NAgetCpuClkFreq () >> WATCHDOG_FRONT_END_DIVIDE_DOWN);
#endif
        serviceRate = BSP_WATCHDOG_TIMEOUT_IN_SECONDS * BSP_TICKS_PER_SECOND / WATCHDOG_RECOMMENDED_SERVICE_RATE;
#endif

        if (tx_timer_create (&watchdogTimer, "watchdog", service_watchdog, 
            NOT_USED, serviceRate, serviceRate, TX_AUTO_ACTIVATE) == TX_SUCCESS)
        {                                   /* if successfully started timer, set flag*/
            serviceFunctionRegistered = TRUE;

            /*  setup the watchdog count and trigger to reset when active   */
            watchdogServiceOnce();
            *NA_SCM_SWCR_ADDRESS      = WATCHDOG_INITIAL_CONFIGURATION + (WATCHDOG_FRONT_END_DIVIDE_DOWN - 1);
        }
        else                                /* if couldn't start timer*/
        {                                   /* set ccode to indicate result, don't wake up chip*/
            result = WATCHDOG_ERROR_CREATING_TIMER_TASK;
        }
    }

    return result;
}


/*ad*
 * This routine sets the watchdog timer to generate interrupt after timeout 
 * instead of reset. Function also installs watchdog ISR.
 *
 * If the watchdog is enabled in reset mode then user need to call this API 
 * before entering the sleep mode. Otherwise unit stops serviceing the watchdog
 * and result forceful reset when watchdog expires. 
 *
 * @since 7.3
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
 *
 * @return WATCHDOG_SUCCESS                       Watchdog mode is changed to generate interrupt 
 *                                                after timeout
 * @return WATCHDOG_ERROR_SETTING_INTERRUPT	      Error installing ISR
 *
 * @see @link watchdog_api_errors
*ad*/
WATCHDOG_CCODE naWatchdogSetInterruptMode(void)
{
    WATCHDOG_CCODE result = WATCHDOG_SUCCESS;
    
    /* check if watchdog is enabled */
    if (narm_read_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, enable) == SCM_SWCR_DISABLE_WATCHDOG)
    {
        /* nothing to do */
        goto done_set_mode;        
    }

    if (watchdogIsrInstalled == FALSE)
    {
        if (naIsrInstall(WATCHDOG_INTERRUPT, naWatchdogIsr, NULL) != SUCCESS)
        {
            result = WATCHDOG_ERROR_SETTING_INTERRUPT;
            goto done_set_mode;
        }

        if (naInterruptEnable(WATCHDOG_INTERRUPT) != SUCCESS)
        {
            result = WATCHDOG_ERROR_SETTING_INTERRUPT;
            goto done_set_mode;
        }

        watchdogIsrInstalled = TRUE;
    }

    if (narm_read_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, response) == SCM_SWCR_GENERATE_RESET)
    {
        /* update watchdog conf register to generate interrupt */
        narm_write_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, response, SCM_SWCR_GENERATE_INTERRUPT);
    }
    
    done_set_mode:
        return result;
}

/*ad*
 * This routine sets the watchdog timer back to reset mode. Function also 
 * uninstalls watchdog ISR. Normally, user doesn't require to call this 
 * function during sleep wakeup because function which installs the watchdog 
 * should set the correct watchdog mode.
 *
 * @since 7.3
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
 *
 * @return WATCHDOG_SUCCESS                       Watchdog mode is changed to generate interrupt 
 *                                                after timeout
 * @return WATCHDOG_ERROR_SETTING_INTERRUPT	      Error uninstalling ISR
 *
 * @see @link watchdog_api_errors
*ad*/
WATCHDOG_CCODE naWatchdogClearInterruptMode(void)
{
    WATCHDOG_CCODE result = WATCHDOG_SUCCESS;
    
    /* check if watchdog is enabled */
    if (narm_read_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, enable) == SCM_SWCR_DISABLE_WATCHDOG)
    {
        /* nothing to do */
        goto done_clear_mode;        
    }

    if (narm_read_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, response) == SCM_SWCR_GENERATE_INTERRUPT)
    {
        /* update watchdog conf register to generate reset */
        narm_write_reg(NA_SCM_SWCR_REG, NA_SCM_SWCR_ADDRESS, response, SCM_SWCR_GENERATE_RESET);
    }

    
    if (watchdogIsrInstalled == TRUE)
    {
        if (naInterruptDisable(WATCHDOG_INTERRUPT) != SUCCESS)
        {
            result = WATCHDOG_ERROR_SETTING_INTERRUPT;
            goto done_clear_mode;
        }

        if (naIsrUninstall(WATCHDOG_INTERRUPT) != SUCCESS)
        {
            result = WATCHDOG_ERROR_SETTING_INTERRUPT;
            goto done_clear_mode;
        }

        watchdogIsrInstalled = FALSE;
    }

    done_clear_mode:
        return result;
}











/*ad*
 * This routine cancels the timer task registered by the 
 * watchdogServicePeriodically().  This has the side effect
 * of causing the system to reset in about 1.6 seconds. 
 *
 * @since 6.0
 * @external
 * @category BSP:Device_Drivers:WatchdogDriver
 *
 * @return WATCHDOG_SUCCESS               timer event is canceled.
 * @return WATCHDOG_WAS_NOT_SERVICING	  timer event was not created.
 *
 * @see @link watchdog_api_errors
*ad*/

WATCHDOG_CCODE watchdogStopServicing(void)
{
    WATCHDOG_CCODE result = WATCHDOG_SUCCESS;

    if (serviceFunctionRegistered)
    {
        tx_timer_deactivate(&watchdogTimer);
        tx_timer_delete(&watchdogTimer);
        serviceFunctionRegistered = FALSE;
    }
    else
    {
        result = WATCHDOG_WAS_NOT_SERVICING;
    }

    return result;
}




