| /* |
| * Copyright (c) 2021 ITE Corporation. All Rights Reserved. |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ite_it8xxx2_watchdog |
| |
| #include <zephyr/drivers/watchdog.h> |
| #include <zephyr/irq.h> |
| #include <errno.h> |
| #include <soc.h> |
| |
| #include <zephyr/logging/log.h> |
| #define LOG_LEVEL CONFIG_WDT_LOG_LEVEL |
| LOG_MODULE_REGISTER(wdt_ite_it8xxx2); |
| |
| #define IT8XXX2_WATCHDOG_MAGIC_BYTE 0x5c |
| #define WARNING_TIMER_PERIOD_MS_TO_1024HZ_COUNT(ms) ((ms) * 1024 / 1000) |
| |
| /* enter critical period or not */ |
| static int wdt_warning_fired; |
| |
| /* device config */ |
| struct wdt_it8xxx2_config { |
| /* wdt register base address */ |
| struct wdt_it8xxx2_regs *base; |
| }; |
| |
| /* driver data */ |
| struct wdt_it8xxx2_data { |
| /* timeout callback used to handle watchdog event */ |
| wdt_callback_t callback; |
| /* indicate whether a watchdog timeout is installed */ |
| bool timeout_installed; |
| /* watchdog feed timeout in milliseconds */ |
| uint32_t timeout; |
| }; |
| |
| static int wdt_it8xxx2_install_timeout(const struct device *dev, |
| const struct wdt_timeout_cfg *config) |
| { |
| const struct wdt_it8xxx2_config *const wdt_config = dev->config; |
| struct wdt_it8xxx2_data *data = dev->data; |
| struct wdt_it8xxx2_regs *const inst = wdt_config->base; |
| |
| /* if watchdog is already running */ |
| if ((inst->ETWCFG) & IT8XXX2_WDT_LEWDCNTL) { |
| return -EBUSY; |
| } |
| |
| /* |
| * Not support lower limit window timeouts (min value must be equal to |
| * 0). Upper limit window timeouts can't be 0 when we install timeout. |
| */ |
| if ((config->window.min != 0) || (config->window.max == 0)) { |
| data->timeout_installed = false; |
| return -EINVAL; |
| } |
| |
| /* save watchdog timeout */ |
| data->timeout = config->window.max; |
| |
| /* install user timeout isr */ |
| data->callback = config->callback; |
| |
| /* mark installed */ |
| data->timeout_installed = true; |
| |
| return 0; |
| } |
| |
| static int wdt_it8xxx2_setup(const struct device *dev, uint8_t options) |
| { |
| const struct wdt_it8xxx2_config *const wdt_config = dev->config; |
| struct wdt_it8xxx2_data *data = dev->data; |
| struct wdt_it8xxx2_regs *const inst = wdt_config->base; |
| uint16_t cnt0 = WARNING_TIMER_PERIOD_MS_TO_1024HZ_COUNT(data->timeout); |
| uint16_t cnt1 = WARNING_TIMER_PERIOD_MS_TO_1024HZ_COUNT((data->timeout |
| + CONFIG_WDT_ITE_WARNING_LEADING_TIME_MS)); |
| |
| /* disable pre-warning timer1 interrupt */ |
| irq_disable(DT_INST_IRQN(0)); |
| |
| if (!data->timeout_installed) { |
| LOG_ERR("No valid WDT timeout installed"); |
| return -EINVAL; |
| } |
| |
| if ((inst->ETWCFG) & IT8XXX2_WDT_LEWDCNTL) { |
| LOG_ERR("WDT is already running"); |
| return -EBUSY; |
| } |
| |
| if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) { |
| LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP is not supported"); |
| return -ENOTSUP; |
| } |
| |
| /* pre-warning timer1 is 16-bit counter down timer */ |
| inst->ET1CNTLHR = (cnt0 >> 8) & 0xff; |
| inst->ET1CNTLLR = cnt0 & 0xff; |
| |
| /* clear pre-warning timer1 interrupt status */ |
| ite_intc_isr_clear(DT_INST_IRQN(0)); |
| |
| /* enable pre-warning timer1 interrupt */ |
| irq_enable(DT_INST_IRQN(0)); |
| |
| /* don't stop watchdog timer counting */ |
| inst->ETWCTRL &= ~IT8XXX2_WDT_EWDSCEN; |
| |
| /* set watchdog timer count */ |
| inst->EWDCNTHR = (cnt1 >> 8) & 0xff; |
| inst->EWDCNTLR = cnt1 & 0xff; |
| |
| /* allow to write timer1 count register */ |
| inst->ETWCFG &= ~IT8XXX2_WDT_LET1CNTL; |
| |
| /* |
| * bit5 = 1: enable key match function to touch watchdog |
| * bit4 = 1: select watchdog clock source from prescaler |
| * bit3 = 1: lock watchdog count register (also mark as watchdog running) |
| * bit1 = 1: lock timer1 prescaler register |
| */ |
| inst->ETWCFG = (IT8XXX2_WDT_EWDKEYEN | |
| IT8XXX2_WDT_EWDSRC | |
| IT8XXX2_WDT_LEWDCNTL | |
| IT8XXX2_WDT_LET1PS); |
| |
| LOG_DBG("WDT Setup and enabled"); |
| |
| return 0; |
| } |
| |
| /* |
| * reload the WDT and pre-warning timer1 counter |
| * |
| * @param dev Pointer to the device structure for the driver instance. |
| * @param channel_id Index of the fed channel, and we only support |
| * channel_id = 0 now. |
| */ |
| static int wdt_it8xxx2_feed(const struct device *dev, int channel_id) |
| { |
| const struct wdt_it8xxx2_config *const wdt_config = dev->config; |
| struct wdt_it8xxx2_data *data = dev->data; |
| struct wdt_it8xxx2_regs *const inst = wdt_config->base; |
| uint16_t cnt0 = WARNING_TIMER_PERIOD_MS_TO_1024HZ_COUNT(data->timeout); |
| |
| ARG_UNUSED(channel_id); |
| |
| /* reset pre-warning timer1 */ |
| inst->ETWCTRL |= IT8XXX2_WDT_ET1RST; |
| |
| /* restart watchdog timer */ |
| inst->EWDKEYR = IT8XXX2_WATCHDOG_MAGIC_BYTE; |
| |
| /* reset pre-warning timer1 to default if time is touched */ |
| if (wdt_warning_fired) { |
| wdt_warning_fired = 0; |
| |
| /* pre-warning timer1 is 16-bit counter down timer */ |
| inst->ET1CNTLHR = (cnt0 >> 8) & 0xff; |
| inst->ET1CNTLLR = cnt0 & 0xff; |
| |
| /* clear timer1 interrupt status */ |
| ite_intc_isr_clear(DT_INST_IRQN(0)); |
| |
| /* enable timer1 interrupt */ |
| irq_enable(DT_INST_IRQN(0)); |
| } |
| |
| LOG_DBG("WDT Kicking"); |
| |
| return 0; |
| } |
| |
| static int wdt_it8xxx2_disable(const struct device *dev) |
| { |
| const struct wdt_it8xxx2_config *const wdt_config = dev->config; |
| struct wdt_it8xxx2_data *data = dev->data; |
| struct wdt_it8xxx2_regs *const inst = wdt_config->base; |
| |
| /* stop watchdog timer counting */ |
| inst->ETWCTRL |= IT8XXX2_WDT_EWDSCEN; |
| |
| /* unlock watchdog count register (also mark as watchdog not running) */ |
| inst->ETWCFG &= ~IT8XXX2_WDT_LEWDCNTL; |
| |
| /* disable pre-warning timer1 interrupt */ |
| irq_disable(DT_INST_IRQN(0)); |
| |
| /* mark uninstalled */ |
| data->timeout_installed = false; |
| |
| LOG_DBG("WDT Disabled"); |
| |
| return 0; |
| } |
| |
| static void wdt_it8xxx2_isr(const struct device *dev) |
| { |
| const struct wdt_it8xxx2_config *const wdt_config = dev->config; |
| struct wdt_it8xxx2_data *data = dev->data; |
| struct wdt_it8xxx2_regs *const inst = wdt_config->base; |
| |
| /* clear pre-warning timer1 interrupt status */ |
| ite_intc_isr_clear(DT_INST_IRQN(0)); |
| |
| /* reset pre-warning timer1 */ |
| inst->ETWCTRL |= IT8XXX2_WDT_ET1RST; |
| |
| /* callback function, ex. print warning message */ |
| if (data->callback) { |
| data->callback(dev, 0); |
| } |
| |
| if (IS_ENABLED(CONFIG_WDT_ITE_REDUCE_WARNING_LEADING_TIME)) { |
| /* |
| * Once warning timer triggered: if watchdog timer isn't reloaded, |
| * then we will reduce interval of warning timer to 30ms to print |
| * more warning messages before watchdog reset. |
| */ |
| if (!wdt_warning_fired) { |
| uint16_t cnt0 = WARNING_TIMER_PERIOD_MS_TO_1024HZ_COUNT(30); |
| |
| /* pre-warning timer1 is 16-bit counter down timer */ |
| inst->ET1CNTLHR = (cnt0 >> 8) & 0xff; |
| inst->ET1CNTLLR = cnt0 & 0xff; |
| |
| /* clear pre-warning timer1 interrupt status */ |
| ite_intc_isr_clear(DT_INST_IRQN(0)); |
| } |
| } |
| wdt_warning_fired++; |
| |
| LOG_DBG("WDT ISR"); |
| } |
| |
| static const struct wdt_driver_api wdt_it8xxx2_api = { |
| .setup = wdt_it8xxx2_setup, |
| .disable = wdt_it8xxx2_disable, |
| .install_timeout = wdt_it8xxx2_install_timeout, |
| .feed = wdt_it8xxx2_feed, |
| }; |
| |
| static int wdt_it8xxx2_init(const struct device *dev) |
| { |
| const struct wdt_it8xxx2_config *const wdt_config = dev->config; |
| struct wdt_it8xxx2_regs *const inst = wdt_config->base; |
| |
| if (IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) { |
| wdt_it8xxx2_disable(dev); |
| } |
| |
| /* unlock access to watchdog registers */ |
| inst->ETWCFG = 0x00; |
| |
| /* set WDT and timer1 to use 1.024kHz clock */ |
| inst->ET1PSR = IT8XXX2_WDT_ETPS_1P024_KHZ; |
| |
| /* set WDT key match enabled and WDT clock to use ET1PSR */ |
| inst->ETWCFG = (IT8XXX2_WDT_EWDKEYEN | |
| IT8XXX2_WDT_EWDSRC); |
| |
| /* |
| * select the mode that watchdog can be stopped, this is needed for |
| * wdt_it8xxx2_disable() api and WDT_OPT_PAUSE_HALTED_BY_DBG flag |
| */ |
| inst->ETWCTRL |= IT8XXX2_WDT_EWDSCMS; |
| |
| IRQ_CONNECT(DT_INST_IRQN(0), 0, wdt_it8xxx2_isr, |
| DEVICE_DT_INST_GET(0), 0); |
| return 0; |
| } |
| |
| static const struct wdt_it8xxx2_config wdt_it8xxx2_cfg_0 = { |
| .base = (struct wdt_it8xxx2_regs *)DT_INST_REG_ADDR(0), |
| }; |
| |
| static struct wdt_it8xxx2_data wdt_it8xxx2_dev_data; |
| |
| DEVICE_DT_INST_DEFINE(0, wdt_it8xxx2_init, NULL, |
| &wdt_it8xxx2_dev_data, &wdt_it8xxx2_cfg_0, |
| PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, |
| &wdt_it8xxx2_api); |