zephyr/drivers/watchdog/wdt_dw.c
Balsundar Ponnusamy ffffab6ba6 drivers: watchdog: wdt_dw: resetting CPU or SoC is not configurable
specific to platform, watchdog reset line can be connected either to
CPU, SOC or none of the entity. These resets cannot be configured from the
application. So added a warning message when application configures this
option

Signed-off-by: Balsundar Ponnusamy <balsundar.ponnusamy@intel.com>
2023-11-27 20:00:29 +01:00

282 lines
9.1 KiB
C

/* SPDX-License-Identifier: Apache-2.0 */
/*
* Copyright (c) 2023 Intel Corporation
*
* Author: Adrian Warecki <adrian.warecki@intel.com>
*/
#define DT_DRV_COMPAT snps_designware_watchdog
#include <zephyr/drivers/watchdog.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys_clock.h>
#include <zephyr/math/ilog2.h>
#include <zephyr/drivers/reset.h>
#include <zephyr/drivers/clock_control.h>
#include "wdt_dw.h"
#include "wdt_dw_common.h"
LOG_MODULE_REGISTER(wdt_dw, CONFIG_WDT_LOG_LEVEL);
/* Device run time data */
struct dw_wdt_dev_data {
/* MMIO mapping information for watchdog register base address */
DEVICE_MMIO_RAM;
/* Clock frequency */
uint32_t clk_freq;
uint32_t config;
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
wdt_callback_t callback;
#endif
};
/* Device constant configuration parameters */
struct dw_wdt_dev_cfg {
DEVICE_MMIO_ROM;
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks)
/* Clock controller dev instance */
const struct device *clk_dev;
/* Identifier for timer to get clock freq from clk manager */
clock_control_subsys_t clkid;
#endif
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
void (*irq_config)(void);
#endif
uint8_t reset_pulse_length;
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)
/* Reset controller device configurations */
struct reset_dt_spec reset_spec;
#endif
};
static int dw_wdt_setup(const struct device *dev, uint8_t options)
{
struct dw_wdt_dev_data *const dev_data = dev->data;
uintptr_t reg_base = DEVICE_MMIO_GET(dev);
int ret;
ret = dw_wdt_check_options(options);
if (ret != 0) {
return ret;
}
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
/* Configure response mode */
dw_wdt_response_mode_set((uint32_t)reg_base, !!dev_data->callback);
#endif
return dw_wdt_configure((uint32_t)reg_base, dev_data->config);
}
static int dw_wdt_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *config)
{
__maybe_unused const struct dw_wdt_dev_cfg *const dev_config = dev->config;
struct dw_wdt_dev_data *const dev_data = dev->data;
uintptr_t reg_base = DEVICE_MMIO_GET(dev);
if (config == NULL) {
LOG_ERR("watchdog timeout configuration missing");
return -ENODATA;
}
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
if (config->callback && !dev_config->irq_config) {
#else
if (config->callback) {
#endif
LOG_ERR("Interrupt is not configured, can't set a callback.");
return -ENOTSUP;
}
if (config->flags) {
LOG_WRN("Watchdog behavior is not configurable.");
}
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
dev_data->callback = config->callback;
#endif
return dw_wdt_calc_period((uint32_t)reg_base, dev_data->clk_freq, config,
&dev_data->config);
}
static int dw_wdt_feed(const struct device *dev, int channel_id)
{
uintptr_t reg_base = DEVICE_MMIO_GET(dev);
/* Only channel 0 is supported */
if (channel_id) {
return -EINVAL;
}
dw_wdt_counter_restart((uint32_t)reg_base);
return 0;
}
int dw_wdt_disable(const struct device *dev)
{
int ret = -ENOTSUP;
/*
* Once watchdog is enabled by setting WDT_EN bit watchdog cannot be disabled by clearing
* WDT_EN bit and to disable/clear WDT_EN bit watchdog IP should undergo reset
*/
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)
const struct dw_wdt_dev_cfg *const dev_config = dev->config;
/*
* Assert and de-assert reset only if the reset prop is defined in the device
* tree node for this dev instance
*/
if (dev_config->reset_spec.dev != NULL) {
if (!device_is_ready(dev_config->reset_spec.dev)) {
LOG_ERR("reset controller device not ready");
return -ENODEV;
}
/* Assert and de-assert reset watchdog */
ret = reset_line_toggle(dev_config->reset_spec.dev, dev_config->reset_spec.id);
if (ret != 0) {
LOG_ERR("watchdog disable/reset failed");
return ret;
}
}
#endif
return ret;
}
static const struct wdt_driver_api dw_wdt_api = {
.setup = dw_wdt_setup,
.disable = dw_wdt_disable,
.install_timeout = dw_wdt_install_timeout,
.feed = dw_wdt_feed,
};
static int dw_wdt_init(const struct device *dev)
{
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE);
const struct dw_wdt_dev_cfg *const dev_config = dev->config;
int ret;
uintptr_t reg_base = DEVICE_MMIO_GET(dev);
/* Reset watchdog controller if reset controller is supported */
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(resets)
ret = dw_wdt_disable(dev);
if (ret != 0) {
return ret;
}
#endif
/* Get clock frequency from the clock manager if supported */
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(clocks)
struct dw_wdt_dev_data *const dev_data = dev->data;
if (!device_is_ready(dev_config->clk_dev)) {
LOG_ERR("Clock controller device not ready");
return -ENODEV;
}
ret = clock_control_get_rate(dev_config->clk_dev, dev_config->clkid,
&dev_data->clk_freq);
if (ret != 0) {
LOG_ERR("Failed to get watchdog clock rate");
return ret;
}
#endif
ret = dw_wdt_probe((uint32_t)reg_base, dev_config->reset_pulse_length);
if (ret)
return ret;
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
if (dev_config->irq_config) {
dev_config->irq_config();
}
#endif
/*
* Enable watchdog if it needs to be enabled at boot.
* watchdog timer will be started with maximum timeout
* that is the default value.
*/
if (!IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) {
dw_wdt_enable((uint32_t)reg_base);
}
return 0;
}
#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(interrupts)
static void dw_wdt_isr(const struct device *dev)
{
struct dw_wdt_dev_data *const dev_data = dev->data;
uintptr_t reg_base = DEVICE_MMIO_GET(dev);
if (dw_wdt_interrupt_status_register_get((uint32_t)reg_base)) {
/*
* Clearing interrupt here will not assert system reset, so interrupt
* will not be cleared here.
*/
if (dev_data->callback) {
dev_data->callback(dev, 0);
}
}
}
#endif
#define CHECK_CLOCK(inst) \
!(DT_INST_NODE_HAS_PROP(inst, clock_frequency) || DT_INST_NODE_HAS_PROP(inst, clocks)) ||
#if DT_INST_FOREACH_STATUS_OKAY(CHECK_CLOCK) 0
#error Clock frequency not configured!
#endif
/* Bindings to the platform */
#define DW_WDT_IRQ_FLAGS(inst) \
COND_CODE_1(DT_INST_IRQ_HAS_CELL(inst, sense), (DT_INST_IRQ(inst, sense)), (0))
#define DW_WDT_RESET_SPEC_INIT(inst) \
.reset_spec = RESET_DT_SPEC_INST_GET(inst),
#define IRQ_CONFIG(inst) \
static void dw_wdt##inst##_irq_config(void) \
{ \
IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), dw_wdt_isr, \
DEVICE_DT_INST_GET(inst), DW_WDT_IRQ_FLAGS(inst)); \
irq_enable(DT_INST_IRQN(inst)); \
}
#define DW_WDT_INIT(inst) \
IF_ENABLED(DT_NODE_HAS_PROP(DT_DRV_INST(inst), interrupts), (IRQ_CONFIG(inst))) \
\
static const struct dw_wdt_dev_cfg wdt_dw##inst##_config = { \
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(inst)), \
.reset_pulse_length = ilog2(DT_INST_PROP_OR(inst, reset_pulse_length, 2)) - 1, \
IF_ENABLED(DT_INST_NODE_HAS_PROP(inst, resets), \
(DW_WDT_RESET_SPEC_INIT(inst))) \
IF_ENABLED(DT_PHA_HAS_CELL(DT_DRV_INST(inst), clocks, clkid), \
( \
.clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \
.clkid = (clock_control_subsys_t)DT_INST_CLOCKS_CELL(inst, clkid), \
)) \
IF_ENABLED(DT_NODE_HAS_PROP(DT_DRV_INST(inst), interrupts), \
(.irq_config = dw_wdt##inst##_irq_config,) \
) \
}; \
\
static struct dw_wdt_dev_data wdt_dw##inst##_data = { \
COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, clock_frequency), \
(.clk_freq = DT_INST_PROP(inst, clock_frequency)), \
(COND_CODE_1(DT_PHA_HAS_CELL(DT_DRV_INST(inst), clocks, clkid), \
(.clk_freq = 0), \
(.clk_freq = DT_INST_PROP_BY_PHANDLE(inst, clocks, clock_frequency))))), \
}; \
\
DEVICE_DT_INST_DEFINE(inst, &dw_wdt_init, NULL, &wdt_dw##inst##_data, \
&wdt_dw##inst##_config, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &dw_wdt_api);
DT_INST_FOREACH_STATUS_OKAY(DW_WDT_INIT)