mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
cde8ac6093
The NXP QN908x CPU family is a Cortex-M4F CPU with integrated USB, Bluetooth Low Energy and in some variants NFC. This patch implements the first steps for having support for this CPU. While the QN908x can be considered the successor of similar chips from NXP like the KW41Z when looking at the feature set, the internal architecture, boot image format and CPU peripherals don't match those in the Kinetis line. Therefore, this patch creates a new directory for just the QN908x chip under cpu/qn908x. The minimal set of peripherals are implemented in this patch to allow the device to boot and enable a GPIO: the gpio and wdt peripheral modules only. The wdt driver is required to boot and disable the wdt. On reset, the wdt is disabled by the chip, however the QN908x bootloader stored in the internal ROM enables the wdt and sets a timer to reboot after 10 seconds, therefore it is needed to disable the wdt in RIOT OS soon after booting. This patch sets it up such that when no periph_wdt module is used the Watchdog is disabled, but if the periph_wdt is used it must be configured (initialized) within the first 10 seconds. Tests performed: Defined a custom board for this CPU and compiled a simple application that blinks some LEDs. Manually tested with periph_wdt and with periph_wdt_cb as well.
192 lines
6.2 KiB
C
192 lines
6.2 KiB
C
/*
|
|
* Copyright (C) 2020 iosabi
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser General
|
|
* Public License v2.1. See the file LICENSE in the top level directory for more
|
|
* details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup cpu_qn908x
|
|
* @ingroup cpu_qn908x_wdt
|
|
*
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Low-level WDOG driver implementation
|
|
*
|
|
* @author iosabi <iosabi@protonmail.com>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include "periph/wdt.h"
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "vendor/drivers/fsl_clock.h"
|
|
|
|
#include "debug.h"
|
|
|
|
/* The number of cycles to refresh the WDT with when kicked. */
|
|
static uint32_t wdt_load_value = 0xffffffff;
|
|
|
|
/* The maximum value the WDT counter could have when kicked. The WDT counter
|
|
* always decrements starting from wdt_load_value, so when used in WINDOW mode
|
|
* a counter value larger than this means that WDT was kicked before the lower
|
|
* bound of the window.
|
|
*/
|
|
static uint32_t wdt_load_window_value = 0xffffffff;
|
|
|
|
/* The value that will be loaded in the WDT counter after the first interrupt
|
|
* triggers. The WDT doesn't not reset the device automatically once the WDT
|
|
* counter reaches 0, instead it first triggers an interrupt and restarts the
|
|
* count again. This value will be loaded by the ISR after it triggers. A value
|
|
* of 0 means to reset immediately after the ISR triggers, which is used if no
|
|
* callback is provided. */
|
|
static uint32_t wdt_load_after_isr = 0;
|
|
|
|
#ifdef MODULE_PERIPH_WDT_CB
|
|
static wdt_cb_t wdt_cb;
|
|
static void *wdt_arg;
|
|
static bool wdt_kick_disabled = false;
|
|
#endif /* MODULE_PERIPH_WDT_CB */
|
|
|
|
/**
|
|
* Before making any change to the WDT it is required to "unlock" it by writing
|
|
* this value to the LOCK register. Call @ref _wdt_lock() to lock it again.
|
|
*/
|
|
static void _wdt_unlock(void)
|
|
{
|
|
WDT->LOCK = 0x1acce551;
|
|
}
|
|
|
|
/**
|
|
* @brief Lock the WDT block to prevent accidental changes.
|
|
*/
|
|
static void _wdt_lock(void)
|
|
{
|
|
WDT->LOCK = 0x10c1ced; /* Any other value is as good. */
|
|
}
|
|
|
|
void wdt_start(void)
|
|
{
|
|
CLOCK_EnableClock(kCLOCK_Wdt);
|
|
}
|
|
|
|
void wdt_stop(void)
|
|
{
|
|
/* This only stops the clock of the watchdog and therefore the counter
|
|
* stops, but leaves it otherwise configured. */
|
|
CLOCK_DisableClock(kCLOCK_Wdt);
|
|
}
|
|
|
|
void wdt_kick(void)
|
|
{
|
|
#ifdef MODULE_PERIPH_WDT_CB
|
|
if (wdt_kick_disabled) {
|
|
return;
|
|
}
|
|
#endif /* MODULE_PERIPH_WDT_CB */
|
|
if (WDT->VALUE > wdt_load_window_value) {
|
|
DEBUG("wdt_kick() called before the minimum window time.");
|
|
/* In this condition we simulate the WDT triggering immediately by
|
|
* setting its LOAD value to 0. This will cause the ISR (and the
|
|
* callback if there is one) if it wasn't called yet, which will update
|
|
* the LOAD value with the time derived from CONFIG_WDT_WARNING_PERIOD
|
|
* if needed and reset the device afterwards. */
|
|
_wdt_unlock();
|
|
WDT->LOAD = 0;
|
|
_wdt_lock();
|
|
while (true) {}
|
|
}
|
|
_wdt_unlock();
|
|
WDT->LOAD = wdt_load_value;
|
|
_wdt_lock();
|
|
}
|
|
|
|
static void wdt_setup(uint32_t min_time, uint32_t max_time, uint32_t isr_time)
|
|
{
|
|
/* Check reset time limit */
|
|
assert((max_time > NWDT_TIME_LOWER_LIMIT)
|
|
&& (max_time < NWDT_TIME_UPPER_LIMIT));
|
|
assert(min_time <= max_time);
|
|
|
|
/* The clock is stopped after setup and calling wdt_start() is required. */
|
|
wdt_stop();
|
|
|
|
const uint32_t tick_per_ms = CLOCK_GetFreq(kCLOCK_WdtClk) / 1000;
|
|
wdt_load_value = tick_per_ms * max_time;
|
|
wdt_load_window_value = tick_per_ms * (max_time - min_time);
|
|
wdt_load_after_isr = tick_per_ms * isr_time;
|
|
/* The ISR is always called as it is needed to trigger the reset after the
|
|
* appropriate delay even in the no callback case. */
|
|
_wdt_unlock();
|
|
WDT->LOAD = wdt_load_value;
|
|
WDT->CTRL = WDT_CTRL_RESEN_MASK | WDT_CTRL_INTEN_MASK;
|
|
_wdt_lock();
|
|
NVIC_EnableIRQ(WDT_IRQn);
|
|
}
|
|
|
|
void wdt_setup_reboot(uint32_t min_time, uint32_t max_time)
|
|
{
|
|
#ifdef MODULE_PERIPH_WDT_CB
|
|
wdt_cb = NULL;
|
|
wdt_arg = NULL;
|
|
#endif /* MODULE_PERIPH_WDT_CB */
|
|
wdt_setup(min_time, max_time, 0);
|
|
}
|
|
|
|
#ifdef MODULE_PERIPH_WDT_CB
|
|
void wdt_setup_reboot_with_callback(uint32_t min_time, uint32_t max_time,
|
|
wdt_cb_t cb, void *arg)
|
|
{
|
|
wdt_cb = cb;
|
|
wdt_arg = arg;
|
|
assert(max_time >= CONFIG_WDT_WARNING_PERIOD);
|
|
/* We don't support having a min_time that falls within the
|
|
* CONFIG_WDT_WARNING_PERIOD since that would mean that you can't call
|
|
* wdt_kick() until some time after the callback is called which is pretty
|
|
* useless considering that the purpose of the callback is to perform
|
|
* "specific safety operations of data logging before the actual reboot."
|
|
* before the reboot happens. After the callback is called the reboot is
|
|
* inevitable and calling wdt_kick() has no effect. This code moves the
|
|
* min_time back to at least the point where the callback is called which is
|
|
* a similar behavior. */
|
|
if (max_time - CONFIG_WDT_WARNING_PERIOD < min_time) {
|
|
min_time = max_time - CONFIG_WDT_WARNING_PERIOD;
|
|
}
|
|
wdt_setup(min_time, max_time - CONFIG_WDT_WARNING_PERIOD,
|
|
CONFIG_WDT_WARNING_PERIOD);
|
|
}
|
|
#endif /* MODULE_PERIPH_WDT_CB */
|
|
|
|
void isr_wdt(void)
|
|
{
|
|
DEBUG("[wdt] isr_wdt with LOAD=%" PRIu32 "\n", WDT->LOAD);
|
|
|
|
/* Set the timer to reset the device after CONFIG_WDT_WARNING_PERIOD but not
|
|
* clear the interrupt bit in the WDT->INT_CLR register. This will cause the
|
|
* WDT to reset the device the next time the counter reaches 0, which now
|
|
* will happen again in CONFIG_WDT_WARNING_PERIOD ms.
|
|
* Since the wdt_cb may return before the new WDT counter triggers, which is
|
|
* the normal case if we expect the callback to do some short safety
|
|
* operations, we would exit the ISR without clearing the interrupt bit in
|
|
* WDT->INT_CLR which would cause the interrupt to be called again. To avoid
|
|
* this situation, we disable the IRQ source in the NVIC.
|
|
* After this ISR triggers, further wdt_kicks() are ignored to prevent the
|
|
* software from kicking the WDT during the CONFIG_WDT_WARNING_PERIOD. */
|
|
NVIC_DisableIRQ(WDT_IRQn);
|
|
_wdt_unlock();
|
|
WDT->LOAD = wdt_load_after_isr;
|
|
_wdt_lock();
|
|
|
|
#ifdef MODULE_PERIPH_WDT_CB
|
|
wdt_kick_disabled = true;
|
|
if (wdt_cb) {
|
|
wdt_cb(wdt_arg);
|
|
}
|
|
#endif /* MODULE_PERIPH_WDT_CB */
|
|
cortexm_isr_end();
|
|
}
|