348 lines
8.2 KiB
C
348 lines
8.2 KiB
C
/*
|
|
* Copyright (c) 2021-2022 Amlogic, Inc. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <FreeRTOS.h>
|
|
#include <task.h>
|
|
#include <aml_tasks_ext.h>
|
|
#include <timers.h>
|
|
#include <common.h>
|
|
#include <irq.h>
|
|
#include <interrupt.h>
|
|
#include <watchdog-data.h>
|
|
#include "watchdog.h"
|
|
|
|
#define DRV_NAME_PREFIX "WDT: "
|
|
|
|
#if !defined(BIT_WATCHDOG_RESET_EN)
|
|
#define BIT_WATCHDOG_RESET_EN BIT(22)
|
|
#endif
|
|
|
|
#if defined(WATCHDOG_FIX_TIMEOUT)
|
|
#define WATCHDOG_TIMEOUT WATCHDOG_FIX_TIMEOUT
|
|
#else
|
|
#define WATCHDOG_TIMEOUT CONFIG_WATCHDOG_TIMEOUT
|
|
#endif
|
|
|
|
#if defined(WATCHDOG_FIX_PING)
|
|
#define WATCHDOG_PING WATCHDOG_FIX_PING
|
|
#else
|
|
#define WATCHDOG_PING CONFIG_WATCHDOG_PING
|
|
#endif
|
|
|
|
#if defined(WATCHDOG_FIX_PRETIMEOUT)
|
|
#define WATCHDOG_PRETIMEOUT WATCHDOG_FIX_PRETIMEOUT
|
|
#else
|
|
#define WATCHDOG_PRETIMEOUT CONFIG_WATCHDOG_PRETIMEOUT
|
|
#endif
|
|
|
|
/*
|
|
* NOTE: If you want to enable dump, you need to enable the following
|
|
* macro definition in FreeRTOSConfig.h
|
|
*
|
|
* Configuration for reference:
|
|
* ======================================================================
|
|
* #include <timer_source.h>
|
|
* static unsigned long long startTime;
|
|
* static inline void prvTimerForRunTimeStats(void)
|
|
* {
|
|
* startTime = timere_read_us();
|
|
* }
|
|
* static inline uint32_t prvTimeCounterValue(void)
|
|
* {
|
|
* return (uint32_t)((timere_read_us() - startTime) >> 8);
|
|
* }
|
|
* #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS prvTimerForRunTimeStats
|
|
* #define portGET_RUN_TIME_COUNTER_VALUE prvTimeCounterValue
|
|
* #define configGENERATE_RUN_TIME_STATS 1
|
|
* #define configUSE_TRACE_FACILITY 1
|
|
* #define configUSE_STATS_FORMATTING_FUNCTIONS 1
|
|
* ======================================================================
|
|
*/
|
|
#if ((configUSE_TRACE_FACILITY == 1) && (configUSE_STATS_FORMATTING_FUNCTIONS > 0))
|
|
#define ENABLE_TASK_DUMP
|
|
#endif
|
|
|
|
static uint32_t prvRegBackup[4];
|
|
static TimerHandle_t prvFeederTimer;
|
|
#if defined(ENABLE_TASK_DUMP)
|
|
static TaskHandle_t prvDumpTask;
|
|
#endif
|
|
|
|
static void prvWatchdogIrqHandler(void)
|
|
{
|
|
#if defined(ENABLE_TASK_DUMP)
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
|
#endif
|
|
unsigned long mepc;
|
|
|
|
__asm__ __volatile__("csrr %0, mepc" : "=r"(mepc));
|
|
printf(DRV_NAME_PREFIX "mepc-> 0x%lx\n", mepc);
|
|
printf(DRV_NAME_PREFIX "!! RESET AFTER %dS !!\n", WATCHDOG_PRETIMEOUT);
|
|
|
|
#if defined(ENABLE_TASK_DUMP)
|
|
vTaskNotifyGiveFromISR(prvDumpTask, &xHigherPriorityTaskWoken);
|
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
|
#endif
|
|
}
|
|
|
|
#if !defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
static unsigned char prvTickCount;
|
|
|
|
static void prvHrtimerSetup(void)
|
|
{
|
|
/*
|
|
* TIMERC:
|
|
* BIT[1:0] 0:cts_sys_clk 1:1us 2:10us 3:100us
|
|
*/
|
|
REG32(SYSCTRL_TIMERC) = 1 * 1000000 / 100; /* 1 second */
|
|
REG32(SYSCTRL_TIMERC_CTRL) |= (1 << 7) | (0 << 6) | 0x3;
|
|
}
|
|
|
|
static void prvHrtimerTickHandler(void)
|
|
{
|
|
prvTickCount++;
|
|
|
|
if (prvTickCount == (WATCHDOG_TIMEOUT - WATCHDOG_PRETIMEOUT)) {
|
|
prvTickCount = 0;
|
|
prvWatchdogIrqHandler();
|
|
} else {
|
|
/* Restart */
|
|
prvHrtimerSetup();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void prvWatchdogPing(void)
|
|
{
|
|
REG32(REG_WATCHDOG_CLR) = 0;
|
|
|
|
#if !defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
prvTickCount = 0;
|
|
/* Restart */
|
|
prvHrtimerSetup();
|
|
#endif
|
|
|
|
#if defined(DEBUG)
|
|
printf(DRV_NAME_PREFIX "PING\n");
|
|
#endif
|
|
}
|
|
|
|
static void prvWatchdogSetTime(bool oscin32k)
|
|
{
|
|
#if defined(DEBUG)
|
|
printf(DRV_NAME_PREFIX "OSCIN: %s\n", oscin32k ? "32K" : "24M");
|
|
#endif
|
|
/*
|
|
* Clock SRC: 24MHz OR 32KHz
|
|
* Clock DIV: 24000 OR 32 (1mS)
|
|
*/
|
|
REG32(REG_WATCHDOG_CTRL) |= BIT(24) | BIT(25) |
|
|
BIT_WATCHDOG_RESET_EN |
|
|
((oscin32k ? 32 : 24000) - 1);
|
|
|
|
/* Set timeout */
|
|
REG32(REG_WATCHDOG_CNT) = WATCHDOG_TIMEOUT * 1000;
|
|
|
|
#if defined(REG_WATCHDOG_DLY_CNT)
|
|
/* Delay 10ms (reset acpu) */
|
|
REG32(REG_WATCHDOG_DLY_CNT) = (oscin32k ? 32 : 24000) * 10 - 1;
|
|
#endif
|
|
|
|
#if defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
/* A4 and later chips support this feature */
|
|
REG32(REG_WATCHDOG_IRQ_OFFSET) = WATCHDOG_PRETIMEOUT * 1000;
|
|
#endif
|
|
}
|
|
|
|
void vWatchdogUpdate(bool oscin32k)
|
|
{
|
|
REG32(REG_WATCHDOG_CTRL) = 0;
|
|
prvWatchdogSetTime(oscin32k);
|
|
#if defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
REG32(REG_WATCHDOG_CTRL) |= BIT(23);
|
|
#endif
|
|
REG32(REG_WATCHDOG_CTRL) |= BIT(18);
|
|
prvWatchdogPing();
|
|
}
|
|
|
|
static void prvWatchdogInit(void)
|
|
{
|
|
/* Backup register */
|
|
prvRegBackup[0] = REG32(REG_WATCHDOG_CTRL);
|
|
prvRegBackup[1] = REG32(REG_WATCHDOG_CNT);
|
|
#if defined(REG_WATCHDOG_DLY_CNT)
|
|
prvRegBackup[2] = REG32(REG_WATCHDOG_DLY_CNT);
|
|
#endif
|
|
#if defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
prvRegBackup[3] = REG32(REG_WATCHDOG_IRQ_OFFSET);
|
|
#endif
|
|
|
|
REG32(REG_WATCHDOG_CTRL) = 0;
|
|
prvWatchdogSetTime(false);
|
|
|
|
#if defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
/* A4 and later chips support this feature */
|
|
REG32(REG_WATCHDOG_CTRL) |= BIT(23); /* Enable irq bit */
|
|
RegisterIrq(IRQ_WATCHDOG, 2, prvWatchdogIrqHandler);
|
|
EnableIrq(IRQ_WATCHDOG);
|
|
#else
|
|
/* Use hardware timer to simulate pretimeout */
|
|
prvHrtimerSetup();
|
|
RegisterIrq(IRQ_NUM_TIMERC, 2, prvHrtimerTickHandler);
|
|
EnableIrq(IRQ_NUM_TIMERC);
|
|
#endif
|
|
|
|
/* Enable */
|
|
REG32(REG_WATCHDOG_CTRL) |= BIT(18);
|
|
|
|
prvWatchdogPing();
|
|
}
|
|
|
|
static void prvWatchdogDeinit(void)
|
|
{
|
|
prvWatchdogPing();
|
|
|
|
/* Disable */
|
|
REG32(REG_WATCHDOG_CTRL) &= ~BIT(18);
|
|
|
|
#if defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
DisableIrq(IRQ_WATCHDOG);
|
|
UnRegisterIrq(IRQ_WATCHDOG);
|
|
#else
|
|
REG32(SYSCTRL_TIMERC_CTRL) = 0;
|
|
DisableIrq(IRQ_NUM_TIMERC);
|
|
UnRegisterIrq(IRQ_NUM_TIMERC);
|
|
#endif
|
|
|
|
/* Restore register */
|
|
#if defined(REG_WATCHDOG_IRQ_OFFSET)
|
|
REG32(REG_WATCHDOG_IRQ_OFFSET) = prvRegBackup[3];
|
|
#endif
|
|
#if defined(REG_WATCHDOG_DLY_CNT)
|
|
REG32(REG_WATCHDOG_DLY_CNT) = prvRegBackup[2];
|
|
#endif
|
|
REG32(REG_WATCHDOG_CNT) = prvRegBackup[1];
|
|
REG32(REG_WATCHDOG_CTRL) = prvRegBackup[0];
|
|
}
|
|
|
|
static void prvWatchdogFeederTimer(TimerHandle_t xTimer)
|
|
{
|
|
(void)xTimer;
|
|
prvWatchdogPing();
|
|
}
|
|
|
|
#if defined(ENABLE_TASK_DUMP)
|
|
static void prvWatchdogDumpTask(void *pvParameters)
|
|
{
|
|
char *pcWriteBuffer;
|
|
|
|
(void)pvParameters;
|
|
|
|
while (1) {
|
|
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
|
|
|
pcWriteBuffer = (char *)pvPortMalloc(2048);
|
|
if (!pcWriteBuffer) {
|
|
printf(DRV_NAME_PREFIX "ALLOC FAIL\n");
|
|
continue;
|
|
}
|
|
|
|
// vTaskList(pcWriteBuffer);
|
|
vTaskRuntimeStatsList(pcWriteBuffer);
|
|
printf("Task Name\tNumber\tStatus\tPriority\tStack\t\tMask\t\tCPU\n");
|
|
printf("---------\t------\t------\t--------\t-----\t\t----\t\t---\n");
|
|
printf("%s", pcWriteBuffer);
|
|
vPortFree(pcWriteBuffer);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(WATCHDOG_SELF_TEST)
|
|
static void prvWatchdogTestTask(void *pvParameters)
|
|
{
|
|
(void)pvParameters;
|
|
|
|
printf(DRV_NAME_PREFIX "SELF-TEST STARTED\n");
|
|
vTaskDelay(pdMS_TO_TICKS((WATCHDOG_TIMEOUT + 2) * 1000));
|
|
printf(DRV_NAME_PREFIX "MONITOR HANG\n");
|
|
while (1)
|
|
;
|
|
}
|
|
#endif
|
|
|
|
void vWatchdogEnable(void)
|
|
{
|
|
/* Feed the watchdog timer */
|
|
prvFeederTimer = xTimerCreate((const char *)"WDT_F_TIM",
|
|
pdMS_TO_TICKS(WATCHDOG_PING * 1000),
|
|
pdTRUE, NULL,
|
|
(TimerCallbackFunction_t)prvWatchdogFeederTimer);
|
|
if (!prvFeederTimer) {
|
|
printf(DRV_NAME_PREFIX "C TIM FAIL\n");
|
|
return;
|
|
}
|
|
|
|
if (xTimerStart(prvFeederTimer, pdMS_TO_TICKS(2000)) != pdPASS) {
|
|
printf(DRV_NAME_PREFIX "S TIM FAIL\n");
|
|
xTimerDelete(prvFeederTimer, pdMS_TO_TICKS(2000));
|
|
return;
|
|
}
|
|
|
|
#if defined(ENABLE_TASK_DUMP)
|
|
/* Dump task */
|
|
xTaskCreate(prvWatchdogDumpTask, "WDT_D_TASK", configMINIMAL_STACK_SIZE,
|
|
NULL, configMAX_PRIORITIES - 1, &prvDumpTask);
|
|
if (!prvDumpTask) {
|
|
printf(DRV_NAME_PREFIX "DUMP TASK FAIL\n");
|
|
xTimerStop(prvFeederTimer, pdMS_TO_TICKS(2000));
|
|
xTimerDelete(prvFeederTimer, pdMS_TO_TICKS(2000));
|
|
prvFeederTimer = NULL;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
prvWatchdogInit();
|
|
|
|
printf(DRV_NAME_PREFIX "EN %d/%d/%d\n", WATCHDOG_TIMEOUT,
|
|
WATCHDOG_PING, WATCHDOG_PRETIMEOUT);
|
|
|
|
#if defined(WATCHDOG_SELF_TEST)
|
|
/* Self-Test task */
|
|
xTaskCreate(prvWatchdogTestTask, "WDT_T_TASK", configMINIMAL_STACK_SIZE,
|
|
NULL, configMAX_PRIORITIES - 2, NULL);
|
|
#endif
|
|
}
|
|
|
|
void vWatchdogDisable(void)
|
|
{
|
|
if (!prvFeederTimer)
|
|
return;
|
|
|
|
prvWatchdogDeinit();
|
|
xTimerStop(prvFeederTimer, pdMS_TO_TICKS(2000));
|
|
xTimerDelete(prvFeederTimer, pdMS_TO_TICKS(2000));
|
|
prvFeederTimer = NULL;
|
|
|
|
#if defined(ENABLE_TASK_DUMP)
|
|
if (prvDumpTask) {
|
|
vTaskDelete(prvDumpTask);
|
|
prvDumpTask = NULL;
|
|
}
|
|
#endif
|
|
|
|
printf(DRV_NAME_PREFIX "DIS\n");
|
|
}
|
|
|
|
void vWatchdogReboot(void)
|
|
{
|
|
printf("\n" DRV_NAME_PREFIX "REBOOT NOW\n");
|
|
REG32(REG_WATCHDOG_CNT) = 1; /* 1mS */
|
|
while (1)
|
|
;
|
|
}
|