diff --git a/components/drivers/watchdog/Kconfig b/components/drivers/watchdog/Kconfig index b70d465dfd3..bc85a68bf83 100644 --- a/components/drivers/watchdog/Kconfig +++ b/components/drivers/watchdog/Kconfig @@ -1,3 +1,20 @@ -config RT_USING_WDT +menuconfig RT_USING_WDT bool "Using Watch Dog device drivers" default n + +config RT_WDT_DW + bool "Synopsys DesignWare watchdog" + depends on RT_USING_DM + depends on RT_USING_WDT + depends on RT_USING_RESET + default n + +config RT_WDT_I6300ESB + bool "Intel 6300ESB Timer/Watchdog" + depends on RT_USING_DM + depends on RT_USING_WDT + depends on RT_USING_PCI + +if RT_USING_DM && RT_USING_WDT + osource "$(SOC_DM_WDT_DIR)/Kconfig" +endif diff --git a/components/drivers/watchdog/SConscript b/components/drivers/watchdog/SConscript index 38934d3c417..93740cde3d8 100644 --- a/components/drivers/watchdog/SConscript +++ b/components/drivers/watchdog/SConscript @@ -1,8 +1,22 @@ from building import * -cwd = GetCurrentDir() -src = Glob('*.c') +group = [] + +if not GetDepend(['RT_USING_WDT']): + Return('group') + +cwd = GetCurrentDir() +list = os.listdir(cwd) CPPPATH = [cwd + '/../include'] -group = DefineGroup('DeviceDrivers', src, depend = ['RT_USING_WDT'], CPPPATH = CPPPATH) + +src = ['dev_watchdog.c'] + +if GetDepend(['RT_WDT_DW']): + src += ['watchdog-dw.c'] + +if GetDepend(['RT_WDT_I6300ESB']): + src += ['watchdog-i6300esb.c'] + +group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH) Return('group') diff --git a/components/drivers/watchdog/watchdog-dw.c b/components/drivers/watchdog/watchdog-dw.c new file mode 100644 index 00000000000..77bd047387c --- /dev/null +++ b/components/drivers/watchdog/watchdog-dw.c @@ -0,0 +1,621 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-3-08 GuEe-GUI the first version + */ + +#include +#include +#include + +#define WDOG_CONTROL_REG_OFFSET 0x00 +#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01 +#define WDOG_CONTROL_REG_RESP_MODE_MASK 0x02 +#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04 +#define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT 4 +#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08 +#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c +#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76 +#define WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10 +#define WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14 +#define WDOG_COMP_PARAMS_5_REG_OFFSET 0xe4 +#define WDOG_COMP_PARAMS_4_REG_OFFSET 0xe8 +#define WDOG_COMP_PARAMS_3_REG_OFFSET 0xec +#define WDOG_COMP_PARAMS_2_REG_OFFSET 0xf0 +#define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4 +#define WDOG_COMP_PARAMS_1_USE_FIX_TOP (1 << 6) +#define WDOG_COMP_VERSION_REG_OFFSET 0xf8 +#define WDOG_COMP_TYPE_REG_OFFSET 0xfc + +/* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */ +#define DW_WDT_NUM_TOPS 16 +#define DW_WDT_FIX_TOP(idx) (1U << (16 + idx)) +#define DW_WDT_DEFAULT_SECONDS 30 + +#define MSEC_PER_SEC 1000L + +enum dw_wdt_rmod +{ + DW_WDT_RMOD_RESET = 1, + DW_WDT_RMOD_IRQ +}; + +struct dw_wdt_timeout +{ + rt_uint32_t top_val; + rt_uint32_t sec; + rt_uint32_t msec; +}; + +struct dw_wdt +{ + rt_watchdog_t parent; + + void *base; + int irq; + struct rt_clk *clk; + struct rt_reset_control *rstc; + + rt_ubase_t rate; + enum dw_wdt_rmod rmod; + struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS]; + /* Save/Restore */ + rt_uint32_t user; + rt_uint32_t timeout; + rt_uint32_t pretimeout; + rt_uint32_t max_hw_heartbeat_ms; + + struct rt_device_notify pretimeout_notify; +}; + +#define raw_to_dw_wdt(raw) rt_container_of(raw, struct dw_wdt, parent) + +static const rt_uint32_t dw_wdt_fix_tops[DW_WDT_NUM_TOPS] = +{ + DW_WDT_FIX_TOP(0), DW_WDT_FIX_TOP(1), DW_WDT_FIX_TOP(2), DW_WDT_FIX_TOP(3), + DW_WDT_FIX_TOP(4), DW_WDT_FIX_TOP(5), DW_WDT_FIX_TOP(6), DW_WDT_FIX_TOP(7), + DW_WDT_FIX_TOP(8), DW_WDT_FIX_TOP(9), DW_WDT_FIX_TOP(10), DW_WDT_FIX_TOP(11), + DW_WDT_FIX_TOP(12), DW_WDT_FIX_TOP(13), DW_WDT_FIX_TOP(14), DW_WDT_FIX_TOP(15) +}; + +rt_inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt) +{ + return HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) & WDOG_CONTROL_REG_WDT_EN_MASK; +} + +static void dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod) +{ + rt_uint32_t val = HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET); + + if (rmod == DW_WDT_RMOD_IRQ) + { + val |= WDOG_CONTROL_REG_RESP_MODE_MASK; + } + else + { + val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK; + } + + HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) = val; + + dw_wdt->rmod = rmod; +} + +static rt_uint32_t dw_wdt_find_best_top(struct dw_wdt *dw_wdt, rt_uint32_t timeout, rt_uint32_t *top_val) +{ + int idx; + + /* + * Find a TOP with timeout greater or equal to the requested number. Note + * we'll select a TOP with maximum timeout if the requested timeout couldn't + * be reached. + */ + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) + { + if (dw_wdt->timeouts[idx].sec >= timeout) + { + break; + } + } + + if (idx == DW_WDT_NUM_TOPS) + { + --idx; + } + + *top_val = dw_wdt->timeouts[idx].top_val; + + return dw_wdt->timeouts[idx].sec; +} + +static rt_uint32_t dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt) +{ + rt_uint64_t msec; + struct dw_wdt_timeout *timeout = &dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1]; + + msec = (rt_uint64_t)timeout->sec * MSEC_PER_SEC + timeout->msec; + + return msec < RT_UINT32_MAX ? msec : RT_UINT32_MAX; +} + +static rt_uint32_t dw_wdt_get_timeout(struct dw_wdt *dw_wdt) +{ + int idx; + int top_val = HWREG32(dw_wdt->base + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; + + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) + { + if (dw_wdt->timeouts[idx].top_val == top_val) + { + break; + } + } + + /* + * In IRQ mode due to the two stages counter, the actual timeout is twice + * greater than the TOP setting. + */ + return dw_wdt->timeouts[idx].sec * dw_wdt->rmod; +} + +static int dw_wdt_keep_alive(struct dw_wdt *dw_wdt) +{ + HWREG32(dw_wdt->base + WDOG_COUNTER_RESTART_REG_OFFSET) = WDOG_COUNTER_RESTART_KICK_VALUE; + + return 0; +} + +static void dw_wdt_set_timeout(struct dw_wdt *dw_wdt, rt_uint32_t top_s) +{ + rt_uint32_t top_val; + rt_uint32_t timeout; + + /* + * Note IRQ mode being enabled means having a non-zero pre-timeout setup. + * In this case we try to find a TOP as close to the half of the requested + * timeout as possible since DW Watchdog IRQ mode is designed in two stages + * way - first timeout rises the pre-timeout interrupt, second timeout + * performs the system reset. So basically the effective watchdog-caused + * reset happens after two watchdog TOPs elapsed. + */ + timeout = dw_wdt_find_best_top(dw_wdt, RT_DIV_ROUND_UP(top_s, dw_wdt->rmod), &top_val); + + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) + { + dw_wdt->pretimeout = timeout; + } + else + { + dw_wdt->pretimeout = 0; + } + + /* + * Set the new value in the watchdog. Some versions of dw_wdt have have + * TOPINIT in the TIMEOUT_RANGE register (as per CP_WDT_DUAL_TOP in + * WDT_COMP_PARAMS_1). On those we effectively get a pat of the watchdog + * right here. + */ + HWREG32(dw_wdt->base + WDOG_TIMEOUT_RANGE_REG_OFFSET) = top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT; + + dw_wdt_keep_alive(dw_wdt); + + /* + * In case users set bigger timeout value than HW can support, + * kernel(watchdog_dev.c) helps to feed watchdog before wdd->max_hw_heartbeat_ms + */ + if (top_s * 1000 <= dw_wdt->max_hw_heartbeat_ms) + { + dw_wdt->timeout = timeout * dw_wdt->rmod; + } + else + { + dw_wdt->timeout = top_s; + } +} + +static void dw_wdt_set_pretimeout(struct dw_wdt *dw_wdt, rt_uint32_t req) +{ + /* + * We ignore actual value of the timeout passed from user-space using it as + * a flag whether the pretimeout functionality is intended to be activated. + */ + dw_wdt_update_mode(dw_wdt, req ? DW_WDT_RMOD_IRQ : DW_WDT_RMOD_RESET); + dw_wdt_set_timeout(dw_wdt, dw_wdt->timeout); +} + +static void dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt) +{ + rt_uint32_t val = HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET); + + /* Disable/enable interrupt mode depending on the RMOD flag. */ + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) + { + val |= WDOG_CONTROL_REG_RESP_MODE_MASK; + } + else + { + val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK; + } + + /* Enable watchdog. */ + HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) = val | WDOG_CONTROL_REG_WDT_EN_MASK; +} + +static int dw_wdt_start(struct dw_wdt *dw_wdt) +{ + rt_clk_enable(dw_wdt->clk); + + dw_wdt_set_timeout(dw_wdt, dw_wdt->timeout); + dw_wdt_keep_alive(dw_wdt); + dw_wdt_arm_system_reset(dw_wdt); + + return 0; +} + +static int dw_wdt_stop(struct dw_wdt *dw_wdt) +{ + /* + * The DesignWare watchdog cannot be stopped once it has been started so we + * do not implement a stop function. The watchdog core will continue to send + * heartbeat requests after the watchdog device has been closed. + */ + rt_clk_disable(dw_wdt->clk); + + rt_reset_control_assert(dw_wdt->rstc); + rt_reset_control_deassert(dw_wdt->rstc); + + return 0; +} + +static rt_uint32_t dw_wdt_get_timeleft(struct dw_wdt *dw_wdt) +{ + rt_uint32_t val, sec; + + val = HWREG32(dw_wdt->base + WDOG_CURRENT_COUNT_REG_OFFSET); + sec = val / dw_wdt->rate; + + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) + { + val = HWREG32(dw_wdt->base + WDOG_INTERRUPT_STATUS_REG_OFFSET); + + if (!val) + { + sec += dw_wdt->pretimeout; + } + } + + return sec; +} + +static rt_err_t dw_wdt_init_timeouts(struct dw_wdt *dw_wdt) +{ + int val, tidx; + rt_uint64_t msec; + struct dw_wdt_timeout tout, *dst; + const rt_uint32_t *tops = dw_wdt_fix_tops; + + /* + * Convert the specified TOPs into an array of watchdog timeouts. We walk + * over the passed TOPs array and calculate corresponding timeouts in + * seconds and milliseconds. The milliseconds granularity is needed to + * distinguish the TOPs with very close timeouts and to set the watchdog max + * heartbeat setting further. + */ + for (val = 0; val < DW_WDT_NUM_TOPS; ++val) + { + tout.top_val = val; + tout.sec = tops[val] / dw_wdt->rate; + msec = (rt_uint64_t)tops[val] * MSEC_PER_SEC; + rt_do_div(msec, dw_wdt->rate); + tout.msec = msec - ((rt_uint64_t)tout.sec * MSEC_PER_SEC); + + /* + * Find a suitable place for the current TOP in the timeouts array so + * that the list is remained in the ascending order. + */ + for (tidx = 0; tidx < val; ++tidx) + { + dst = &dw_wdt->timeouts[tidx]; + + if (tout.sec > dst->sec || (tout.sec == dst->sec && tout.msec >= dst->msec)) + { + continue; + } + else + { + struct dw_wdt_timeout tmp = *dst; + + *dst = tout; + tout = tmp; + } + } + + dw_wdt->timeouts[val] = tout; + } + + if (!dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1].sec) + { + return -RT_ENOSYS; + } + + return RT_EOK; +} + +static rt_err_t dw_wdt_init(rt_watchdog_t *wdt) +{ + rt_err_t status = RT_EOK; + struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt); + + /* Enable normal reset without pre-timeout by default. */ + dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET); + + if (dw_wdt_init_timeouts(dw_wdt)) + { + return -RT_ERROR; + } + + dw_wdt->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt); + + /* + * If the watchdog is already running, use its already configured timeout. + * Otherwise use the default or the value provided through devicetree. + */ + if (dw_wdt_is_enabled(dw_wdt)) + { + dw_wdt->timeout = dw_wdt_get_timeout(dw_wdt); + } + else + { + dw_wdt->timeout = DW_WDT_DEFAULT_SECONDS; + } + + return status; +} + +static rt_err_t dw_wdt_control(rt_watchdog_t *wdt, int cmd, void *args) +{ + rt_err_t status = RT_EOK; + struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt); + + switch (cmd) + { + case RT_DEVICE_CTRL_WDT_GET_TIMEOUT: + *(rt_uint32_t *)args = dw_wdt_get_timeout(dw_wdt); + break; + + case RT_DEVICE_CTRL_WDT_SET_TIMEOUT: + dw_wdt_set_timeout(dw_wdt, *(rt_uint32_t *)args); + break; + + case RT_DEVICE_CTRL_WDT_GET_TIMELEFT: + *(rt_uint32_t *)args = dw_wdt_get_timeleft(dw_wdt); + break; + + case RT_DEVICE_CTRL_WDT_KEEPALIVE: + dw_wdt_set_pretimeout(dw_wdt, dw_wdt->pretimeout); + dw_wdt_keep_alive(dw_wdt); + break; + + case RT_DEVICE_CTRL_WDT_START: + dw_wdt_start(dw_wdt); + dw_wdt->user++; + break; + + case RT_DEVICE_CTRL_WDT_STOP: + dw_wdt_stop(dw_wdt); + dw_wdt->user--; + break; + + case RT_DEVICE_CTRL_NOTIFY_SET: + rt_hw_interrupt_mask(dw_wdt->irq); + if (args) + { + rt_memcpy(&dw_wdt->pretimeout_notify, args, sizeof(dw_wdt->pretimeout_notify)); + } + else + { + rt_memset(&dw_wdt->pretimeout_notify, 0, sizeof(dw_wdt->pretimeout_notify)); + } + rt_hw_interrupt_umask(dw_wdt->irq); + break; + + default: + status = -RT_EINVAL; + } + + return status; +} + +static const struct rt_watchdog_ops dw_wdt_ops = +{ + .init = dw_wdt_init, + .control = dw_wdt_control, +}; + +#ifdef RT_USING_PM +static rt_err_t dw_wdt_pm_suspend(const struct rt_device *device, rt_uint8_t mode) +{ + rt_watchdog_t *wdt = rt_container_of(device, rt_watchdog_t, parent); + struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt); + + dw_wdt->timeout = dw_wdt_get_timeleft(dw_wdt) / dw_wdt->rate; + dw_wdt_stop(dw_wdt); + + return RT_EOK; +} + +static void dw_wdt_pm_resume(const struct rt_device *device, rt_uint8_t mode) +{ + rt_watchdog_t *wdt = rt_container_of(device, rt_watchdog_t, parent); + struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt); + + if (!dw_wdt->user) + { + return; + } + + if (!dw_wdt_init(wdt)) + { + dw_wdt_start(dw_wdt); + } +} + +static const struct rt_device_pm_ops dw_wdt_pm_ops = +{ + .suspend = dw_wdt_pm_suspend, + .resume = dw_wdt_pm_resume, +}; +#endif /* RT_USING_PM */ + +static void dw_wdt_isr(int irqno, void *param) +{ + struct dw_wdt *wdt = (struct dw_wdt *)param; + + if (!HWREG32(wdt->base + WDOG_INTERRUPT_STATUS_REG_OFFSET)) + { + return; + } + + /* Clear the IRQ status (EOI) */ + (void)HWREG32(wdt->base + WDOG_INTERRUPT_CLEAR_REG_OFFSET); + + if (wdt->pretimeout_notify.notify) + { + wdt->pretimeout_notify.notify(wdt->pretimeout_notify.dev); + } +} + +static void dw_wdt_free(struct dw_wdt *dw_wdt) +{ + if (dw_wdt->base) + { + rt_iounmap(dw_wdt->base); + } + + if (!rt_is_err_or_null(dw_wdt->clk)) + { + rt_clk_disable(dw_wdt->clk); + } + + if (!rt_is_err_or_null(dw_wdt->rstc)) + { + rt_reset_control_assert(dw_wdt->rstc); + rt_reset_control_put(dw_wdt->rstc); + } + + rt_free(dw_wdt); +} + +static rt_err_t dw_wdt_probe(struct rt_platform_device *pdev) +{ + rt_err_t err = RT_EOK; + const char *dev_name; + struct rt_device *dev = &pdev->parent; + struct dw_wdt *dw_wdt = rt_calloc(1, sizeof(*dw_wdt)); + + if (!dw_wdt) + { + return -RT_ENOMEM; + } + + dw_wdt->base = rt_dm_dev_iomap(dev, 0); + + if (!dw_wdt->base) + { + err = -RT_EIO; + goto _free_res; + } + + dw_wdt->irq = rt_dm_dev_get_irq(dev, 0); + + if (dw_wdt->irq < 0) + { + err = dw_wdt->irq; + goto _free_res; + } + + dw_wdt->clk = rt_clk_get_by_name(dev, "pclk"); + + if (rt_is_err(dw_wdt->clk)) + { + dw_wdt->clk = rt_clk_get_by_index(dev, 0); + + if (rt_is_err(dw_wdt->clk)) + { + err = rt_ptr_err(dw_wdt->clk); + goto _free_res; + } + } + + dw_wdt->rstc = rt_reset_control_get_by_index(dev, 0); + + if (rt_is_err(dw_wdt->rstc)) + { + err = rt_ptr_err(dw_wdt->rstc); + goto _free_res; + } + + rt_reset_control_deassert(dw_wdt->rstc); + + dev->user_data = dw_wdt; + + dw_wdt->rate = rt_clk_get_rate(dw_wdt->clk); + dw_wdt->parent.ops = &dw_wdt_ops; + + rt_dm_dev_set_name_auto(&dw_wdt->parent.parent, "wdt"); + dev_name = rt_dm_dev_get_name(&dw_wdt->parent.parent); + + rt_hw_interrupt_install(dw_wdt->irq, dw_wdt_isr, dw_wdt, dev_name); + rt_hw_interrupt_umask(dw_wdt->irq); + +#ifdef RT_USING_PM + rt_pm_device_register(&dw_wdt->parent.parent, &dw_wdt_pm_ops); +#endif + + rt_hw_watchdog_register(&dw_wdt->parent, dev_name, 0, dw_wdt); + + return RT_EOK; + +_free_res: + dw_wdt_free(dw_wdt); + + return err; +} + +static rt_err_t dw_wdt_remove(struct rt_platform_device *pdev) +{ + struct dw_wdt *dw_wdt = pdev->parent.user_data; + + rt_hw_interrupt_mask(dw_wdt->irq); + rt_pic_detach_irq(dw_wdt->irq, dw_wdt); + +#ifdef RT_USING_PM + rt_pm_device_unregister(&dw_wdt->parent.parent); +#endif + + rt_device_unregister(&dw_wdt->parent.parent); + + dw_wdt_free(dw_wdt); + + return RT_EOK; +} + +static const struct rt_ofw_node_id dw_wdt_ofw_ids[] = +{ + { .compatible = "snps,dw-wdt" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver dw_wdt_driver = +{ + .name = "dw-wdt", + .ids = dw_wdt_ofw_ids, + + .probe = dw_wdt_probe, + .remove = dw_wdt_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(dw_wdt_driver); diff --git a/components/drivers/watchdog/watchdog-i6300esb.c b/components/drivers/watchdog/watchdog-i6300esb.c new file mode 100644 index 00000000000..6a792820b23 --- /dev/null +++ b/components/drivers/watchdog/watchdog-i6300esb.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2006-2022, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2022-11-26 GuEe-GUI first version + */ + +#include +#include + +#define DBG_TAG "wdt.i6300esb" +#define DBG_LVL DBG_INFO +#include + +#define I6300ESB_REG_BAR 0 + +/* PCI configuration registers */ +#define ESB_CONFIG_PCI_REG 0x60 /* Config register */ +#define ESB_LOCK_PCI_REG 0x68 /* WDT lock register */ + +/* Memory mapped registers */ +#define ESB_TIMER1_REG 0x00 /* Timer1 value after each reset */ +#define ESB_TIMER2_REG 0x04 /* Timer2 value after each reset */ +#define ESB_GINTSR_REG 0x08 /* General Interrupt Status Reg */ +#define ESB_RELOAD_REG 0x0c /* Reload register */ + +/* Lock register bits */ +#define ESB_WDT_FUNC (0x01 << 2) /* Watchdog functionality */ +#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */ +#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */ + +/* Config register bits */ +#define ESB_WDT_REBOOT (0x01 << 5) /* Enable reboot on timeout */ +#define ESB_WDT_FREQ (0x01 << 2) /* Decrement frequency */ +#define ESB_WDT_INTTYPE (0x03 << 0) /* Interrupt type on timer1 timeout */ + +/* Reload register bits */ +#define ESB_WDT_TIMEOUT (0x01 << 9) /* Watchdog timed out */ +#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */ + +/* Magic constants */ +#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ +#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */ + +/* 30 sec default heartbeat (1 < heartbeat < 2*1023) */ +#define ESB_HEARTBEAT_MIN 1 +#define ESB_HEARTBEAT_MAX 2046 +#define ESB_HEARTBEAT_DEFAULT 30 + +struct i6300esb_wdt +{ + rt_watchdog_t parent; + + void *regs; + rt_uint32_t timeout; + struct rt_pci_device *pdev; +}; + +#define raw_to_i6300esb_wdt(raw) rt_container_of(raw, struct i6300esb_wdt, parent) + +/* + * Prepare for reloading the timer by unlocking the proper registers. + * This is performed by first writing 0x80 followed by 0x86 to the + * reload register. After this the appropriate registers can be written + * to once before they need to be unlocked again. + */ +rt_inline void i6300esb_wdt_unlock_registers(struct i6300esb_wdt *esb) +{ + HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_UNLOCK1; + HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_UNLOCK2; +} + +static rt_uint32_t i6300esb_timer_start(struct i6300esb_wdt *esb) +{ + i6300esb_wdt_unlock_registers(esb); + HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD; + + rt_pci_write_config_u8(esb->pdev, ESB_LOCK_PCI_REG, ESB_WDT_ENABLE); + + return RT_EOK; +} + +static rt_uint32_t i6300esb_timer_stop(struct i6300esb_wdt *esb) +{ + rt_uint8_t val; + + /* First, reset timers as suggested by the docs */ + i6300esb_wdt_unlock_registers(esb); + HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD; + + /* Then disable the WDT */ + rt_pci_write_config_u8(esb->pdev, ESB_LOCK_PCI_REG, 0x0); + rt_pci_read_config_u8(esb->pdev, ESB_LOCK_PCI_REG, &val); + + /* Returns 0 if the timer was disabled, non-zero otherwise */ + return val & ESB_WDT_ENABLE; +} + +static rt_err_t esb_timer_keepalive(struct i6300esb_wdt *esb) +{ + i6300esb_wdt_unlock_registers(esb); + HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD; + + return RT_EOK; +} + +static rt_err_t i6300esb_timer_set_heartbeat(struct i6300esb_wdt *esb, rt_uint32_t time) +{ + rt_uint32_t val; + + /* + * We shift by 9, so if we are passed a value of 1 sec, + * val will be 1 << 9 = 512, then write that to two + * timers => 2 * 512 = 1024 (which is decremented at 1KHz) + */ + val = time << 9; + + /* Write timer 1 */ + i6300esb_wdt_unlock_registers(esb); + HWREG32(esb->regs + ESB_TIMER1_REG) = val; + + /* Write timer 2 */ + i6300esb_wdt_unlock_registers(esb); + HWREG32(esb->regs + ESB_TIMER2_REG) = val; + + /* Reload */ + i6300esb_wdt_unlock_registers(esb); + HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_RELOAD; + + esb->timeout = time; + + return RT_EOK; +} + +static rt_err_t i6300esb_wdt_init(rt_watchdog_t *wdt) +{ + return RT_EOK; +} + +static rt_err_t i6300esb_wdt_control(rt_watchdog_t *wdt, int cmd, void *args) +{ + rt_err_t err = RT_EOK; + struct i6300esb_wdt *esb = raw_to_i6300esb_wdt(wdt); + + switch (cmd) + { + case RT_DEVICE_CTRL_WDT_GET_TIMEOUT: + *(rt_uint32_t *)args = esb->timeout; + break; + + case RT_DEVICE_CTRL_WDT_SET_TIMEOUT: + err = i6300esb_timer_set_heartbeat(esb, *(rt_uint32_t *)args); + break; + + case RT_DEVICE_CTRL_WDT_KEEPALIVE: + err = esb_timer_keepalive(esb); + break; + + case RT_DEVICE_CTRL_WDT_START: + err = i6300esb_timer_start(esb); + break; + + case RT_DEVICE_CTRL_WDT_STOP: + err = i6300esb_timer_stop(esb); + break; + + default: + err = -RT_EINVAL; + } + + return err; +} + +static const struct rt_watchdog_ops i6300esb_wdt_ops = +{ + .init = i6300esb_wdt_init, + .control = i6300esb_wdt_control, +}; + +static rt_err_t i6300esb_wdt_probe(struct rt_pci_device *pdev) +{ + rt_err_t err; + rt_uint8_t val1; + rt_uint16_t val2; + const char *dev_name; + struct i6300esb_wdt *esb = rt_calloc(1, sizeof(*esb)); + + if (!esb) + { + return -RT_ENOMEM; + } + + esb->regs = rt_pci_iomap(pdev, I6300ESB_REG_BAR); + + if (!esb->regs) + { + err = -RT_EIO; + + goto _fail; + } + + /* + * Config register: + * Bit 5 : 0 = Enable WDT_OUTPUT + * Bit 2 : 0 = set the timer frequency to the PCI clock + * divided by 2^15 (approx 1KHz). + * Bits 1:0 : 11 = WDT_INT_TYPE Disabled. + * The watchdog has two timers, it can be setup so that the expiry of timer1 + * results in an interrupt and the expiry of timer2 results in a reboot. + * We set it to not generate any interrupts as there is not much + * we can do with it right now. + */ + rt_pci_write_config_u16(pdev, ESB_CONFIG_PCI_REG, 0x0003); + + /* Check that the WDT isn't already locked */ + rt_pci_read_config_u8(pdev, ESB_LOCK_PCI_REG, &val1); + if (val1 & ESB_WDT_LOCK) + { + LOG_W("Nowayout already set"); + } + + /* Set the timer to watchdog mode and disable it for now */ + rt_pci_write_config_u8(pdev, ESB_LOCK_PCI_REG, 0x00); + + /* Check if the watchdog was previously triggered */ + i6300esb_wdt_unlock_registers(esb); + val2 = HWREG16(esb->regs + ESB_RELOAD_REG); + if (val2 & ESB_WDT_TIMEOUT) + { + LOG_D("Card previously reset the CPU"); + } + + /* Reset WDT_TIMEOUT flag and timers */ + i6300esb_wdt_unlock_registers(esb); + HWREG16(esb->regs + ESB_RELOAD_REG) = ESB_WDT_TIMEOUT | ESB_WDT_RELOAD; + + /* And set the correct timeout value */ + i6300esb_timer_set_heartbeat(esb, ESB_HEARTBEAT_DEFAULT); + + pdev->parent.user_data = esb; + + esb->pdev = pdev; + esb->parent.ops = &i6300esb_wdt_ops; + + rt_dm_dev_set_name_auto(&esb->parent.parent, "wdt"); + dev_name = rt_dm_dev_get_name(&esb->parent.parent); + rt_hw_watchdog_register(&esb->parent, dev_name, 0, esb); + + return RT_EOK; + +_fail: + if (esb->regs) + { + rt_iounmap(esb->regs); + } + + rt_free(esb); + + return err; +} + +static rt_err_t i6300esb_wdt_remove(struct rt_pci_device *pdev) +{ + struct i6300esb_wdt *esb = pdev->parent.user_data; + + i6300esb_timer_stop(esb); + + rt_device_unregister(&esb->parent.parent); + + rt_iounmap(esb->regs); + rt_free(esb); + + return RT_EOK; +} + +static const struct rt_pci_device_id i6300esb_wdt_pci_ids[] = +{ + { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_INTEL, 0x25ab), }, + { /* sentinel */ } +}; + +static struct rt_pci_driver i6300esb_wdt_driver = +{ + .name = "i6300esb-wdt", + + .ids = i6300esb_wdt_pci_ids, + .probe = i6300esb_wdt_probe, + .remove = i6300esb_wdt_remove, +}; +RT_PCI_DRIVER_EXPORT(i6300esb_wdt_driver);