242 lines
6.6 KiB
C
242 lines
6.6 KiB
C
/* SPDX-License-Identifier: Apache-2.0 */
|
|
/*
|
|
* Copyright (c) 2023 Intel Corporation
|
|
*
|
|
* Author: Adrian Warecki <adrian.warecki@intel.com>
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
*
|
|
* @brief Intel ACE per-core watchdogs driver
|
|
*
|
|
* The ace platform has a set of designware watchdogs, one for each core. This driver is responsible
|
|
* for finding the base addresses of subordinate devices, controlling the pause signal and handling
|
|
* interrupts. The registers from the DSP Core Watch Dog Timer Control & Status block are used for
|
|
* this purpose. This block is shared by all per-core watchdogs.
|
|
*
|
|
* The base addresses of the subordinate watchdogs are read from the control registers. As a result,
|
|
* in the device tree we have only one base address for the intel watchdog.
|
|
*
|
|
* The designware watchdog only supports a hardware pause signal. It cannot be paused
|
|
* programmatically. On ace platform there are GPIO-like registers that allows control of a hardware
|
|
* pause signal for subordinate watchdogs.
|
|
*
|
|
* All subordinate watchdog devices share the same interrupt number. Each watchdog reports
|
|
* an interrupt to the core to which it has been assigned. The same interrupt number cannot
|
|
* be used by multiple devices in the device tree. This driver handles interrupts from all
|
|
* subordinate watchdogs and identifies which device signal it.
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT intel_adsp_watchdog
|
|
|
|
#include <zephyr/drivers/watchdog.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys_clock.h>
|
|
#include <zephyr/math/ilog2.h>
|
|
|
|
#include "wdt_dw.h"
|
|
#include "wdt_dw_common.h"
|
|
#include "wdt_intel_adsp.h"
|
|
|
|
LOG_MODULE_REGISTER(wdt_intel_adsp, CONFIG_WDT_LOG_LEVEL);
|
|
|
|
#define DEV_NODE DT_INST(0, DT_DRV_COMPAT)
|
|
#define WDT_INTEL_ADSP_INTERRUPT_SUPPORT DT_NODE_HAS_PROP(DEV_NODE, interrupts)
|
|
|
|
/* Device run time data */
|
|
struct intel_adsp_wdt_dev_data {
|
|
uint32_t core_wdt[CONFIG_MP_MAX_NUM_CPUS];
|
|
uint32_t period_cfg;
|
|
bool allow_reset;
|
|
#if WDT_INTEL_ADSP_INTERRUPT_SUPPORT
|
|
wdt_callback_t callback;
|
|
#endif
|
|
};
|
|
|
|
/* Device constant configuration parameters */
|
|
struct intel_adsp_wdt_dev_cfg {
|
|
uint32_t base;
|
|
uint32_t clk_freq;
|
|
};
|
|
|
|
static int intel_adsp_wdt_setup(const struct device *dev, uint8_t options)
|
|
{
|
|
const struct intel_adsp_wdt_dev_cfg *const dev_config = dev->config;
|
|
struct intel_adsp_wdt_dev_data *const dev_data = dev->data;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
ret = dw_wdt_check_options(options);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < arch_num_cpus(); i++) {
|
|
ret = dw_wdt_configure(dev_data->core_wdt[i], dev_data->period_cfg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
#if WDT_INTEL_ADSP_INTERRUPT_SUPPORT
|
|
dw_wdt_response_mode_set(dev_data->core_wdt[i], !!dev_data->callback);
|
|
#endif
|
|
intel_adsp_wdt_reset_set(dev_config->base, i, dev_data->allow_reset);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int intel_adsp_wdt_install_timeout(const struct device *dev,
|
|
const struct wdt_timeout_cfg *config)
|
|
{
|
|
const struct intel_adsp_wdt_dev_cfg *const dev_config = dev->config;
|
|
struct intel_adsp_wdt_dev_data *const dev_data = dev->data;
|
|
int ret;
|
|
|
|
#if WDT_INTEL_ADSP_INTERRUPT_SUPPORT
|
|
dev_data->callback = config->callback;
|
|
#else
|
|
if (config->callback) {
|
|
LOG_ERR("Interrupt is not configured, can't set a callback.");
|
|
return -ENOTSUP;
|
|
}
|
|
#endif
|
|
|
|
ret = dw_wdt_calc_period(dev_data->core_wdt[0], dev_config->clk_freq, config,
|
|
&dev_data->period_cfg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (config->flags & WDT_FLAG_RESET_CPU_CORE) {
|
|
dev_data->allow_reset = true;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int intel_adsp_wdt_feed(const struct device *dev, int channel_id)
|
|
{
|
|
struct intel_adsp_wdt_dev_data *const dev_data = dev->data;
|
|
|
|
if (channel_id >= arch_num_cpus()) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
dw_wdt_counter_restart(dev_data->core_wdt[channel_id]);
|
|
return 0;
|
|
}
|
|
|
|
#if WDT_INTEL_ADSP_INTERRUPT_SUPPORT
|
|
static void intel_adsp_wdt_isr(const struct device *dev)
|
|
{
|
|
struct intel_adsp_wdt_dev_data *const dev_data = dev->data;
|
|
const uint32_t cpu = arch_proc_id();
|
|
const uint32_t base = dev_data->core_wdt[cpu];
|
|
|
|
if (dw_wdt_interrupt_status_register_get(base)) {
|
|
if (dev_data->callback) {
|
|
dev_data->callback(dev, cpu);
|
|
}
|
|
|
|
dw_wdt_clear_interrupt(base);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static int intel_adsp_wdt_init(const struct device *dev)
|
|
{
|
|
const unsigned int reset_pulse_length =
|
|
ilog2(DT_PROP(DEV_NODE, reset_pulse_length)) - 1;
|
|
const struct intel_adsp_wdt_dev_cfg *const dev_config = dev->config;
|
|
struct intel_adsp_wdt_dev_data *const dev_data = dev->data;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
for (i = 0; i < arch_num_cpus(); i++) {
|
|
dev_data->core_wdt[i] = intel_adsp_wdt_pointer_get(dev_config->base, i);
|
|
ret = dw_wdt_probe(dev_data->core_wdt[i], reset_pulse_length);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
#if WDT_INTEL_ADSP_INTERRUPT_SUPPORT
|
|
IRQ_CONNECT(DT_IRQN(DEV_NODE), DT_IRQ(DEV_NODE, priority), intel_adsp_wdt_isr,
|
|
DEVICE_DT_GET(DEV_NODE), DT_IRQ(DEV_NODE, sense));
|
|
irq_enable(DT_IRQN(DEV_NODE));
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Pause watchdog operation
|
|
*
|
|
* Sets the pause signal to stop the watchdog timing
|
|
*
|
|
* @param dev Pointer to the device structure
|
|
* @param channel_id Channel identifier
|
|
*/
|
|
int intel_adsp_watchdog_pause(const struct device *dev, const int channel_id)
|
|
{
|
|
const struct intel_adsp_wdt_dev_cfg *const dev_config = dev->config;
|
|
|
|
if (channel_id >= arch_num_cpus()) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
intel_adsp_wdt_pause(dev_config->base, channel_id);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Resume watchdog operation
|
|
*
|
|
* Clears the pause signal to resume the watchdog timing
|
|
*
|
|
* @param dev Pointer to the device structure
|
|
* @param channel_id Channel identifier
|
|
*/
|
|
int intel_adsp_watchdog_resume(const struct device *dev, const int channel_id)
|
|
{
|
|
const struct intel_adsp_wdt_dev_cfg *const dev_config = dev->config;
|
|
|
|
if (channel_id >= arch_num_cpus()) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
intel_adsp_wdt_resume(dev_config->base, channel_id);
|
|
return 0;
|
|
}
|
|
|
|
int dw_wdt_disable(const struct device *dev)
|
|
{
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static const struct wdt_driver_api intel_adsp_wdt_api = {
|
|
.setup = intel_adsp_wdt_setup,
|
|
.disable = dw_wdt_disable,
|
|
.install_timeout = intel_adsp_wdt_install_timeout,
|
|
.feed = intel_adsp_wdt_feed,
|
|
};
|
|
|
|
#if !(DT_NODE_HAS_PROP(DEV_NODE, clock_frequency) || DT_NODE_HAS_PROP(DEV_NODE, clocks))
|
|
#error Clock frequency not configured!
|
|
#endif
|
|
|
|
static const struct intel_adsp_wdt_dev_cfg wdt_intel_adsp_config = {
|
|
.base = DT_REG_ADDR(DEV_NODE),
|
|
COND_CODE_1(DT_NODE_HAS_PROP(DEV_NODE, clock_frequency),
|
|
(.clk_freq = DT_PROP(DEV_NODE, clock_frequency)),
|
|
(.clk_freq = DT_PROP_BY_PHANDLE(DEV_NODE, clocks, clock_frequency))
|
|
),
|
|
};
|
|
|
|
static struct intel_adsp_wdt_dev_data wdt_intel_adsp_data;
|
|
|
|
DEVICE_DT_DEFINE(DEV_NODE, &intel_adsp_wdt_init, NULL, &wdt_intel_adsp_data, &wdt_intel_adsp_config,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &intel_adsp_wdt_api);
|