/* * Copyright (c) 2021 ITE Corporation. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT ite_it8xxx2_watchdog #include #include #include #include #include #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);