2 * Watchdog driver for Alphascale ASM9260.
4 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
6 * Licensed under GPLv2 or later.
9 #include <linux/bitops.h>
10 #include <linux/clk.h>
11 #include <linux/delay.h>
12 #include <linux/interrupt.h>
14 #include <linux/module.h>
16 #include <linux/platform_device.h>
17 #include <linux/reboot.h>
18 #include <linux/reset.h>
19 #include <linux/watchdog.h>
21 #define CLOCK_FREQ 1000000
23 /* Watchdog Mode register */
25 /* Wake interrupt. Set by HW, can't be cleared. */
26 #define BM_MOD_WDINT BIT(3)
27 /* This bit set if timeout reached. Cleared by SW. */
28 #define BM_MOD_WDTOF BIT(2)
29 /* HW Reset on timeout */
30 #define BM_MOD_WDRESET BIT(1)
32 #define BM_MOD_WDEN BIT(0)
35 * Watchdog Timer Constant register
36 * Minimal value is 0xff, the meaning of this value
37 * depends on used clock: T = WDCLK * (0xff + 1) * 4
40 #define BM_WDTC_MAX(freq) (0x7fffffff / (freq))
42 /* Watchdog Feed register */
43 #define HW_WDFEED 0x08
45 /* Watchdog Timer Value register */
48 #define ASM9260_WDT_DEFAULT_TIMEOUT 30
50 enum asm9260_wdt_mode
{
56 struct asm9260_wdt_priv
{
58 struct watchdog_device wdd
;
61 struct reset_control
*rst
;
62 struct notifier_block restart_handler
;
66 unsigned long wdt_freq
;
67 enum asm9260_wdt_mode mode
;
70 static int asm9260_wdt_feed(struct watchdog_device
*wdd
)
72 struct asm9260_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
74 iowrite32(0xaa, priv
->iobase
+ HW_WDFEED
);
75 iowrite32(0x55, priv
->iobase
+ HW_WDFEED
);
80 static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device
*wdd
)
82 struct asm9260_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
85 counter
= ioread32(priv
->iobase
+ HW_WDTV
);
87 return DIV_ROUND_CLOSEST(counter
, priv
->wdt_freq
);
90 static int asm9260_wdt_updatetimeout(struct watchdog_device
*wdd
)
92 struct asm9260_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
95 counter
= wdd
->timeout
* priv
->wdt_freq
;
97 iowrite32(counter
, priv
->iobase
+ HW_WDTC
);
102 static int asm9260_wdt_enable(struct watchdog_device
*wdd
)
104 struct asm9260_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
107 if (priv
->mode
== HW_RESET
)
108 mode
= BM_MOD_WDRESET
;
110 iowrite32(BM_MOD_WDEN
| mode
, priv
->iobase
+ HW_WDMOD
);
112 asm9260_wdt_updatetimeout(wdd
);
114 asm9260_wdt_feed(wdd
);
119 static int asm9260_wdt_disable(struct watchdog_device
*wdd
)
121 struct asm9260_wdt_priv
*priv
= watchdog_get_drvdata(wdd
);
123 /* The only way to disable WD is to reset it. */
124 reset_control_assert(priv
->rst
);
125 reset_control_deassert(priv
->rst
);
130 static int asm9260_wdt_settimeout(struct watchdog_device
*wdd
, unsigned int to
)
133 asm9260_wdt_updatetimeout(wdd
);
138 static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv
*priv
)
140 /* init WD if it was not started */
142 iowrite32(BM_MOD_WDEN
| BM_MOD_WDRESET
, priv
->iobase
+ HW_WDMOD
);
144 iowrite32(0xff, priv
->iobase
+ HW_WDTC
);
145 /* first pass correct sequence */
146 asm9260_wdt_feed(&priv
->wdd
);
148 * Then write wrong pattern to the feed to trigger reset
151 iowrite32(0xff, priv
->iobase
+ HW_WDFEED
);
156 static irqreturn_t
asm9260_wdt_irq(int irq
, void *devid
)
158 struct asm9260_wdt_priv
*priv
= devid
;
161 stat
= ioread32(priv
->iobase
+ HW_WDMOD
);
162 if (!(stat
& BM_MOD_WDINT
))
165 if (priv
->mode
== DEBUG
) {
166 dev_info(priv
->dev
, "Watchdog Timeout. Do nothing.\n");
168 dev_info(priv
->dev
, "Watchdog Timeout. Doing SW Reset.\n");
169 asm9260_wdt_sys_reset(priv
);
175 static int asm9260_restart_handler(struct notifier_block
*this,
176 unsigned long mode
, void *cmd
)
178 struct asm9260_wdt_priv
*priv
=
179 container_of(this, struct asm9260_wdt_priv
, restart_handler
);
181 asm9260_wdt_sys_reset(priv
);
186 static const struct watchdog_info asm9260_wdt_ident
= {
187 .options
= WDIOF_SETTIMEOUT
| WDIOF_KEEPALIVEPING
189 .identity
= "Alphascale asm9260 Watchdog",
192 static struct watchdog_ops asm9260_wdt_ops
= {
193 .owner
= THIS_MODULE
,
194 .start
= asm9260_wdt_enable
,
195 .stop
= asm9260_wdt_disable
,
196 .get_timeleft
= asm9260_wdt_gettimeleft
,
197 .ping
= asm9260_wdt_feed
,
198 .set_timeout
= asm9260_wdt_settimeout
,
201 static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv
*priv
)
206 priv
->clk
= devm_clk_get(priv
->dev
, "mod");
207 if (IS_ERR(priv
->clk
)) {
208 dev_err(priv
->dev
, "Failed to get \"mod\" clk\n");
209 return PTR_ERR(priv
->clk
);
212 /* configure AHB clock */
213 priv
->clk_ahb
= devm_clk_get(priv
->dev
, "ahb");
214 if (IS_ERR(priv
->clk_ahb
)) {
215 dev_err(priv
->dev
, "Failed to get \"ahb\" clk\n");
216 return PTR_ERR(priv
->clk_ahb
);
219 err
= clk_prepare_enable(priv
->clk_ahb
);
221 dev_err(priv
->dev
, "Failed to enable ahb_clk!\n");
225 err
= clk_set_rate(priv
->clk
, CLOCK_FREQ
);
227 clk_disable_unprepare(priv
->clk_ahb
);
228 dev_err(priv
->dev
, "Failed to set rate!\n");
232 err
= clk_prepare_enable(priv
->clk
);
234 clk_disable_unprepare(priv
->clk_ahb
);
235 dev_err(priv
->dev
, "Failed to enable clk!\n");
239 /* wdt has internal divider */
240 clk
= clk_get_rate(priv
->clk
);
242 clk_disable_unprepare(priv
->clk
);
243 clk_disable_unprepare(priv
->clk_ahb
);
244 dev_err(priv
->dev
, "Failed, clk is 0!\n");
248 priv
->wdt_freq
= clk
/ 2;
253 static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv
*priv
)
259 priv
->mode
= HW_RESET
;
261 ret
= of_property_read_string(priv
->dev
->of_node
,
262 "alphascale,mode", &tmp
);
266 if (!strcmp(tmp
, "hw"))
267 priv
->mode
= HW_RESET
;
268 else if (!strcmp(tmp
, "sw"))
269 priv
->mode
= SW_RESET
;
270 else if (!strcmp(tmp
, "debug"))
273 dev_warn(priv
->dev
, "unknown reset-type: %s. Using default \"hw\" mode.",
277 static int asm9260_wdt_probe(struct platform_device
*pdev
)
279 struct asm9260_wdt_priv
*priv
;
280 struct watchdog_device
*wdd
;
281 struct resource
*res
;
283 const char * const mode_name
[] = { "hw", "sw", "debug", };
285 priv
= devm_kzalloc(&pdev
->dev
, sizeof(struct asm9260_wdt_priv
),
290 priv
->dev
= &pdev
->dev
;
292 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
293 priv
->iobase
= devm_ioremap_resource(&pdev
->dev
, res
);
294 if (IS_ERR(priv
->iobase
))
295 return PTR_ERR(priv
->iobase
);
297 ret
= asm9260_wdt_get_dt_clks(priv
);
301 priv
->rst
= devm_reset_control_get(&pdev
->dev
, "wdt_rst");
302 if (IS_ERR(priv
->rst
))
303 return PTR_ERR(priv
->rst
);
306 wdd
->info
= &asm9260_wdt_ident
;
307 wdd
->ops
= &asm9260_wdt_ops
;
308 wdd
->min_timeout
= 1;
309 wdd
->max_timeout
= BM_WDTC_MAX(priv
->wdt_freq
);
310 wdd
->parent
= &pdev
->dev
;
312 watchdog_set_drvdata(wdd
, priv
);
315 * If 'timeout-sec' unspecified in devicetree, assume a 30 second
316 * default, unless the max timeout is less than 30 seconds, then use
319 wdd
->timeout
= ASM9260_WDT_DEFAULT_TIMEOUT
;
320 watchdog_init_timeout(wdd
, 0, &pdev
->dev
);
322 asm9260_wdt_get_dt_mode(priv
);
324 if (priv
->mode
!= HW_RESET
)
325 priv
->irq
= platform_get_irq(pdev
, 0);
329 * Not all supported platforms specify an interrupt for the
330 * watchdog, so let's make it optional.
332 ret
= devm_request_irq(&pdev
->dev
, priv
->irq
,
333 asm9260_wdt_irq
, 0, pdev
->name
, priv
);
335 dev_warn(&pdev
->dev
, "failed to request IRQ\n");
338 ret
= watchdog_register_device(wdd
);
342 platform_set_drvdata(pdev
, priv
);
344 priv
->restart_handler
.notifier_call
= asm9260_restart_handler
;
345 priv
->restart_handler
.priority
= 128;
346 ret
= register_restart_handler(&priv
->restart_handler
);
348 dev_warn(&pdev
->dev
, "cannot register restart handler\n");
350 dev_info(&pdev
->dev
, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
351 wdd
->timeout
, mode_name
[priv
->mode
]);
355 clk_disable_unprepare(priv
->clk
);
356 clk_disable_unprepare(priv
->clk_ahb
);
360 static void asm9260_wdt_shutdown(struct platform_device
*pdev
)
362 struct asm9260_wdt_priv
*priv
= platform_get_drvdata(pdev
);
364 asm9260_wdt_disable(&priv
->wdd
);
367 static int asm9260_wdt_remove(struct platform_device
*pdev
)
369 struct asm9260_wdt_priv
*priv
= platform_get_drvdata(pdev
);
371 asm9260_wdt_disable(&priv
->wdd
);
373 unregister_restart_handler(&priv
->restart_handler
);
375 watchdog_unregister_device(&priv
->wdd
);
377 clk_disable_unprepare(priv
->clk
);
378 clk_disable_unprepare(priv
->clk_ahb
);
383 static const struct of_device_id asm9260_wdt_of_match
[] = {
384 { .compatible
= "alphascale,asm9260-wdt"},
387 MODULE_DEVICE_TABLE(of
, asm9260_wdt_of_match
);
389 static struct platform_driver asm9260_wdt_driver
= {
391 .name
= "asm9260-wdt",
392 .owner
= THIS_MODULE
,
393 .of_match_table
= asm9260_wdt_of_match
,
395 .probe
= asm9260_wdt_probe
,
396 .remove
= asm9260_wdt_remove
,
397 .shutdown
= asm9260_wdt_shutdown
,
399 module_platform_driver(asm9260_wdt_driver
);
401 MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
402 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
403 MODULE_LICENSE("GPL");