| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621 |
- /*
- * 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 <rthw.h>
- #include <rtthread.h>
- #include <rtdevice.h>
- #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);
|