You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
258 lines
6.1 KiB
258 lines
6.1 KiB
/* |
|
* Copyright (C) 2017 Intel Deutschland GmbH |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT atmel_sam_watchdog |
|
|
|
/** |
|
* @brief Watchdog (WDT) Driver for Atmel SAM MCUs |
|
* |
|
* Note: |
|
* - Once the watchdog disable bit is set, it cannot be cleared till next |
|
* power reset, i.e, the watchdog cannot be started once stopped. |
|
* - Since the MCU boots with WDT enabled, the CONFIG_WDT_DISABLE_AT_BOOT |
|
* is set default at boot and watchdog module is disabled in the MCU for |
|
* systems that don't need watchdog functionality. |
|
* - If the application needs to use the watchdog in the system, then |
|
* CONFIG_WDT_DISABLE_AT_BOOT must be unset in the app's config file |
|
*/ |
|
|
|
#include <zephyr/drivers/watchdog.h> |
|
#include <zephyr/irq.h> |
|
#include <soc.h> |
|
|
|
#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL |
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(wdt_sam); |
|
|
|
#define SAM_PRESCALAR 128 |
|
#define WDT_MAX_VALUE 4095 |
|
|
|
/* Device constant configuration parameters */ |
|
struct wdt_sam_dev_cfg { |
|
Wdt *regs; |
|
}; |
|
|
|
struct wdt_sam_dev_data { |
|
wdt_callback_t cb; |
|
uint32_t mode; |
|
bool timeout_valid; |
|
bool mode_set; |
|
}; |
|
|
|
static struct wdt_sam_dev_data wdt_sam_data = { 0 }; |
|
|
|
static void wdt_sam_isr(const struct device *dev) |
|
{ |
|
const struct wdt_sam_dev_cfg *config = dev->config; |
|
uint32_t wdt_sr; |
|
Wdt * const wdt = config->regs; |
|
struct wdt_sam_dev_data *data = dev->data; |
|
|
|
/* Clear status bit to acknowledge interrupt by dummy read. */ |
|
wdt_sr = wdt->WDT_SR; |
|
|
|
data->cb(dev, 0); |
|
} |
|
|
|
/** |
|
* @brief Calculates the watchdog counter value (WDV) |
|
* to be installed in the watchdog timer |
|
* |
|
* @param timeout Timeout value in milliseconds. |
|
* @param slow clock on board in Hz. |
|
*/ |
|
int wdt_sam_convert_timeout(uint32_t timeout, uint32_t sclk) |
|
{ |
|
uint32_t max, min; |
|
|
|
timeout = timeout * 1000U; |
|
min = (SAM_PRESCALAR * 1000000) / sclk; |
|
max = min * WDT_MAX_VALUE; |
|
if ((timeout < min) || (timeout > max)) { |
|
LOG_ERR("Invalid timeout value allowed range:" |
|
"%d ms to %d ms", min / 1000U, max / 1000U); |
|
return -EINVAL; |
|
} |
|
|
|
return WDT_MR_WDV(timeout / min); |
|
} |
|
|
|
static int wdt_sam_disable(const struct device *dev) |
|
{ |
|
const struct wdt_sam_dev_cfg *config = dev->config; |
|
|
|
Wdt * const wdt = config->regs; |
|
struct wdt_sam_dev_data *data = dev->data; |
|
|
|
/* since Watchdog mode register is 'write-once', we can't disable if |
|
* someone has already set the mode register |
|
*/ |
|
if (data->mode_set) { |
|
return -EPERM; |
|
} |
|
|
|
/* do we handle -EFAULT here */ |
|
|
|
/* Watchdog Mode register is 'write-once' only register. |
|
* Once disabled, it cannot be enabled until the device is reset |
|
*/ |
|
wdt->WDT_MR |= WDT_MR_WDDIS; |
|
data->mode_set = true; |
|
|
|
return 0; |
|
} |
|
|
|
static int wdt_sam_setup(const struct device *dev, uint8_t options) |
|
{ |
|
const struct wdt_sam_dev_cfg *config = dev->config; |
|
|
|
Wdt * const wdt = config->regs; |
|
struct wdt_sam_dev_data *data = dev->data; |
|
|
|
if (!data->timeout_valid) { |
|
LOG_ERR("No valid timeouts installed"); |
|
return -EINVAL; |
|
} |
|
|
|
/* since Watchdog mode register is 'write-once', we can't set if |
|
* someone has already set the mode register |
|
*/ |
|
if (data->mode_set) { |
|
return -EPERM; |
|
} |
|
|
|
if ((options & WDT_OPT_PAUSE_IN_SLEEP) == WDT_OPT_PAUSE_IN_SLEEP) { |
|
data->mode |= WDT_MR_WDIDLEHLT; |
|
} |
|
|
|
if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) == |
|
WDT_OPT_PAUSE_HALTED_BY_DBG) { |
|
data->mode |= WDT_MR_WDDBGHLT; |
|
} |
|
|
|
wdt->WDT_MR = data->mode; |
|
data->mode_set = true; |
|
|
|
return 0; |
|
} |
|
|
|
static int wdt_sam_install_timeout(const struct device *dev, |
|
const struct wdt_timeout_cfg *cfg) |
|
{ |
|
uint32_t wdt_mode = 0U; |
|
int timeout_value; |
|
|
|
struct wdt_sam_dev_data *data = dev->data; |
|
|
|
if (data->timeout_valid) { |
|
LOG_ERR("No more timeouts can be installed"); |
|
return -ENOMEM; |
|
} |
|
|
|
if (cfg->window.min != 0U) { |
|
return -EINVAL; |
|
} |
|
|
|
/* |
|
* Convert time to cycles. SAM3X SoC doesn't supports window |
|
* timeout config. So the api expects the timeout to be filled |
|
* in the max field of the timeout config. |
|
*/ |
|
timeout_value = wdt_sam_convert_timeout(cfg->window.max, |
|
(uint32_t) CHIP_FREQ_XTAL_32K); |
|
|
|
if (timeout_value < 0) { |
|
return -EINVAL; |
|
} |
|
|
|
switch (cfg->flags) { |
|
case WDT_FLAG_RESET_SOC: |
|
/*A Watchdog fault (underflow or error) activates all resets */ |
|
wdt_mode = WDT_MR_WDRSTEN; /* WDT reset enable */ |
|
break; |
|
|
|
case WDT_FLAG_RESET_NONE: |
|
/* A Watchdog fault (underflow or error) asserts interrupt. */ |
|
if (cfg->callback) { |
|
wdt_mode = WDT_MR_WDFIEN; /* WDT fault interrupt. */ |
|
data->cb = cfg->callback; |
|
} else { |
|
LOG_ERR("Invalid(NULL) ISR callback passed\n"); |
|
return -EINVAL; |
|
} |
|
break; |
|
|
|
/* Processor only reset mode not available in same70 series */ |
|
#ifdef WDT_MR_WDRPROC |
|
case WDT_FLAG_RESET_CPU_CORE: |
|
/*A Watchdog fault activates the processor reset*/ |
|
LOG_DBG("Configuring reset CPU only mode\n"); |
|
wdt_mode = WDT_MR_WDRSTEN | /* WDT reset enable */ |
|
WDT_MR_WDRPROC; /* WDT reset processor only*/ |
|
break; |
|
#endif |
|
default: |
|
LOG_ERR("Unsupported watchdog config Flag\n"); |
|
return -ENOTSUP; |
|
} |
|
|
|
data->mode = wdt_mode | |
|
WDT_MR_WDV(timeout_value) | |
|
WDT_MR_WDD(timeout_value); |
|
|
|
data->timeout_valid = true; |
|
|
|
return 0; |
|
} |
|
|
|
static int wdt_sam_feed(const struct device *dev, int channel_id) |
|
{ |
|
const struct wdt_sam_dev_cfg *config = dev->config; |
|
|
|
/* |
|
* On watchdog restart the Watchdog counter is immediately |
|
* reloaded/fed with the 12-bit watchdog counter |
|
* value from WDT_MR and restarted |
|
*/ |
|
Wdt * const wdt = config->regs; |
|
|
|
wdt->WDT_CR |= WDT_CR_KEY_PASSWD | WDT_CR_WDRSTT; |
|
|
|
return 0; |
|
} |
|
|
|
static DEVICE_API(wdt, wdt_sam_api) = { |
|
.setup = wdt_sam_setup, |
|
.disable = wdt_sam_disable, |
|
.install_timeout = wdt_sam_install_timeout, |
|
.feed = wdt_sam_feed, |
|
}; |
|
|
|
static const struct wdt_sam_dev_cfg wdt_sam_cfg = { |
|
.regs = (Wdt *)DT_INST_REG_ADDR(0), |
|
}; |
|
|
|
static void wdt_sam_irq_config(void) |
|
{ |
|
IRQ_CONNECT(DT_INST_IRQN(0), |
|
DT_INST_IRQ(0, priority), wdt_sam_isr, |
|
DEVICE_DT_INST_GET(0), 0); |
|
irq_enable(DT_INST_IRQN(0)); |
|
} |
|
|
|
static int wdt_sam_init(const struct device *dev) |
|
{ |
|
#ifdef CONFIG_WDT_DISABLE_AT_BOOT |
|
wdt_sam_disable(dev); |
|
#endif |
|
|
|
wdt_sam_irq_config(); |
|
return 0; |
|
} |
|
|
|
DEVICE_DT_INST_DEFINE(0, wdt_sam_init, NULL, |
|
&wdt_sam_data, &wdt_sam_cfg, PRE_KERNEL_1, |
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sam_api);
|
|
|