zephyr/drivers/watchdog/wdt_intel_adsp.c

242 lines
6.6 KiB
C
Raw Normal View History

drivers: wdt: Add wdt_intel_adsp driver Added a new watchdog driver which can handle a multiple wdt_dw instances and can control the pause signal. The mlt platform has three designware watchdogs, one for each core. I decided to create a separate intel watchdog driver for the following reasons: 1. All three 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. So, it would be assigned to only one device. The other dw watchdog devices would use this assignment, even though it would not be described for them in the dt. The interrupt handler function in dw watchdog checks the interrupt flag. If the interrupt was connected to the first watchdog, and the second or third watchdog signal an interrupt, the interrupt handler of the first device would ignore it because it would not have set the interrupt flag. The watchdog device don't knows anything about the existence of the others devices. 2. The designware watchdog only supports a hardware pause signal. It cannot be paused programmatically. On the mtl platform, there is a separate group of control registers for all per-core watchdogs. There are GPIO-like registers that allows control of a hardware pause signal for subordinate watchdogs. This separate block is shared by all three watchdogs. 3. The base addresses of the subordinate watchdogs are read from the aforementioned control registers. As a result, in the device tree we have only one base address for the intel watchdog, which points to the pause control registers and containing the base addresses of the subordinate devices. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
2023-01-10 09:35:17 +01:00
/* 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++) {
drivers: wdt: Add wdt_intel_adsp driver Added a new watchdog driver which can handle a multiple wdt_dw instances and can control the pause signal. The mlt platform has three designware watchdogs, one for each core. I decided to create a separate intel watchdog driver for the following reasons: 1. All three 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. So, it would be assigned to only one device. The other dw watchdog devices would use this assignment, even though it would not be described for them in the dt. The interrupt handler function in dw watchdog checks the interrupt flag. If the interrupt was connected to the first watchdog, and the second or third watchdog signal an interrupt, the interrupt handler of the first device would ignore it because it would not have set the interrupt flag. The watchdog device don't knows anything about the existence of the others devices. 2. The designware watchdog only supports a hardware pause signal. It cannot be paused programmatically. On the mtl platform, there is a separate group of control registers for all per-core watchdogs. There are GPIO-like registers that allows control of a hardware pause signal for subordinate watchdogs. This separate block is shared by all three watchdogs. 3. The base addresses of the subordinate watchdogs are read from the aforementioned control registers. As a result, in the device tree we have only one base address for the intel watchdog, which points to the pause control registers and containing the base addresses of the subordinate devices. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
2023-01-10 09:35:17 +01:00
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()) {
drivers: wdt: Add wdt_intel_adsp driver Added a new watchdog driver which can handle a multiple wdt_dw instances and can control the pause signal. The mlt platform has three designware watchdogs, one for each core. I decided to create a separate intel watchdog driver for the following reasons: 1. All three 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. So, it would be assigned to only one device. The other dw watchdog devices would use this assignment, even though it would not be described for them in the dt. The interrupt handler function in dw watchdog checks the interrupt flag. If the interrupt was connected to the first watchdog, and the second or third watchdog signal an interrupt, the interrupt handler of the first device would ignore it because it would not have set the interrupt flag. The watchdog device don't knows anything about the existence of the others devices. 2. The designware watchdog only supports a hardware pause signal. It cannot be paused programmatically. On the mtl platform, there is a separate group of control registers for all per-core watchdogs. There are GPIO-like registers that allows control of a hardware pause signal for subordinate watchdogs. This separate block is shared by all three watchdogs. 3. The base addresses of the subordinate watchdogs are read from the aforementioned control registers. As a result, in the device tree we have only one base address for the intel watchdog, which points to the pause control registers and containing the base addresses of the subordinate devices. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
2023-01-10 09:35:17 +01:00
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++) {
drivers: wdt: Add wdt_intel_adsp driver Added a new watchdog driver which can handle a multiple wdt_dw instances and can control the pause signal. The mlt platform has three designware watchdogs, one for each core. I decided to create a separate intel watchdog driver for the following reasons: 1. All three 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. So, it would be assigned to only one device. The other dw watchdog devices would use this assignment, even though it would not be described for them in the dt. The interrupt handler function in dw watchdog checks the interrupt flag. If the interrupt was connected to the first watchdog, and the second or third watchdog signal an interrupt, the interrupt handler of the first device would ignore it because it would not have set the interrupt flag. The watchdog device don't knows anything about the existence of the others devices. 2. The designware watchdog only supports a hardware pause signal. It cannot be paused programmatically. On the mtl platform, there is a separate group of control registers for all per-core watchdogs. There are GPIO-like registers that allows control of a hardware pause signal for subordinate watchdogs. This separate block is shared by all three watchdogs. 3. The base addresses of the subordinate watchdogs are read from the aforementioned control registers. As a result, in the device tree we have only one base address for the intel watchdog, which points to the pause control registers and containing the base addresses of the subordinate devices. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
2023-01-10 09:35:17 +01:00
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()) {
drivers: wdt: Add wdt_intel_adsp driver Added a new watchdog driver which can handle a multiple wdt_dw instances and can control the pause signal. The mlt platform has three designware watchdogs, one for each core. I decided to create a separate intel watchdog driver for the following reasons: 1. All three 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. So, it would be assigned to only one device. The other dw watchdog devices would use this assignment, even though it would not be described for them in the dt. The interrupt handler function in dw watchdog checks the interrupt flag. If the interrupt was connected to the first watchdog, and the second or third watchdog signal an interrupt, the interrupt handler of the first device would ignore it because it would not have set the interrupt flag. The watchdog device don't knows anything about the existence of the others devices. 2. The designware watchdog only supports a hardware pause signal. It cannot be paused programmatically. On the mtl platform, there is a separate group of control registers for all per-core watchdogs. There are GPIO-like registers that allows control of a hardware pause signal for subordinate watchdogs. This separate block is shared by all three watchdogs. 3. The base addresses of the subordinate watchdogs are read from the aforementioned control registers. As a result, in the device tree we have only one base address for the intel watchdog, which points to the pause control registers and containing the base addresses of the subordinate devices. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
2023-01-10 09:35:17 +01:00
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()) {
drivers: wdt: Add wdt_intel_adsp driver Added a new watchdog driver which can handle a multiple wdt_dw instances and can control the pause signal. The mlt platform has three designware watchdogs, one for each core. I decided to create a separate intel watchdog driver for the following reasons: 1. All three 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. So, it would be assigned to only one device. The other dw watchdog devices would use this assignment, even though it would not be described for them in the dt. The interrupt handler function in dw watchdog checks the interrupt flag. If the interrupt was connected to the first watchdog, and the second or third watchdog signal an interrupt, the interrupt handler of the first device would ignore it because it would not have set the interrupt flag. The watchdog device don't knows anything about the existence of the others devices. 2. The designware watchdog only supports a hardware pause signal. It cannot be paused programmatically. On the mtl platform, there is a separate group of control registers for all per-core watchdogs. There are GPIO-like registers that allows control of a hardware pause signal for subordinate watchdogs. This separate block is shared by all three watchdogs. 3. The base addresses of the subordinate watchdogs are read from the aforementioned control registers. As a result, in the device tree we have only one base address for the intel watchdog, which points to the pause control registers and containing the base addresses of the subordinate devices. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
2023-01-10 09:35:17 +01:00
return -EINVAL;
}
intel_adsp_wdt_resume(dev_config->base, channel_id);
return 0;
}
int dw_wdt_disable(const struct device *dev)
{
return -ENOTSUP;
}
drivers: wdt: Add wdt_intel_adsp driver Added a new watchdog driver which can handle a multiple wdt_dw instances and can control the pause signal. The mlt platform has three designware watchdogs, one for each core. I decided to create a separate intel watchdog driver for the following reasons: 1. All three 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. So, it would be assigned to only one device. The other dw watchdog devices would use this assignment, even though it would not be described for them in the dt. The interrupt handler function in dw watchdog checks the interrupt flag. If the interrupt was connected to the first watchdog, and the second or third watchdog signal an interrupt, the interrupt handler of the first device would ignore it because it would not have set the interrupt flag. The watchdog device don't knows anything about the existence of the others devices. 2. The designware watchdog only supports a hardware pause signal. It cannot be paused programmatically. On the mtl platform, there is a separate group of control registers for all per-core watchdogs. There are GPIO-like registers that allows control of a hardware pause signal for subordinate watchdogs. This separate block is shared by all three watchdogs. 3. The base addresses of the subordinate watchdogs are read from the aforementioned control registers. As a result, in the device tree we have only one base address for the intel watchdog, which points to the pause control registers and containing the base addresses of the subordinate devices. Signed-off-by: Adrian Warecki <adrian.warecki@intel.com>
2023-01-10 09:35:17 +01:00
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);