Files

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)
;
}