Zephyr AXI WDT Driver Implementation

This page provides details on the implementation of the AXI WDT Zephyr driver.

Watchdog Subsystem

Zephyr uses watchdog subsystem along with supported driver to enable, set timeout, kick, disable the Axi Timebase WDT. The APIs which are defined in the subsystem are exposed to the application layer. Based on the compatible string, corresponding driver API is called.

The watchdog subsystem has a structure where drivers need to register their APIs by following the same protocol used in the structure mentioned.

Zephyr AXI watchdog (wdt_xilinx_axi) driver stack: the application calls into the watchdog subsystem, which calls the driver setup / install-timeout / feed / disable callbacks, and the driver issues sys_write32 / sys_read32 register accesses to the hardware.

Zephyr AXI watchdog driver stack.

Data structures

/**
 * @brief Watchdog timeout window.
 *
 * Each installed timeout needs feeding within the specified time window,
 * otherwise the watchdog will trigger. If the watchdog instance does not
 * support window timeouts then min value must be equal to 0.
 *
 * @note If specified values can not be precisely set they are always rounded
 * up.
 */
struct wdt_window {
    /** Lower limit of watchdog feed timeout in milliseconds. */
    uint32_t min;
    /** Upper limit of watchdog feed timeout in milliseconds. */
    uint32_t max;
};


/** @brief Watchdog timeout configuration. */
struct wdt_timeout_cfg {
    /** Timing parameters of watchdog timeout. */
    struct wdt_window window;
    /** Timeout callback (can be `NULL`). */
    wdt_callback_t callback;
#if defined(CONFIG_WDT_MULTISTAGE) || defined(__DOXYGEN__)
    /**
     * Pointer to the next timeout configuration.
     *
     * This field is only available if @kconfig{CONFIG_WDT_MULTISTAGE} is
     * enabled (watchdogs with staged timeouts functionality). Value must be
     * `NULL` for single stage timeout.
     */
    struct wdt_timeout_cfg *next;
#endif
    /** Flags (see @ref WDT_FLAGS). */
    uint8_t flags;
};

Typedefs

/**
 * @brief Watchdog callback.
 *
 * @param dev Watchdog device instance.
 * @param channel_id Channel identifier.
 */
typedef void (*wdt_callback_t)(const struct device *dev, int channel_id);

Subsystem wdt_driver_api

__subsystem struct wdt_driver_api {
    wdt_api_setup setup;
    wdt_api_disable disable;
    wdt_api_install_timeout install_timeout;
    wdt_api_feed feed;
};

Watchdog options

#define WDT_OPT_PAUSE_IN_SLEEP   BIT(0)          /* Pause watchdog timer when CPU is in sleep state. */

#define WDT_OPT_PAUSE_HALTED_BY_DBG   BIT(1)    /* Pause watchdog timer when CPU is halted by the debugger. */

Watchdog Behavior Flags

#define WDT_FLAG_RESET_NONE   (0 << WDT_FLAG_RESET_SHIFT)
Reset: none.

#define WDT_FLAG_RESET_CPU_CORE   (1 << WDT_FLAG_RESET_SHIFT)
Reset: CPU core.

#define WDT_FLAG_RESET_SOC   (2 << WDT_FLAG_RESET_SHIFT)
Reset: SoC.

wdt_setup()

int wdt_setup(const struct device *dev, uint8_t options);

Setup the watchdog instance

This function is used for configuring global watchdog settings that affect all timeouts. Call it after installing timeouts. After successful return, all installed timeouts are valid and must be serviced periodically by calling wdt_feed().

Parameters

dev

Watchdog device instance.

options

Configuration options (see Watchdog Options).

Return Values for wdt_setup()

0

If successful.

-ENOTSUP

If any of the set options is not supported.

-EBUSY

If the watchdog instance is already set up.

-errno

In case of any other failure.

wdt_install_timeout()

static int wdt_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg)

Install a new timeout.

Note: This function must be used before wdt_setup(). Changes applied here have no effects until wdt_setup() is called.

Parameters

dev

Watchdog device instance

cfg

Timeout configuration

Return Values for wdt_install_timeout()

channel_id

If successful, a non-negative value indicating the index of the channel to which the timeout was assigned. This value is supposed to be used as the parameter in calls to wdt_feed().

-EBUSY

If timeout can not be installed while watchdog has already been setup.

-ENOMEM

If no more timeouts can be installed.

-ENOTSUP

If any of the set flags is not supported.

-EINVAL

If any of the window timeout value is out of possible range. This value is also returned if watchdog supports only one timeout value for all timeouts and the supplied timeout window differs from windows for alarms installed so far.

-errno

In case of any other failure.

wdt_feed()

int wdt_feed (const struct device *dev, int channel_id)

Feed specified watchdog timeout.

Parameters

dev

Watchdog device instance.

channel_id

Channel index.

Return Values for wdt_feed()

0

If successful.

-EAGAIN

If completing the feed operation stalls the caller, for example due to an in-progress watchdog operation such as a previous wdt_feed() call.

-EINVAL

If there is no installed timeout for supplied channel.

-errno

In case of any other failure.

wdt_disable()

int wdt_disable (const struct device *dev)

Disable watchdog instance.

This function disables the watchdog instance and automatically uninstalls all timeouts. To set up a new watchdog, install timeouts and call wdt_setup() again.

Not all watchdogs can be restarted after they are disabled.

Parameters

dev

Watchdog device instance.

Return Values for wdt_disable()

0

If successful.

-EFAULT

If watchdog instance is not enabled.

-EPERM

If the watchdog cannot be disabled directly by application code.

-errno

In case of any other failure.

axi-timebase-wdt Node

DT Node

axi_timebase_wdt_0: watchdog@41a00000 {
    compatible = "xlnx,axi-timebase-wdt-3.0" , "xlnx,xps-timebase-wdt-1.00.a";
    reg = <0x41a00000 0x10000>;
    clocks = <&clk100>;
    xlnx,wdt-enable-once = <0x1>;
    status = "okay";
    xlnx,wdt-interval = <0x1e>;
};

zephyr/drivers/watchdog/CMakeLists.txt

zephyr_library_sources_ifdef(CONFIG_WDT_XILINX_AXI wdt_xilinx_axi.c)

zephyr/drivers/watchdog/Kconfig

source "drivers/watchdog/Kconfig.xlnx"

zephyr/drivers/watchdog/Kconfig.xlnx

config WDT_XILINX_AXI
    bool "Xilinx AXI Timebase WDT driver"
    default y
    depends on DT_HAS_XLNX_XPS_TIMEBASE_WDT_1_00_A_ENABLED
    help
        Enable the Xilinx AXI Timebase WDT driver.

if WDT_XILINX_AXI
config WDT_XILINX_AXI_HWINFO_API
    bool "Expose HWINFO API in Xilinx AXI Timebase WDT driver"
    default y
    select HWINFO
    help
        Controls whether the Xilinx AXI Timebase WDT driver exposes a HWINFO
        API which allows determining whether the WDT initiated the last
        system reset. This may need to be disabled if using a device or SoC
        which already implements this API.
endif # WDT_XILINX_AXI

zephyr/dts/bindings/watchdog/xlnx,xps-timebase-wdt-1.00.a.yaml

# Copyright (c) 2023, Calian
# SPDX-License-Identifier: Apache-2.0

description: Xilinx AXI timebase WDT core

compatible: "xlnx,xps-timebase-wdt-1.00.a"

include: base.yaml

properties:
  reg:
    required: true

  clocks:
    description: Reference to AXI clock for watchdog core
    required: true

  xlnx,wdt-interval:
    description:
      Configured bit width of watchdog counter in core, controls watchdog expiry duration.
    required: true
    type: int

  xlnx,wdt-enable-once:
    description: Indicates whether WDT can only be enabled once and not disabled
    required: true
    type: int
    enum:
      - 0
      - 1

Probe

#define WDT_XILINX_AXI_INIT(inst)                                                                   \
    static struct xilinx_wdt_axi_data wdt_xilinx_axi_##inst##_dev_data;                             \
                                                                                                    \
    static const struct xilinx_wdt_axi_config wdt_xilinx_axi_##inst##_cfg = {                       \
        .base = DT_INST_REG_ADDR(inst),                                                             \
        .clock_rate = DT_INST_PROP_BY_PHANDLE(inst, clocks, clock_frequency),                       \
        .timer_width_max = DT_INST_PROP(inst, xlnx_wdt_interval),                                   \
        .enable_once = DT_INST_PROP(inst, xlnx_wdt_enable_once),                                    \
    };                                                                                              \
                                                                                                    \
    DEVICE_DT_INST_DEFINE(inst, &wdt_xilinx_axi_init, NULL, &wdt_xilinx_axi_##inst##_dev_data,      \
                  &wdt_xilinx_axi_##inst##_cfg, PRE_KERNEL_1,                                       \
                  CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_xilinx_api);

DT_INST_FOREACH_STATUS_OKAY(WDT_XILINX_AXI_INIT)

Functional Implementation

wdt_xilinx_axi_install_timeout

  1. WDT expiry happens after two wraps of the timer (first raises an interrupt which is not used, second triggers a reset) so add 1 to timer_width.

  2. Check if timer width is good width or bad width.

  3. If good timer width, write the width to MWR register.

  4. If bad width, return -EINVAL.

wdt_xilinx_axi_setup

  1. We do not actually know or control at the driver level whether the WDT pauses in CPU sleep or when halted by the debugger, so we do not check anything with the options.

  2. Write to TWCSR0 and TWCSR1 to start the timer.

wdt_xilinx_axi_feed

  1. Feed the wdt if wdt is already started and by writing to TWCSR0.

wdt_xilinx_axi_disable

  1. Disable the wdt by writing the mask of CSR0_WDS to TWCSR0 and 0 to TWCSR1.