]> git.karo-electronics.de Git - karo-tx-linux.git/blob - drivers/watchdog/asm9260_wdt.c
Merge branch 'perf-urgent-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[karo-tx-linux.git] / drivers / watchdog / asm9260_wdt.c
1 /*
2  * Watchdog driver for Alphascale ASM9260.
3  *
4  * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de>
5  *
6  * Licensed under GPLv2 or later.
7  */
8
9 #include <linux/bitops.h>
10 #include <linux/clk.h>
11 #include <linux/delay.h>
12 #include <linux/interrupt.h>
13 #include <linux/io.h>
14 #include <linux/module.h>
15 #include <linux/of.h>
16 #include <linux/platform_device.h>
17 #include <linux/reboot.h>
18 #include <linux/reset.h>
19 #include <linux/watchdog.h>
20
21 #define CLOCK_FREQ      1000000
22
23 /* Watchdog Mode register */
24 #define HW_WDMOD                        0x00
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)
31 /* WD enable */
32 #define BM_MOD_WDEN                     BIT(0)
33
34 /*
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
38  */
39 #define HW_WDTC                         0x04
40 #define BM_WDTC_MAX(freq)               (0x7fffffff / (freq))
41
42 /* Watchdog Feed register */
43 #define HW_WDFEED                       0x08
44
45 /* Watchdog Timer Value register */
46 #define HW_WDTV                         0x0c
47
48 #define ASM9260_WDT_DEFAULT_TIMEOUT     30
49
50 enum asm9260_wdt_mode {
51         HW_RESET,
52         SW_RESET,
53         DEBUG,
54 };
55
56 struct asm9260_wdt_priv {
57         struct device           *dev;
58         struct watchdog_device  wdd;
59         struct clk              *clk;
60         struct clk              *clk_ahb;
61         struct reset_control    *rst;
62         struct notifier_block   restart_handler;
63
64         void __iomem            *iobase;
65         int                     irq;
66         unsigned long           wdt_freq;
67         enum asm9260_wdt_mode   mode;
68 };
69
70 static int asm9260_wdt_feed(struct watchdog_device *wdd)
71 {
72         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
73
74         iowrite32(0xaa, priv->iobase + HW_WDFEED);
75         iowrite32(0x55, priv->iobase + HW_WDFEED);
76
77         return 0;
78 }
79
80 static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
81 {
82         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
83         u32 counter;
84
85         counter = ioread32(priv->iobase + HW_WDTV);
86
87         return DIV_ROUND_CLOSEST(counter, priv->wdt_freq);
88 }
89
90 static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
91 {
92         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
93         u32 counter;
94
95         counter = wdd->timeout * priv->wdt_freq;
96
97         iowrite32(counter, priv->iobase + HW_WDTC);
98
99         return 0;
100 }
101
102 static int asm9260_wdt_enable(struct watchdog_device *wdd)
103 {
104         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
105         u32 mode = 0;
106
107         if (priv->mode == HW_RESET)
108                 mode = BM_MOD_WDRESET;
109
110         iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
111
112         asm9260_wdt_updatetimeout(wdd);
113
114         asm9260_wdt_feed(wdd);
115
116         return 0;
117 }
118
119 static int asm9260_wdt_disable(struct watchdog_device *wdd)
120 {
121         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
122
123         /* The only way to disable WD is to reset it. */
124         reset_control_assert(priv->rst);
125         reset_control_deassert(priv->rst);
126
127         return 0;
128 }
129
130 static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
131 {
132         wdd->timeout = to;
133         asm9260_wdt_updatetimeout(wdd);
134
135         return 0;
136 }
137
138 static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
139 {
140         /* init WD if it was not started */
141
142         iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
143
144         iowrite32(0xff, priv->iobase + HW_WDTC);
145         /* first pass correct sequence */
146         asm9260_wdt_feed(&priv->wdd);
147         /*
148          * Then write wrong pattern to the feed to trigger reset
149          * ASAP.
150          */
151         iowrite32(0xff, priv->iobase + HW_WDFEED);
152
153         mdelay(1000);
154 }
155
156 static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
157 {
158         struct asm9260_wdt_priv *priv = devid;
159         u32 stat;
160
161         stat = ioread32(priv->iobase + HW_WDMOD);
162         if (!(stat & BM_MOD_WDINT))
163                 return IRQ_NONE;
164
165         if (priv->mode == DEBUG) {
166                 dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
167         } else {
168                 dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
169                 asm9260_wdt_sys_reset(priv);
170         }
171
172         return IRQ_HANDLED;
173 }
174
175 static int asm9260_restart_handler(struct notifier_block *this,
176                                    unsigned long mode, void *cmd)
177 {
178         struct asm9260_wdt_priv *priv =
179                 container_of(this, struct asm9260_wdt_priv, restart_handler);
180
181         asm9260_wdt_sys_reset(priv);
182
183         return NOTIFY_DONE;
184 }
185
186 static const struct watchdog_info asm9260_wdt_ident = {
187         .options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
188                                 | WDIOF_MAGICCLOSE,
189         .identity         =     "Alphascale asm9260 Watchdog",
190 };
191
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,
199 };
200
201 static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
202 {
203         int err;
204         unsigned long clk;
205
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);
210         }
211
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);
217         }
218
219         err = clk_prepare_enable(priv->clk_ahb);
220         if (err) {
221                 dev_err(priv->dev, "Failed to enable ahb_clk!\n");
222                 return err;
223         }
224
225         err = clk_set_rate(priv->clk, CLOCK_FREQ);
226         if (err) {
227                 clk_disable_unprepare(priv->clk_ahb);
228                 dev_err(priv->dev, "Failed to set rate!\n");
229                 return err;
230         }
231
232         err = clk_prepare_enable(priv->clk);
233         if (err) {
234                 clk_disable_unprepare(priv->clk_ahb);
235                 dev_err(priv->dev, "Failed to enable clk!\n");
236                 return err;
237         }
238
239         /* wdt has internal divider */
240         clk = clk_get_rate(priv->clk);
241         if (!clk) {
242                 clk_disable_unprepare(priv->clk);
243                 clk_disable_unprepare(priv->clk_ahb);
244                 dev_err(priv->dev, "Failed, clk is 0!\n");
245                 return -EINVAL;
246         }
247
248         priv->wdt_freq = clk / 2;
249
250         return 0;
251 }
252
253 static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
254 {
255         const char *tmp;
256         int ret;
257
258         /* default mode */
259         priv->mode = HW_RESET;
260
261         ret = of_property_read_string(priv->dev->of_node,
262                                       "alphascale,mode", &tmp);
263         if (ret < 0)
264                 return;
265
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"))
271                 priv->mode = DEBUG;
272         else
273                 dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
274                          tmp);
275 }
276
277 static int asm9260_wdt_probe(struct platform_device *pdev)
278 {
279         struct asm9260_wdt_priv *priv;
280         struct watchdog_device *wdd;
281         struct resource *res;
282         int ret;
283         const char * const mode_name[] = { "hw", "sw", "debug", };
284
285         priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv),
286                             GFP_KERNEL);
287         if (!priv)
288                 return -ENOMEM;
289
290         priv->dev = &pdev->dev;
291
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);
296
297         ret = asm9260_wdt_get_dt_clks(priv);
298         if (ret)
299                 return ret;
300
301         priv->rst = devm_reset_control_get(&pdev->dev, "wdt_rst");
302         if (IS_ERR(priv->rst))
303                 return PTR_ERR(priv->rst);
304
305         wdd = &priv->wdd;
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;
311
312         watchdog_set_drvdata(wdd, priv);
313
314         /*
315          * If 'timeout-sec' unspecified in devicetree, assume a 30 second
316          * default, unless the max timeout is less than 30 seconds, then use
317          * the max instead.
318          */
319         wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
320         watchdog_init_timeout(wdd, 0, &pdev->dev);
321
322         asm9260_wdt_get_dt_mode(priv);
323
324         if (priv->mode != HW_RESET)
325                 priv->irq = platform_get_irq(pdev, 0);
326
327         if (priv->irq > 0) {
328                 /*
329                  * Not all supported platforms specify an interrupt for the
330                  * watchdog, so let's make it optional.
331                  */
332                 ret = devm_request_irq(&pdev->dev, priv->irq,
333                                        asm9260_wdt_irq, 0, pdev->name, priv);
334                 if (ret < 0)
335                         dev_warn(&pdev->dev, "failed to request IRQ\n");
336         }
337
338         ret = watchdog_register_device(wdd);
339         if (ret)
340                 goto clk_off;
341
342         platform_set_drvdata(pdev, priv);
343
344         priv->restart_handler.notifier_call = asm9260_restart_handler;
345         priv->restart_handler.priority = 128;
346         ret = register_restart_handler(&priv->restart_handler);
347         if (ret)
348                 dev_warn(&pdev->dev, "cannot register restart handler\n");
349
350         dev_info(&pdev->dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
351                  wdd->timeout, mode_name[priv->mode]);
352         return 0;
353
354 clk_off:
355         clk_disable_unprepare(priv->clk);
356         clk_disable_unprepare(priv->clk_ahb);
357         return ret;
358 }
359
360 static void asm9260_wdt_shutdown(struct platform_device *pdev)
361 {
362         struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
363
364         asm9260_wdt_disable(&priv->wdd);
365 }
366
367 static int asm9260_wdt_remove(struct platform_device *pdev)
368 {
369         struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
370
371         asm9260_wdt_disable(&priv->wdd);
372
373         unregister_restart_handler(&priv->restart_handler);
374
375         watchdog_unregister_device(&priv->wdd);
376
377         clk_disable_unprepare(priv->clk);
378         clk_disable_unprepare(priv->clk_ahb);
379
380         return 0;
381 }
382
383 static const struct of_device_id asm9260_wdt_of_match[] = {
384         { .compatible = "alphascale,asm9260-wdt"},
385         {},
386 };
387 MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
388
389 static struct platform_driver asm9260_wdt_driver = {
390         .driver = {
391                 .name = "asm9260-wdt",
392                 .of_match_table = asm9260_wdt_of_match,
393         },
394         .probe = asm9260_wdt_probe,
395         .remove = asm9260_wdt_remove,
396         .shutdown = asm9260_wdt_shutdown,
397 };
398 module_platform_driver(asm9260_wdt_driver);
399
400 MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
401 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
402 MODULE_LICENSE("GPL");