watchdog: hpwdt: Update version number.
[sfrench/cifs-2.6.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/reset.h>
18 #include <linux/watchdog.h>
19
20 #define CLOCK_FREQ      1000000
21
22 /* Watchdog Mode register */
23 #define HW_WDMOD                        0x00
24 /* Wake interrupt. Set by HW, can't be cleared. */
25 #define BM_MOD_WDINT                    BIT(3)
26 /* This bit set if timeout reached. Cleared by SW. */
27 #define BM_MOD_WDTOF                    BIT(2)
28 /* HW Reset on timeout */
29 #define BM_MOD_WDRESET                  BIT(1)
30 /* WD enable */
31 #define BM_MOD_WDEN                     BIT(0)
32
33 /*
34  * Watchdog Timer Constant register
35  * Minimal value is 0xff, the meaning of this value
36  * depends on used clock: T = WDCLK * (0xff + 1) * 4
37  */
38 #define HW_WDTC                         0x04
39 #define BM_WDTC_MAX(freq)               (0x7fffffff / (freq))
40
41 /* Watchdog Feed register */
42 #define HW_WDFEED                       0x08
43
44 /* Watchdog Timer Value register */
45 #define HW_WDTV                         0x0c
46
47 #define ASM9260_WDT_DEFAULT_TIMEOUT     30
48
49 enum asm9260_wdt_mode {
50         HW_RESET,
51         SW_RESET,
52         DEBUG,
53 };
54
55 struct asm9260_wdt_priv {
56         struct device           *dev;
57         struct watchdog_device  wdd;
58         struct clk              *clk;
59         struct clk              *clk_ahb;
60         struct reset_control    *rst;
61
62         void __iomem            *iobase;
63         int                     irq;
64         unsigned long           wdt_freq;
65         enum asm9260_wdt_mode   mode;
66 };
67
68 static int asm9260_wdt_feed(struct watchdog_device *wdd)
69 {
70         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
71
72         iowrite32(0xaa, priv->iobase + HW_WDFEED);
73         iowrite32(0x55, priv->iobase + HW_WDFEED);
74
75         return 0;
76 }
77
78 static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd)
79 {
80         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
81         u32 counter;
82
83         counter = ioread32(priv->iobase + HW_WDTV);
84
85         return counter / priv->wdt_freq;
86 }
87
88 static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd)
89 {
90         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
91         u32 counter;
92
93         counter = wdd->timeout * priv->wdt_freq;
94
95         iowrite32(counter, priv->iobase + HW_WDTC);
96
97         return 0;
98 }
99
100 static int asm9260_wdt_enable(struct watchdog_device *wdd)
101 {
102         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
103         u32 mode = 0;
104
105         if (priv->mode == HW_RESET)
106                 mode = BM_MOD_WDRESET;
107
108         iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD);
109
110         asm9260_wdt_updatetimeout(wdd);
111
112         asm9260_wdt_feed(wdd);
113
114         return 0;
115 }
116
117 static int asm9260_wdt_disable(struct watchdog_device *wdd)
118 {
119         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
120
121         /* The only way to disable WD is to reset it. */
122         reset_control_assert(priv->rst);
123         reset_control_deassert(priv->rst);
124
125         return 0;
126 }
127
128 static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to)
129 {
130         wdd->timeout = to;
131         asm9260_wdt_updatetimeout(wdd);
132
133         return 0;
134 }
135
136 static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv)
137 {
138         /* init WD if it was not started */
139
140         iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD);
141
142         iowrite32(0xff, priv->iobase + HW_WDTC);
143         /* first pass correct sequence */
144         asm9260_wdt_feed(&priv->wdd);
145         /*
146          * Then write wrong pattern to the feed to trigger reset
147          * ASAP.
148          */
149         iowrite32(0xff, priv->iobase + HW_WDFEED);
150
151         mdelay(1000);
152 }
153
154 static irqreturn_t asm9260_wdt_irq(int irq, void *devid)
155 {
156         struct asm9260_wdt_priv *priv = devid;
157         u32 stat;
158
159         stat = ioread32(priv->iobase + HW_WDMOD);
160         if (!(stat & BM_MOD_WDINT))
161                 return IRQ_NONE;
162
163         if (priv->mode == DEBUG) {
164                 dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n");
165         } else {
166                 dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n");
167                 asm9260_wdt_sys_reset(priv);
168         }
169
170         return IRQ_HANDLED;
171 }
172
173 static int asm9260_restart(struct watchdog_device *wdd, unsigned long action,
174                            void *data)
175 {
176         struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd);
177
178         asm9260_wdt_sys_reset(priv);
179
180         return 0;
181 }
182
183 static const struct watchdog_info asm9260_wdt_ident = {
184         .options          =     WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
185                                 | WDIOF_MAGICCLOSE,
186         .identity         =     "Alphascale asm9260 Watchdog",
187 };
188
189 static const struct watchdog_ops asm9260_wdt_ops = {
190         .owner          = THIS_MODULE,
191         .start          = asm9260_wdt_enable,
192         .stop           = asm9260_wdt_disable,
193         .get_timeleft   = asm9260_wdt_gettimeleft,
194         .ping           = asm9260_wdt_feed,
195         .set_timeout    = asm9260_wdt_settimeout,
196         .restart        = asm9260_restart,
197 };
198
199 static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv)
200 {
201         int err;
202         unsigned long clk;
203
204         priv->clk = devm_clk_get(priv->dev, "mod");
205         if (IS_ERR(priv->clk)) {
206                 dev_err(priv->dev, "Failed to get \"mod\" clk\n");
207                 return PTR_ERR(priv->clk);
208         }
209
210         /* configure AHB clock */
211         priv->clk_ahb = devm_clk_get(priv->dev, "ahb");
212         if (IS_ERR(priv->clk_ahb)) {
213                 dev_err(priv->dev, "Failed to get \"ahb\" clk\n");
214                 return PTR_ERR(priv->clk_ahb);
215         }
216
217         err = clk_prepare_enable(priv->clk_ahb);
218         if (err) {
219                 dev_err(priv->dev, "Failed to enable ahb_clk!\n");
220                 return err;
221         }
222
223         err = clk_set_rate(priv->clk, CLOCK_FREQ);
224         if (err) {
225                 clk_disable_unprepare(priv->clk_ahb);
226                 dev_err(priv->dev, "Failed to set rate!\n");
227                 return err;
228         }
229
230         err = clk_prepare_enable(priv->clk);
231         if (err) {
232                 clk_disable_unprepare(priv->clk_ahb);
233                 dev_err(priv->dev, "Failed to enable clk!\n");
234                 return err;
235         }
236
237         /* wdt has internal divider */
238         clk = clk_get_rate(priv->clk);
239         if (!clk) {
240                 clk_disable_unprepare(priv->clk);
241                 clk_disable_unprepare(priv->clk_ahb);
242                 dev_err(priv->dev, "Failed, clk is 0!\n");
243                 return -EINVAL;
244         }
245
246         priv->wdt_freq = clk / 2;
247
248         return 0;
249 }
250
251 static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv)
252 {
253         const char *tmp;
254         int ret;
255
256         /* default mode */
257         priv->mode = HW_RESET;
258
259         ret = of_property_read_string(priv->dev->of_node,
260                                       "alphascale,mode", &tmp);
261         if (ret < 0)
262                 return;
263
264         if (!strcmp(tmp, "hw"))
265                 priv->mode = HW_RESET;
266         else if (!strcmp(tmp, "sw"))
267                 priv->mode = SW_RESET;
268         else if (!strcmp(tmp, "debug"))
269                 priv->mode = DEBUG;
270         else
271                 dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",
272                          tmp);
273 }
274
275 static int asm9260_wdt_probe(struct platform_device *pdev)
276 {
277         struct asm9260_wdt_priv *priv;
278         struct watchdog_device *wdd;
279         struct resource *res;
280         int ret;
281         const char * const mode_name[] = { "hw", "sw", "debug", };
282
283         priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv),
284                             GFP_KERNEL);
285         if (!priv)
286                 return -ENOMEM;
287
288         priv->dev = &pdev->dev;
289
290         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
291         priv->iobase = devm_ioremap_resource(&pdev->dev, res);
292         if (IS_ERR(priv->iobase))
293                 return PTR_ERR(priv->iobase);
294
295         priv->rst = devm_reset_control_get_exclusive(&pdev->dev, "wdt_rst");
296         if (IS_ERR(priv->rst))
297                 return PTR_ERR(priv->rst);
298
299         ret = asm9260_wdt_get_dt_clks(priv);
300         if (ret)
301                 return ret;
302
303         wdd = &priv->wdd;
304         wdd->info = &asm9260_wdt_ident;
305         wdd->ops = &asm9260_wdt_ops;
306         wdd->min_timeout = 1;
307         wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
308         wdd->parent = &pdev->dev;
309
310         watchdog_set_drvdata(wdd, priv);
311
312         /*
313          * If 'timeout-sec' unspecified in devicetree, assume a 30 second
314          * default, unless the max timeout is less than 30 seconds, then use
315          * the max instead.
316          */
317         wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
318         watchdog_init_timeout(wdd, 0, &pdev->dev);
319
320         asm9260_wdt_get_dt_mode(priv);
321
322         if (priv->mode != HW_RESET)
323                 priv->irq = platform_get_irq(pdev, 0);
324
325         if (priv->irq > 0) {
326                 /*
327                  * Not all supported platforms specify an interrupt for the
328                  * watchdog, so let's make it optional.
329                  */
330                 ret = devm_request_irq(&pdev->dev, priv->irq,
331                                        asm9260_wdt_irq, 0, pdev->name, priv);
332                 if (ret < 0)
333                         dev_warn(&pdev->dev, "failed to request IRQ\n");
334         }
335
336         watchdog_set_restart_priority(wdd, 128);
337
338         ret = watchdog_register_device(wdd);
339         if (ret)
340                 goto clk_off;
341
342         platform_set_drvdata(pdev, priv);
343
344         dev_info(&pdev->dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
345                  wdd->timeout, mode_name[priv->mode]);
346         return 0;
347
348 clk_off:
349         clk_disable_unprepare(priv->clk);
350         clk_disable_unprepare(priv->clk_ahb);
351         return ret;
352 }
353
354 static void asm9260_wdt_shutdown(struct platform_device *pdev)
355 {
356         struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
357
358         asm9260_wdt_disable(&priv->wdd);
359 }
360
361 static int asm9260_wdt_remove(struct platform_device *pdev)
362 {
363         struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
364
365         asm9260_wdt_disable(&priv->wdd);
366
367         watchdog_unregister_device(&priv->wdd);
368
369         clk_disable_unprepare(priv->clk);
370         clk_disable_unprepare(priv->clk_ahb);
371
372         return 0;
373 }
374
375 static const struct of_device_id asm9260_wdt_of_match[] = {
376         { .compatible = "alphascale,asm9260-wdt"},
377         {},
378 };
379 MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
380
381 static struct platform_driver asm9260_wdt_driver = {
382         .driver = {
383                 .name = "asm9260-wdt",
384                 .of_match_table = asm9260_wdt_of_match,
385         },
386         .probe = asm9260_wdt_probe,
387         .remove = asm9260_wdt_remove,
388         .shutdown = asm9260_wdt_shutdown,
389 };
390 module_platform_driver(asm9260_wdt_driver);
391
392 MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
393 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
394 MODULE_LICENSE("GPL");