blob: fba21de2bbadbc0b80f8cb83224ea9cd2fbc5ab8 [file] [log] [blame]
Thomas Gleixner2874c5f2019-05-27 08:55:01 +02001// SPDX-License-Identifier: GPL-2.0-or-later
Jamie Ilesc9353ae2011-01-24 12:19:12 +00002/*
3 * Copyright 2010-2011 Picochip Ltd., Jamie Iles
4 * http://www.picochip.com
5 *
Jamie Ilesc9353ae2011-01-24 12:19:12 +00006 * This file implements a driver for the Synopsys DesignWare watchdog device
Baruch Siach58a251f2013-12-30 14:25:54 +02007 * in the many subsystems. The watchdog has 16 different timeout periods
Jamie Ilesc9353ae2011-01-24 12:19:12 +00008 * and these are a function of the input clock frequency.
9 *
10 * The DesignWare watchdog cannot be stopped once it has been started so we
Guenter Roeckf29a72c2016-02-28 13:12:19 -080011 * do not implement a stop function. The watchdog core will continue to send
12 * heartbeat requests after the watchdog device has been closed.
Jamie Ilesc9353ae2011-01-24 12:19:12 +000013 */
Joe Perches27c766a2012-02-15 15:06:19 -080014
Jamie Ilesc9353ae2011-01-24 12:19:12 +000015#include <linux/bitops.h>
16#include <linux/clk.h>
Jisheng Zhang31228f42014-09-23 15:42:12 +080017#include <linux/delay.h>
Jamie Ilesc9353ae2011-01-24 12:19:12 +000018#include <linux/err.h>
Jamie Ilesc9353ae2011-01-24 12:19:12 +000019#include <linux/io.h>
20#include <linux/kernel.h>
Jamie Ilesc9353ae2011-01-24 12:19:12 +000021#include <linux/module.h>
22#include <linux/moduleparam.h>
Dinh Nguyen58e56372013-10-22 11:59:12 -050023#include <linux/of.h>
Jamie Ilesc9353ae2011-01-24 12:19:12 +000024#include <linux/pm.h>
25#include <linux/platform_device.h>
Steffen Trumtrar65a3b692017-05-22 10:51:39 +020026#include <linux/reset.h>
Jamie Ilesc9353ae2011-01-24 12:19:12 +000027#include <linux/watchdog.h>
28
29#define WDOG_CONTROL_REG_OFFSET 0x00
30#define WDOG_CONTROL_REG_WDT_EN_MASK 0x01
Brian Norrisa81abbb2018-03-09 19:46:06 -080031#define WDOG_CONTROL_REG_RESP_MODE_MASK 0x02
Jamie Ilesc9353ae2011-01-24 12:19:12 +000032#define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04
Jisheng Zhangdfa07142014-09-23 15:42:11 +080033#define WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT 4
Jamie Ilesc9353ae2011-01-24 12:19:12 +000034#define WDOG_CURRENT_COUNT_REG_OFFSET 0x08
35#define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c
36#define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
37
38/* The maximum TOP (timeout period) value that can be set in the watchdog. */
39#define DW_WDT_MAX_TOP 15
40
Doug Andersonb5ade9b2015-01-27 14:25:17 -080041#define DW_WDT_DEFAULT_SECONDS 30
42
Wim Van Sebroeck86a1e182012-03-05 16:51:11 +010043static bool nowayout = WATCHDOG_NOWAYOUT;
44module_param(nowayout, bool, 0);
Jamie Ilesc9353ae2011-01-24 12:19:12 +000045MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
46 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
47
Guenter Roeckf29a72c2016-02-28 13:12:19 -080048struct dw_wdt {
Jamie Ilesc9353ae2011-01-24 12:19:12 +000049 void __iomem *regs;
50 struct clk *clk;
Guenter Roeckc97344f2016-08-09 22:35:58 -070051 unsigned long rate;
Guenter Roeckf29a72c2016-02-28 13:12:19 -080052 struct watchdog_device wdd;
Steffen Trumtrar65a3b692017-05-22 10:51:39 +020053 struct reset_control *rst;
Brian Norris8c088372018-03-09 19:46:07 -080054 /* Save/restore */
55 u32 control;
56 u32 timeout;
Guenter Roeckf29a72c2016-02-28 13:12:19 -080057};
Jamie Ilesc9353ae2011-01-24 12:19:12 +000058
Guenter Roeckf29a72c2016-02-28 13:12:19 -080059#define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd)
60
61static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt)
Jamie Ilesc9353ae2011-01-24 12:19:12 +000062{
Guenter Roeckf29a72c2016-02-28 13:12:19 -080063 return readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET) &
Jamie Ilesc9353ae2011-01-24 12:19:12 +000064 WDOG_CONTROL_REG_WDT_EN_MASK;
65}
66
Guenter Roeckf29a72c2016-02-28 13:12:19 -080067static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top)
Jamie Ilesc9353ae2011-01-24 12:19:12 +000068{
69 /*
70 * There are 16 possible timeout values in 0..15 where the number of
71 * cycles is 2 ^ (16 + i) and the watchdog counts down.
72 */
Guenter Roeckc97344f2016-08-09 22:35:58 -070073 return (1U << (16 + top)) / dw_wdt->rate;
Jamie Ilesc9353ae2011-01-24 12:19:12 +000074}
75
Guenter Roeckf29a72c2016-02-28 13:12:19 -080076static int dw_wdt_get_top(struct dw_wdt *dw_wdt)
Jamie Ilesc9353ae2011-01-24 12:19:12 +000077{
Guenter Roeckf29a72c2016-02-28 13:12:19 -080078 int top = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
Jamie Ilesc9353ae2011-01-24 12:19:12 +000079
Guenter Roeckf29a72c2016-02-28 13:12:19 -080080 return dw_wdt_top_in_seconds(dw_wdt, top);
Jamie Ilesc9353ae2011-01-24 12:19:12 +000081}
82
Guenter Roeckf29a72c2016-02-28 13:12:19 -080083static int dw_wdt_ping(struct watchdog_device *wdd)
Jamie Ilesc9353ae2011-01-24 12:19:12 +000084{
Guenter Roeckf29a72c2016-02-28 13:12:19 -080085 struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
Jamie Ilesc9353ae2011-01-24 12:19:12 +000086
Guenter Roeckf29a72c2016-02-28 13:12:19 -080087 writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt->regs +
Doug Andersona0085012015-01-27 14:25:16 -080088 WDOG_COUNTER_RESTART_REG_OFFSET);
Guenter Roeckf29a72c2016-02-28 13:12:19 -080089
90 return 0;
Doug Andersona0085012015-01-27 14:25:16 -080091}
92
Guenter Roeckf29a72c2016-02-28 13:12:19 -080093static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
Jamie Ilesc9353ae2011-01-24 12:19:12 +000094{
Guenter Roeckf29a72c2016-02-28 13:12:19 -080095 struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
Jamie Ilesc9353ae2011-01-24 12:19:12 +000096 int i, top_val = DW_WDT_MAX_TOP;
97
98 /*
99 * Iterate over the timeout values until we find the closest match. We
100 * always look for >=.
101 */
102 for (i = 0; i <= DW_WDT_MAX_TOP; ++i)
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800103 if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) {
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000104 top_val = i;
105 break;
106 }
107
Doug Andersona0085012015-01-27 14:25:16 -0800108 /*
109 * Set the new value in the watchdog. Some versions of dw_wdt
110 * have have TOPINIT in the TIMEOUT_RANGE register (as per
111 * CP_WDT_DUAL_TOP in WDT_COMP_PARAMS_1). On those we
112 * effectively get a pat of the watchdog right here.
113 */
Jisheng Zhangdfa07142014-09-23 15:42:11 +0800114 writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT,
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800115 dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000116
Wang, Peng 1. (NSB - CN/Hangzhou)d4ba76d2019-11-25 02:04:13 +0000117 /*
118 * In case users set bigger timeout value than HW can support,
119 * kernel(watchdog_dev.c) helps to feed watchdog before
120 * wdd->max_hw_heartbeat_ms
121 */
122 if (top_s * 1000 <= wdd->max_hw_heartbeat_ms)
123 wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val);
124 else
125 wdd->timeout = top_s;
Doug Andersona0085012015-01-27 14:25:16 -0800126
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800127 return 0;
128}
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000129
Brian Norrisa81abbb2018-03-09 19:46:06 -0800130static void dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt)
131{
132 u32 val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
133
134 /* Disable interrupt mode; always perform system reset. */
135 val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
136 /* Enable watchdog. */
137 val |= WDOG_CONTROL_REG_WDT_EN_MASK;
138 writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
139}
140
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800141static int dw_wdt_start(struct watchdog_device *wdd)
142{
143 struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
144
145 dw_wdt_set_timeout(wdd, wdd->timeout);
Jack Mitchelle7046df2020-01-07 15:51:55 +0000146 dw_wdt_ping(&dw_wdt->wdd);
Brian Norrisa81abbb2018-03-09 19:46:06 -0800147 dw_wdt_arm_system_reset(dw_wdt);
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800148
149 return 0;
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000150}
151
Oleksij Rempel1bfe8882017-09-26 08:11:22 +0200152static int dw_wdt_stop(struct watchdog_device *wdd)
153{
154 struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
155
156 if (!dw_wdt->rst) {
157 set_bit(WDOG_HW_RUNNING, &wdd->status);
158 return 0;
159 }
160
161 reset_control_assert(dw_wdt->rst);
162 reset_control_deassert(dw_wdt->rst);
163
164 return 0;
165}
166
Guenter Roecka70dcc02017-01-04 12:27:21 -0800167static int dw_wdt_restart(struct watchdog_device *wdd,
168 unsigned long action, void *data)
Jisheng Zhang31228f42014-09-23 15:42:12 +0800169{
Guenter Roecka70dcc02017-01-04 12:27:21 -0800170 struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
Jisheng Zhang31228f42014-09-23 15:42:12 +0800171
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800172 writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
Brian Norrisa81abbb2018-03-09 19:46:06 -0800173 if (dw_wdt_is_enabled(dw_wdt))
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800174 writel(WDOG_COUNTER_RESTART_KICK_VALUE,
175 dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET);
Jisheng Zhang31228f42014-09-23 15:42:12 +0800176 else
Brian Norrisa81abbb2018-03-09 19:46:06 -0800177 dw_wdt_arm_system_reset(dw_wdt);
Jisheng Zhang31228f42014-09-23 15:42:12 +0800178
179 /* wait for reset to assert... */
180 mdelay(500);
181
Guenter Roecka70dcc02017-01-04 12:27:21 -0800182 return 0;
Jisheng Zhang31228f42014-09-23 15:42:12 +0800183}
184
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800185static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd)
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000186{
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800187 struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000188
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800189 return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
Guenter Roeckc97344f2016-08-09 22:35:58 -0700190 dw_wdt->rate;
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000191}
192
193static const struct watchdog_info dw_wdt_ident = {
194 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
195 WDIOF_MAGICCLOSE,
196 .identity = "Synopsys DesignWare Watchdog",
197};
198
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800199static const struct watchdog_ops dw_wdt_ops = {
200 .owner = THIS_MODULE,
201 .start = dw_wdt_start,
Oleksij Rempel1bfe8882017-09-26 08:11:22 +0200202 .stop = dw_wdt_stop,
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800203 .ping = dw_wdt_ping,
204 .set_timeout = dw_wdt_set_timeout,
205 .get_timeleft = dw_wdt_get_timeleft,
Guenter Roecka70dcc02017-01-04 12:27:21 -0800206 .restart = dw_wdt_restart,
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800207};
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000208
Heiko Stübnerad83c6c2013-06-26 20:03:52 +0200209#ifdef CONFIG_PM_SLEEP
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000210static int dw_wdt_suspend(struct device *dev)
211{
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800212 struct dw_wdt *dw_wdt = dev_get_drvdata(dev);
213
Brian Norris8c088372018-03-09 19:46:07 -0800214 dw_wdt->control = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
215 dw_wdt->timeout = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
216
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800217 clk_disable_unprepare(dw_wdt->clk);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000218
219 return 0;
220}
221
222static int dw_wdt_resume(struct device *dev)
223{
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800224 struct dw_wdt *dw_wdt = dev_get_drvdata(dev);
225 int err = clk_prepare_enable(dw_wdt->clk);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000226
227 if (err)
228 return err;
229
Brian Norris8c088372018-03-09 19:46:07 -0800230 writel(dw_wdt->timeout, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
231 writel(dw_wdt->control, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
232
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800233 dw_wdt_ping(&dw_wdt->wdd);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000234
235 return 0;
236}
Heiko Stübnerad83c6c2013-06-26 20:03:52 +0200237#endif /* CONFIG_PM_SLEEP */
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000238
Heiko Stübnerad83c6c2013-06-26 20:03:52 +0200239static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000240
Bill Pemberton2d991a12012-11-19 13:21:41 -0500241static int dw_wdt_drv_probe(struct platform_device *pdev)
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000242{
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800243 struct device *dev = &pdev->dev;
244 struct watchdog_device *wdd;
245 struct dw_wdt *dw_wdt;
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000246 int ret;
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000247
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800248 dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL);
249 if (!dw_wdt)
250 return -ENOMEM;
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000251
Guenter Roeck0f0a6a22019-04-02 12:01:53 -0700252 dw_wdt->regs = devm_platform_ioremap_resource(pdev, 0);
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800253 if (IS_ERR(dw_wdt->regs))
254 return PTR_ERR(dw_wdt->regs);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000255
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800256 dw_wdt->clk = devm_clk_get(dev, NULL);
257 if (IS_ERR(dw_wdt->clk))
258 return PTR_ERR(dw_wdt->clk);
259
260 ret = clk_prepare_enable(dw_wdt->clk);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000261 if (ret)
Jingoo Hancf3cc8c2013-04-29 18:15:26 +0900262 return ret;
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000263
Guenter Roeckc97344f2016-08-09 22:35:58 -0700264 dw_wdt->rate = clk_get_rate(dw_wdt->clk);
265 if (dw_wdt->rate == 0) {
266 ret = -EINVAL;
267 goto out_disable_clk;
268 }
269
Steffen Trumtrar65a3b692017-05-22 10:51:39 +0200270 dw_wdt->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
271 if (IS_ERR(dw_wdt->rst)) {
272 ret = PTR_ERR(dw_wdt->rst);
273 goto out_disable_clk;
274 }
275
276 reset_control_deassert(dw_wdt->rst);
277
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800278 wdd = &dw_wdt->wdd;
279 wdd->info = &dw_wdt_ident;
280 wdd->ops = &dw_wdt_ops;
281 wdd->min_timeout = 1;
282 wdd->max_hw_heartbeat_ms =
283 dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP) * 1000;
284 wdd->parent = dev;
285
286 watchdog_set_drvdata(wdd, dw_wdt);
287 watchdog_set_nowayout(wdd, nowayout);
288 watchdog_init_timeout(wdd, 0, dev);
289
290 /*
291 * If the watchdog is already running, use its already configured
292 * timeout. Otherwise use the default or the value provided through
293 * devicetree.
294 */
295 if (dw_wdt_is_enabled(dw_wdt)) {
296 wdd->timeout = dw_wdt_get_top(dw_wdt);
297 set_bit(WDOG_HW_RUNNING, &wdd->status);
298 } else {
299 wdd->timeout = DW_WDT_DEFAULT_SECONDS;
300 watchdog_init_timeout(wdd, 0, dev);
301 }
302
303 platform_set_drvdata(pdev, dw_wdt);
304
Guenter Roecka70dcc02017-01-04 12:27:21 -0800305 watchdog_set_restart_priority(wdd, 128);
306
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800307 ret = watchdog_register_device(wdd);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000308 if (ret)
309 goto out_disable_clk;
310
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000311 return 0;
312
313out_disable_clk:
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800314 clk_disable_unprepare(dw_wdt->clk);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000315 return ret;
316}
317
Bill Pemberton4b12b892012-11-19 13:26:24 -0500318static int dw_wdt_drv_remove(struct platform_device *pdev)
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000319{
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800320 struct dw_wdt *dw_wdt = platform_get_drvdata(pdev);
Jisheng Zhang31228f42014-09-23 15:42:12 +0800321
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800322 watchdog_unregister_device(&dw_wdt->wdd);
Steffen Trumtrar65a3b692017-05-22 10:51:39 +0200323 reset_control_assert(dw_wdt->rst);
Guenter Roeckf29a72c2016-02-28 13:12:19 -0800324 clk_disable_unprepare(dw_wdt->clk);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000325
326 return 0;
327}
328
Dinh Nguyen58e56372013-10-22 11:59:12 -0500329#ifdef CONFIG_OF
330static const struct of_device_id dw_wdt_of_match[] = {
331 { .compatible = "snps,dw-wdt", },
332 { /* sentinel */ }
333};
334MODULE_DEVICE_TABLE(of, dw_wdt_of_match);
335#endif
336
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000337static struct platform_driver dw_wdt_driver = {
338 .probe = dw_wdt_drv_probe,
Bill Pemberton82268712012-11-19 13:21:12 -0500339 .remove = dw_wdt_drv_remove,
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000340 .driver = {
341 .name = "dw_wdt",
Dinh Nguyen58e56372013-10-22 11:59:12 -0500342 .of_match_table = of_match_ptr(dw_wdt_of_match),
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000343 .pm = &dw_wdt_pm_ops,
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000344 },
345};
346
Axel Linb8ec6112011-11-29 13:56:27 +0800347module_platform_driver(dw_wdt_driver);
Jamie Ilesc9353ae2011-01-24 12:19:12 +0000348
349MODULE_AUTHOR("Jamie Iles");
350MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver");
351MODULE_LICENSE("GPL");