watchdog-dw.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. /*
  2. * Copyright (c) 2006-2022, RT-Thread Development Team
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. *
  6. * Change Logs:
  7. * Date Author Notes
  8. * 2022-3-08 GuEe-GUI the first version
  9. */
  10. #include <rthw.h>
  11. #include <rtthread.h>
  12. #include <rtdevice.h>
  13. #define WDOG_CONTROL_REG_OFFSET 0x00
  14. #define WDOG_CONTROL_REG_WDT_EN_MASK 0x01
  15. #define WDOG_CONTROL_REG_RESP_MODE_MASK 0x02
  16. #define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04
  17. #define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT 4
  18. #define WDOG_CURRENT_COUNT_REG_OFFSET 0x08
  19. #define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c
  20. #define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
  21. #define WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10
  22. #define WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14
  23. #define WDOG_COMP_PARAMS_5_REG_OFFSET 0xe4
  24. #define WDOG_COMP_PARAMS_4_REG_OFFSET 0xe8
  25. #define WDOG_COMP_PARAMS_3_REG_OFFSET 0xec
  26. #define WDOG_COMP_PARAMS_2_REG_OFFSET 0xf0
  27. #define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4
  28. #define WDOG_COMP_PARAMS_1_USE_FIX_TOP (1 << 6)
  29. #define WDOG_COMP_VERSION_REG_OFFSET 0xf8
  30. #define WDOG_COMP_TYPE_REG_OFFSET 0xfc
  31. /* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */
  32. #define DW_WDT_NUM_TOPS 16
  33. #define DW_WDT_FIX_TOP(idx) (1U << (16 + idx))
  34. #define DW_WDT_DEFAULT_SECONDS 30
  35. #define MSEC_PER_SEC 1000L
  36. enum dw_wdt_rmod
  37. {
  38. DW_WDT_RMOD_RESET = 1,
  39. DW_WDT_RMOD_IRQ
  40. };
  41. struct dw_wdt_timeout
  42. {
  43. rt_uint32_t top_val;
  44. rt_uint32_t sec;
  45. rt_uint32_t msec;
  46. };
  47. struct dw_wdt
  48. {
  49. rt_watchdog_t parent;
  50. void *base;
  51. int irq;
  52. struct rt_clk *clk;
  53. struct rt_reset_control *rstc;
  54. rt_ubase_t rate;
  55. enum dw_wdt_rmod rmod;
  56. struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS];
  57. /* Save/Restore */
  58. rt_uint32_t user;
  59. rt_uint32_t timeout;
  60. rt_uint32_t pretimeout;
  61. rt_uint32_t max_hw_heartbeat_ms;
  62. struct rt_device_notify pretimeout_notify;
  63. };
  64. #define raw_to_dw_wdt(raw) rt_container_of(raw, struct dw_wdt, parent)
  65. static const rt_uint32_t dw_wdt_fix_tops[DW_WDT_NUM_TOPS] =
  66. {
  67. DW_WDT_FIX_TOP(0), DW_WDT_FIX_TOP(1), DW_WDT_FIX_TOP(2), DW_WDT_FIX_TOP(3),
  68. DW_WDT_FIX_TOP(4), DW_WDT_FIX_TOP(5), DW_WDT_FIX_TOP(6), DW_WDT_FIX_TOP(7),
  69. DW_WDT_FIX_TOP(8), DW_WDT_FIX_TOP(9), DW_WDT_FIX_TOP(10), DW_WDT_FIX_TOP(11),
  70. DW_WDT_FIX_TOP(12), DW_WDT_FIX_TOP(13), DW_WDT_FIX_TOP(14), DW_WDT_FIX_TOP(15)
  71. };
  72. rt_inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt)
  73. {
  74. return HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) & WDOG_CONTROL_REG_WDT_EN_MASK;
  75. }
  76. static void dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod)
  77. {
  78. rt_uint32_t val = HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
  79. if (rmod == DW_WDT_RMOD_IRQ)
  80. {
  81. val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
  82. }
  83. else
  84. {
  85. val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
  86. }
  87. HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) = val;
  88. dw_wdt->rmod = rmod;
  89. }
  90. static rt_uint32_t dw_wdt_find_best_top(struct dw_wdt *dw_wdt, rt_uint32_t timeout, rt_uint32_t *top_val)
  91. {
  92. int idx;
  93. /*
  94. * Find a TOP with timeout greater or equal to the requested number. Note
  95. * we'll select a TOP with maximum timeout if the requested timeout couldn't
  96. * be reached.
  97. */
  98. for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx)
  99. {
  100. if (dw_wdt->timeouts[idx].sec >= timeout)
  101. {
  102. break;
  103. }
  104. }
  105. if (idx == DW_WDT_NUM_TOPS)
  106. {
  107. --idx;
  108. }
  109. *top_val = dw_wdt->timeouts[idx].top_val;
  110. return dw_wdt->timeouts[idx].sec;
  111. }
  112. static rt_uint32_t dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt)
  113. {
  114. rt_uint64_t msec;
  115. struct dw_wdt_timeout *timeout = &dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1];
  116. msec = (rt_uint64_t)timeout->sec * MSEC_PER_SEC + timeout->msec;
  117. return msec < RT_UINT32_MAX ? msec : RT_UINT32_MAX;
  118. }
  119. static rt_uint32_t dw_wdt_get_timeout(struct dw_wdt *dw_wdt)
  120. {
  121. int idx;
  122. int top_val = HWREG32(dw_wdt->base + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
  123. for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx)
  124. {
  125. if (dw_wdt->timeouts[idx].top_val == top_val)
  126. {
  127. break;
  128. }
  129. }
  130. /*
  131. * In IRQ mode due to the two stages counter, the actual timeout is twice
  132. * greater than the TOP setting.
  133. */
  134. return dw_wdt->timeouts[idx].sec * dw_wdt->rmod;
  135. }
  136. static int dw_wdt_keep_alive(struct dw_wdt *dw_wdt)
  137. {
  138. HWREG32(dw_wdt->base + WDOG_COUNTER_RESTART_REG_OFFSET) = WDOG_COUNTER_RESTART_KICK_VALUE;
  139. return 0;
  140. }
  141. static void dw_wdt_set_timeout(struct dw_wdt *dw_wdt, rt_uint32_t top_s)
  142. {
  143. rt_uint32_t top_val;
  144. rt_uint32_t timeout;
  145. /*
  146. * Note IRQ mode being enabled means having a non-zero pre-timeout setup.
  147. * In this case we try to find a TOP as close to the half of the requested
  148. * timeout as possible since DW Watchdog IRQ mode is designed in two stages
  149. * way - first timeout rises the pre-timeout interrupt, second timeout
  150. * performs the system reset. So basically the effective watchdog-caused
  151. * reset happens after two watchdog TOPs elapsed.
  152. */
  153. timeout = dw_wdt_find_best_top(dw_wdt, RT_DIV_ROUND_UP(top_s, dw_wdt->rmod), &top_val);
  154. if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
  155. {
  156. dw_wdt->pretimeout = timeout;
  157. }
  158. else
  159. {
  160. dw_wdt->pretimeout = 0;
  161. }
  162. /*
  163. * Set the new value in the watchdog. Some versions of dw_wdt have have
  164. * TOPINIT in the TIMEOUT_RANGE register (as per CP_WDT_DUAL_TOP in
  165. * WDT_COMP_PARAMS_1). On those we effectively get a pat of the watchdog
  166. * right here.
  167. */
  168. HWREG32(dw_wdt->base + WDOG_TIMEOUT_RANGE_REG_OFFSET) = top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT;
  169. dw_wdt_keep_alive(dw_wdt);
  170. /*
  171. * In case users set bigger timeout value than HW can support,
  172. * kernel(watchdog_dev.c) helps to feed watchdog before wdd->max_hw_heartbeat_ms
  173. */
  174. if (top_s * 1000 <= dw_wdt->max_hw_heartbeat_ms)
  175. {
  176. dw_wdt->timeout = timeout * dw_wdt->rmod;
  177. }
  178. else
  179. {
  180. dw_wdt->timeout = top_s;
  181. }
  182. }
  183. static void dw_wdt_set_pretimeout(struct dw_wdt *dw_wdt, rt_uint32_t req)
  184. {
  185. /*
  186. * We ignore actual value of the timeout passed from user-space using it as
  187. * a flag whether the pretimeout functionality is intended to be activated.
  188. */
  189. dw_wdt_update_mode(dw_wdt, req ? DW_WDT_RMOD_IRQ : DW_WDT_RMOD_RESET);
  190. dw_wdt_set_timeout(dw_wdt, dw_wdt->timeout);
  191. }
  192. static void dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt)
  193. {
  194. rt_uint32_t val = HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET);
  195. /* Disable/enable interrupt mode depending on the RMOD flag. */
  196. if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
  197. {
  198. val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
  199. }
  200. else
  201. {
  202. val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
  203. }
  204. /* Enable watchdog. */
  205. HWREG32(dw_wdt->base + WDOG_CONTROL_REG_OFFSET) = val | WDOG_CONTROL_REG_WDT_EN_MASK;
  206. }
  207. static int dw_wdt_start(struct dw_wdt *dw_wdt)
  208. {
  209. rt_clk_enable(dw_wdt->clk);
  210. dw_wdt_set_timeout(dw_wdt, dw_wdt->timeout);
  211. dw_wdt_keep_alive(dw_wdt);
  212. dw_wdt_arm_system_reset(dw_wdt);
  213. return 0;
  214. }
  215. static int dw_wdt_stop(struct dw_wdt *dw_wdt)
  216. {
  217. /*
  218. * The DesignWare watchdog cannot be stopped once it has been started so we
  219. * do not implement a stop function. The watchdog core will continue to send
  220. * heartbeat requests after the watchdog device has been closed.
  221. */
  222. rt_clk_disable(dw_wdt->clk);
  223. rt_reset_control_assert(dw_wdt->rstc);
  224. rt_reset_control_deassert(dw_wdt->rstc);
  225. return 0;
  226. }
  227. static rt_uint32_t dw_wdt_get_timeleft(struct dw_wdt *dw_wdt)
  228. {
  229. rt_uint32_t val, sec;
  230. val = HWREG32(dw_wdt->base + WDOG_CURRENT_COUNT_REG_OFFSET);
  231. sec = val / dw_wdt->rate;
  232. if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
  233. {
  234. val = HWREG32(dw_wdt->base + WDOG_INTERRUPT_STATUS_REG_OFFSET);
  235. if (!val)
  236. {
  237. sec += dw_wdt->pretimeout;
  238. }
  239. }
  240. return sec;
  241. }
  242. static rt_err_t dw_wdt_init_timeouts(struct dw_wdt *dw_wdt)
  243. {
  244. int val, tidx;
  245. rt_uint64_t msec;
  246. struct dw_wdt_timeout tout, *dst;
  247. const rt_uint32_t *tops = dw_wdt_fix_tops;
  248. /*
  249. * Convert the specified TOPs into an array of watchdog timeouts. We walk
  250. * over the passed TOPs array and calculate corresponding timeouts in
  251. * seconds and milliseconds. The milliseconds granularity is needed to
  252. * distinguish the TOPs with very close timeouts and to set the watchdog max
  253. * heartbeat setting further.
  254. */
  255. for (val = 0; val < DW_WDT_NUM_TOPS; ++val)
  256. {
  257. tout.top_val = val;
  258. tout.sec = tops[val] / dw_wdt->rate;
  259. msec = (rt_uint64_t)tops[val] * MSEC_PER_SEC;
  260. rt_do_div(msec, dw_wdt->rate);
  261. tout.msec = msec - ((rt_uint64_t)tout.sec * MSEC_PER_SEC);
  262. /*
  263. * Find a suitable place for the current TOP in the timeouts array so
  264. * that the list is remained in the ascending order.
  265. */
  266. for (tidx = 0; tidx < val; ++tidx)
  267. {
  268. dst = &dw_wdt->timeouts[tidx];
  269. if (tout.sec > dst->sec || (tout.sec == dst->sec && tout.msec >= dst->msec))
  270. {
  271. continue;
  272. }
  273. else
  274. {
  275. struct dw_wdt_timeout tmp = *dst;
  276. *dst = tout;
  277. tout = tmp;
  278. }
  279. }
  280. dw_wdt->timeouts[val] = tout;
  281. }
  282. if (!dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1].sec)
  283. {
  284. return -RT_ENOSYS;
  285. }
  286. return RT_EOK;
  287. }
  288. static rt_err_t dw_wdt_init(rt_watchdog_t *wdt)
  289. {
  290. rt_err_t status = RT_EOK;
  291. struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
  292. /* Enable normal reset without pre-timeout by default. */
  293. dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET);
  294. if (dw_wdt_init_timeouts(dw_wdt))
  295. {
  296. return -RT_ERROR;
  297. }
  298. dw_wdt->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt);
  299. /*
  300. * If the watchdog is already running, use its already configured timeout.
  301. * Otherwise use the default or the value provided through devicetree.
  302. */
  303. if (dw_wdt_is_enabled(dw_wdt))
  304. {
  305. dw_wdt->timeout = dw_wdt_get_timeout(dw_wdt);
  306. }
  307. else
  308. {
  309. dw_wdt->timeout = DW_WDT_DEFAULT_SECONDS;
  310. }
  311. return status;
  312. }
  313. static rt_err_t dw_wdt_control(rt_watchdog_t *wdt, int cmd, void *args)
  314. {
  315. rt_err_t status = RT_EOK;
  316. struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
  317. switch (cmd)
  318. {
  319. case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
  320. *(rt_uint32_t *)args = dw_wdt_get_timeout(dw_wdt);
  321. break;
  322. case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
  323. dw_wdt_set_timeout(dw_wdt, *(rt_uint32_t *)args);
  324. break;
  325. case RT_DEVICE_CTRL_WDT_GET_TIMELEFT:
  326. *(rt_uint32_t *)args = dw_wdt_get_timeleft(dw_wdt);
  327. break;
  328. case RT_DEVICE_CTRL_WDT_KEEPALIVE:
  329. dw_wdt_set_pretimeout(dw_wdt, dw_wdt->pretimeout);
  330. dw_wdt_keep_alive(dw_wdt);
  331. break;
  332. case RT_DEVICE_CTRL_WDT_START:
  333. dw_wdt_start(dw_wdt);
  334. dw_wdt->user++;
  335. break;
  336. case RT_DEVICE_CTRL_WDT_STOP:
  337. dw_wdt_stop(dw_wdt);
  338. dw_wdt->user--;
  339. break;
  340. case RT_DEVICE_CTRL_NOTIFY_SET:
  341. rt_hw_interrupt_mask(dw_wdt->irq);
  342. if (args)
  343. {
  344. rt_memcpy(&dw_wdt->pretimeout_notify, args, sizeof(dw_wdt->pretimeout_notify));
  345. }
  346. else
  347. {
  348. rt_memset(&dw_wdt->pretimeout_notify, 0, sizeof(dw_wdt->pretimeout_notify));
  349. }
  350. rt_hw_interrupt_umask(dw_wdt->irq);
  351. break;
  352. default:
  353. status = -RT_EINVAL;
  354. }
  355. return status;
  356. }
  357. static const struct rt_watchdog_ops dw_wdt_ops =
  358. {
  359. .init = dw_wdt_init,
  360. .control = dw_wdt_control,
  361. };
  362. #ifdef RT_USING_PM
  363. static rt_err_t dw_wdt_pm_suspend(const struct rt_device *device, rt_uint8_t mode)
  364. {
  365. rt_watchdog_t *wdt = rt_container_of(device, rt_watchdog_t, parent);
  366. struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
  367. dw_wdt->timeout = dw_wdt_get_timeleft(dw_wdt) / dw_wdt->rate;
  368. dw_wdt_stop(dw_wdt);
  369. return RT_EOK;
  370. }
  371. static void dw_wdt_pm_resume(const struct rt_device *device, rt_uint8_t mode)
  372. {
  373. rt_watchdog_t *wdt = rt_container_of(device, rt_watchdog_t, parent);
  374. struct dw_wdt *dw_wdt = raw_to_dw_wdt(wdt);
  375. if (!dw_wdt->user)
  376. {
  377. return;
  378. }
  379. if (!dw_wdt_init(wdt))
  380. {
  381. dw_wdt_start(dw_wdt);
  382. }
  383. }
  384. static const struct rt_device_pm_ops dw_wdt_pm_ops =
  385. {
  386. .suspend = dw_wdt_pm_suspend,
  387. .resume = dw_wdt_pm_resume,
  388. };
  389. #endif /* RT_USING_PM */
  390. static void dw_wdt_isr(int irqno, void *param)
  391. {
  392. struct dw_wdt *wdt = (struct dw_wdt *)param;
  393. if (!HWREG32(wdt->base + WDOG_INTERRUPT_STATUS_REG_OFFSET))
  394. {
  395. return;
  396. }
  397. /* Clear the IRQ status (EOI) */
  398. (void)HWREG32(wdt->base + WDOG_INTERRUPT_CLEAR_REG_OFFSET);
  399. if (wdt->pretimeout_notify.notify)
  400. {
  401. wdt->pretimeout_notify.notify(wdt->pretimeout_notify.dev);
  402. }
  403. }
  404. static void dw_wdt_free(struct dw_wdt *dw_wdt)
  405. {
  406. if (dw_wdt->base)
  407. {
  408. rt_iounmap(dw_wdt->base);
  409. }
  410. if (!rt_is_err_or_null(dw_wdt->clk))
  411. {
  412. rt_clk_disable(dw_wdt->clk);
  413. }
  414. if (!rt_is_err_or_null(dw_wdt->rstc))
  415. {
  416. rt_reset_control_assert(dw_wdt->rstc);
  417. rt_reset_control_put(dw_wdt->rstc);
  418. }
  419. rt_free(dw_wdt);
  420. }
  421. static rt_err_t dw_wdt_probe(struct rt_platform_device *pdev)
  422. {
  423. rt_err_t err = RT_EOK;
  424. const char *dev_name;
  425. struct rt_device *dev = &pdev->parent;
  426. struct dw_wdt *dw_wdt = rt_calloc(1, sizeof(*dw_wdt));
  427. if (!dw_wdt)
  428. {
  429. return -RT_ENOMEM;
  430. }
  431. dw_wdt->base = rt_dm_dev_iomap(dev, 0);
  432. if (!dw_wdt->base)
  433. {
  434. err = -RT_EIO;
  435. goto _free_res;
  436. }
  437. dw_wdt->irq = rt_dm_dev_get_irq(dev, 0);
  438. if (dw_wdt->irq < 0)
  439. {
  440. err = dw_wdt->irq;
  441. goto _free_res;
  442. }
  443. dw_wdt->clk = rt_clk_get_by_name(dev, "pclk");
  444. if (rt_is_err(dw_wdt->clk))
  445. {
  446. dw_wdt->clk = rt_clk_get_by_index(dev, 0);
  447. if (rt_is_err(dw_wdt->clk))
  448. {
  449. err = rt_ptr_err(dw_wdt->clk);
  450. goto _free_res;
  451. }
  452. }
  453. dw_wdt->rstc = rt_reset_control_get_by_index(dev, 0);
  454. if (rt_is_err(dw_wdt->rstc))
  455. {
  456. err = rt_ptr_err(dw_wdt->rstc);
  457. goto _free_res;
  458. }
  459. rt_reset_control_deassert(dw_wdt->rstc);
  460. dev->user_data = dw_wdt;
  461. dw_wdt->rate = rt_clk_get_rate(dw_wdt->clk);
  462. dw_wdt->parent.ops = &dw_wdt_ops;
  463. rt_dm_dev_set_name_auto(&dw_wdt->parent.parent, "wdt");
  464. dev_name = rt_dm_dev_get_name(&dw_wdt->parent.parent);
  465. rt_hw_interrupt_install(dw_wdt->irq, dw_wdt_isr, dw_wdt, dev_name);
  466. rt_hw_interrupt_umask(dw_wdt->irq);
  467. #ifdef RT_USING_PM
  468. rt_pm_device_register(&dw_wdt->parent.parent, &dw_wdt_pm_ops);
  469. #endif
  470. rt_hw_watchdog_register(&dw_wdt->parent, dev_name, 0, dw_wdt);
  471. return RT_EOK;
  472. _free_res:
  473. dw_wdt_free(dw_wdt);
  474. return err;
  475. }
  476. static rt_err_t dw_wdt_remove(struct rt_platform_device *pdev)
  477. {
  478. struct dw_wdt *dw_wdt = pdev->parent.user_data;
  479. rt_hw_interrupt_mask(dw_wdt->irq);
  480. rt_pic_detach_irq(dw_wdt->irq, dw_wdt);
  481. #ifdef RT_USING_PM
  482. rt_pm_device_unregister(&dw_wdt->parent.parent);
  483. #endif
  484. rt_device_unregister(&dw_wdt->parent.parent);
  485. dw_wdt_free(dw_wdt);
  486. return RT_EOK;
  487. }
  488. static const struct rt_ofw_node_id dw_wdt_ofw_ids[] =
  489. {
  490. { .compatible = "snps,dw-wdt" },
  491. { /* sentinel */ }
  492. };
  493. static struct rt_platform_driver dw_wdt_driver =
  494. {
  495. .name = "dw-wdt",
  496. .ids = dw_wdt_ofw_ids,
  497. .probe = dw_wdt_probe,
  498. .remove = dw_wdt_remove,
  499. };
  500. RT_PLATFORM_DRIVER_EXPORT(dw_wdt_driver);