242 lines
6.6 KiB
C
242 lines
6.6 KiB
C
|
/*
|
||
|
* Copyright (c) 2006-2022, RT-Thread Development Team
|
||
|
*
|
||
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
*
|
||
|
* Change Logs:
|
||
|
* Date Author Notes
|
||
|
* 2022-07-20 Emuzit first version
|
||
|
*/
|
||
|
#include <rthw.h>
|
||
|
#include <rtdebug.h>
|
||
|
#include <drivers/watchdog.h>
|
||
|
#include "ch56x_sys.h"
|
||
|
|
||
|
#define WDOG_HICOUNT_MAX 0xfff // enough to hold (4095 * 120M/524288) >> 8
|
||
|
|
||
|
struct watchdog_device
|
||
|
{
|
||
|
rt_watchdog_t parent;
|
||
|
volatile uint32_t hicount;
|
||
|
uint32_t timeout;
|
||
|
uint32_t reload;
|
||
|
uint8_t is_start;
|
||
|
};
|
||
|
|
||
|
static struct watchdog_device watchdog_device;
|
||
|
|
||
|
static void wdt_reload_counter(rt_watchdog_t *wdt, int cmd)
|
||
|
{
|
||
|
struct watchdog_device *wdt_dev = (void *)wdt;
|
||
|
|
||
|
volatile struct sys_registers *sys = (void *)SYS_REG_BASE;
|
||
|
|
||
|
rt_base_t level;
|
||
|
|
||
|
level = rt_hw_interrupt_disable();
|
||
|
/* reload WDOG_COUNT also clears RB_WDOG_INT_FLAG*/
|
||
|
sys->WDOG_COUNT = (uint8_t)wdt_dev->reload;
|
||
|
wdt_dev->hicount = wdt_dev->reload >> 8;
|
||
|
if (cmd != RT_DEVICE_CTRL_WDT_KEEPALIVE && wdt_dev->is_start)
|
||
|
{
|
||
|
sys_safe_access_enter(sys);
|
||
|
if ((wdt_dev->reload >> 8) == WDOG_HICOUNT_MAX)
|
||
|
{
|
||
|
/* WDOG_COUNT can work on its own, no wdog_irq needed */
|
||
|
sys->RST_WDOG_CTRL.reg = wdog_ctrl_wdat(RB_WDOG_RST_EN);
|
||
|
rt_hw_interrupt_mask(WDOG_IRQn);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* Extend wdt with wdt_dev->hicount through wdog_irq.
|
||
|
* CAVEAT: wdt not effective if global interrupt disabled !!
|
||
|
*/
|
||
|
sys->RST_WDOG_CTRL.reg = wdog_ctrl_wdat(RB_WDOG_INT_EN);
|
||
|
rt_hw_interrupt_umask(WDOG_IRQn);
|
||
|
}
|
||
|
sys_safe_access_leave(sys);
|
||
|
}
|
||
|
rt_hw_interrupt_enable(level);
|
||
|
}
|
||
|
|
||
|
static uint32_t wdt_get_timeleft(rt_watchdog_t *wdt)
|
||
|
{
|
||
|
struct watchdog_device *wdt_dev = (void *)wdt;
|
||
|
|
||
|
volatile struct sys_registers *sys = (void *)SYS_REG_BASE;
|
||
|
|
||
|
uint32_t countleft;
|
||
|
uint64_t count64;
|
||
|
|
||
|
if ((wdt_dev->reload >> 8) == WDOG_HICOUNT_MAX)
|
||
|
{
|
||
|
/* WDOG_COUNT can work on its own, without hicount */
|
||
|
countleft = 0xff - sys->WDOG_COUNT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
uint32_t c1 = sys->WDOG_COUNT;
|
||
|
uint32_t hc = wdt_dev->hicount;
|
||
|
uint32_t c2 = sys->WDOG_COUNT;
|
||
|
/* check if WDOG_COUNT overflows between c1/c2 reads */
|
||
|
if (c2 < c1)
|
||
|
{
|
||
|
rt_base_t level = rt_hw_interrupt_disable();
|
||
|
hc = wdt_dev->hicount;
|
||
|
if (sys->RST_WDOG_CTRL.wdog_int_flag && sys->RST_WDOG_CTRL.wdog_int_en)
|
||
|
{
|
||
|
hc++;
|
||
|
}
|
||
|
rt_hw_interrupt_enable(level);
|
||
|
}
|
||
|
countleft = ((WDOG_HICOUNT_MAX << 8) + 0xff) - ((hc << 8) + c2);
|
||
|
}
|
||
|
|
||
|
/* convert wdt count to seconds : count / (Fsys/524288) */
|
||
|
count64 = countleft;
|
||
|
return (uint32_t)((count64 << 19) / sys_hclk_get());
|
||
|
}
|
||
|
|
||
|
static uint32_t _convert_timeout_to_reload(uint32_t seconds)
|
||
|
{
|
||
|
uint32_t N, R, Fsys, reload = -1;
|
||
|
|
||
|
/* timeout is limited to 4095, not to overflow 32-bit (T * 2^19) */
|
||
|
if (seconds < 4096)
|
||
|
{
|
||
|
/* watchdog timer is clocked at Fsys/524288, arround 3~228Hz */
|
||
|
Fsys = sys_hclk_get();
|
||
|
/* T * (Fsys/2^19) => (T * N) + T * (R/2^19) */
|
||
|
N = Fsys >> 19;
|
||
|
R = Fsys & 0x7ffff;
|
||
|
reload = (WDOG_HICOUNT_MAX << 8) + 0xff;
|
||
|
reload -= seconds * N + ((seconds * R) >> 19) + 1;
|
||
|
}
|
||
|
|
||
|
return reload;
|
||
|
}
|
||
|
|
||
|
static void _stop_wdog_operation()
|
||
|
{
|
||
|
volatile struct sys_registers *sys = (void *)SYS_REG_BASE;
|
||
|
|
||
|
rt_base_t level = rt_hw_interrupt_disable();
|
||
|
sys_safe_access_enter(sys);
|
||
|
sys->RST_WDOG_CTRL.reg = wdog_ctrl_wdat(RB_WDOG_INT_FLAG);
|
||
|
sys_safe_access_leave(sys);
|
||
|
rt_hw_interrupt_enable(level);
|
||
|
|
||
|
rt_hw_interrupt_mask(WDOG_IRQn);
|
||
|
}
|
||
|
|
||
|
static rt_err_t wdt_init(rt_watchdog_t *wdt)
|
||
|
{
|
||
|
struct watchdog_device *wdt_dev = (void *)wdt;
|
||
|
|
||
|
_stop_wdog_operation();
|
||
|
wdt_dev->is_start = 0;
|
||
|
wdt_dev->timeout = -1;
|
||
|
wdt_dev->reload = -1;
|
||
|
|
||
|
return RT_EOK;
|
||
|
}
|
||
|
|
||
|
static rt_err_t wdt_control(rt_watchdog_t *wdt, int cmd, void *arg)
|
||
|
{
|
||
|
struct watchdog_device *wdt_dev = (void *)wdt;
|
||
|
|
||
|
uint32_t reload, timeout;
|
||
|
|
||
|
switch (cmd)
|
||
|
{
|
||
|
case RT_DEVICE_CTRL_WDT_KEEPALIVE:
|
||
|
wdt_reload_counter(wdt, cmd);
|
||
|
break;
|
||
|
case RT_DEVICE_CTRL_WDT_GET_TIMELEFT:
|
||
|
*((uint32_t *)arg) = wdt_get_timeleft(wdt);
|
||
|
break;
|
||
|
case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
|
||
|
*((uint32_t *)arg) = wdt_dev->timeout;
|
||
|
break;
|
||
|
case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
|
||
|
/* CAVEAT: Setting timeout larger than an 8-bit WDOG_COUNT can
|
||
|
* hold turns the wdog into interrupt mode, which makes wdog
|
||
|
* usless if cause of death is lost global interrupt enable.
|
||
|
*/
|
||
|
timeout = *((uint32_t *)arg);
|
||
|
reload = _convert_timeout_to_reload(timeout);
|
||
|
if ((reload >> 8) > WDOG_HICOUNT_MAX)
|
||
|
{
|
||
|
return -RT_EINVAL;
|
||
|
}
|
||
|
wdt_dev->timeout = timeout;
|
||
|
wdt_dev->reload = reload;
|
||
|
/* FIXME: code example implies wdt started by SET_TIMEOUT ? */
|
||
|
case RT_DEVICE_CTRL_WDT_START:
|
||
|
if ((wdt_dev->reload >> 8) > WDOG_HICOUNT_MAX)
|
||
|
{
|
||
|
return -RT_EINVAL;
|
||
|
}
|
||
|
wdt_dev->is_start = 1;
|
||
|
wdt_reload_counter(wdt, cmd);
|
||
|
break;
|
||
|
case RT_DEVICE_CTRL_WDT_STOP:
|
||
|
_stop_wdog_operation();
|
||
|
wdt_dev->is_start = 0;
|
||
|
break;
|
||
|
default:
|
||
|
return -RT_ERROR;
|
||
|
}
|
||
|
|
||
|
return RT_EOK;
|
||
|
}
|
||
|
|
||
|
static struct rt_watchdog_ops watchdog_ops =
|
||
|
{
|
||
|
.init = wdt_init,
|
||
|
.control = wdt_control,
|
||
|
};
|
||
|
|
||
|
int rt_hw_wdt_init(void)
|
||
|
{
|
||
|
rt_uint32_t flag;
|
||
|
|
||
|
watchdog_device.parent.ops = &watchdog_ops;
|
||
|
|
||
|
flag = RT_DEVICE_FLAG_DEACTIVATE;
|
||
|
return rt_hw_watchdog_register(&watchdog_device.parent, "wdt", flag, RT_NULL);
|
||
|
}
|
||
|
INIT_BOARD_EXPORT(rt_hw_wdt_init);
|
||
|
|
||
|
void wdog_irq_handler(void) __attribute__((interrupt()));
|
||
|
void wdog_irq_handler(void)
|
||
|
{
|
||
|
volatile struct pfic_registers *pfic;
|
||
|
volatile struct sys_registers *sys;
|
||
|
|
||
|
rt_interrupt_enter();
|
||
|
|
||
|
sys = (struct sys_registers *)SYS_REG_BASE;
|
||
|
/* FIXME: RB_WDOG_INT_FLAG seems completely not functioning at all !!
|
||
|
* It's not set at WDOG_COUNT overflow, writing 1 to it does not clear
|
||
|
* wdt interrupt. Bit 16 of pfic->IPR[0] is not effective thereof.
|
||
|
*/
|
||
|
if (watchdog_device.hicount < WDOG_HICOUNT_MAX)
|
||
|
{
|
||
|
watchdog_device.hicount++;
|
||
|
/* clear interrupt flag */
|
||
|
//sys->RST_WDOG_CTRL.reg |= RB_WDOG_INT_FLAG;
|
||
|
sys->WDOG_COUNT = sys->WDOG_COUNT;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
/* reset system if watchdog timeout */
|
||
|
uint8_t u8v = RB_SOFTWARE_RESET | RB_WDOG_INT_FLAG;
|
||
|
sys_safe_access_enter(sys);
|
||
|
sys->RST_WDOG_CTRL.reg = wdog_ctrl_wdat(u8v);
|
||
|
sys_safe_access_leave(sys);
|
||
|
}
|
||
|
|
||
|
rt_interrupt_leave();
|
||
|
}
|