| /* 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) |