watchdog: Convert to use devm_platform_ioremap_resource
[linux-2.6-block.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         int ret;
280         static const char * const mode_name[] = { "hw", "sw", "debug", };
281
282         priv = devm_kzalloc(&pdev->dev, sizeof(struct asm9260_wdt_priv),
283                             GFP_KERNEL);
284         if (!priv)
285                 return -ENOMEM;
286
287         priv->dev = &pdev->dev;
288
289         priv->iobase = devm_platform_ioremap_resource(pdev, 0);
290         if (IS_ERR(priv->iobase))
291                 return PTR_ERR(priv->iobase);
292
293         priv->rst = devm_reset_control_get_exclusive(&pdev->dev, "wdt_rst");
294         if (IS_ERR(priv->rst))
295                 return PTR_ERR(priv->rst);
296
297         ret = asm9260_wdt_get_dt_clks(priv);
298         if (ret)
299                 return ret;
300
301         wdd = &priv->wdd;
302         wdd->info = &asm9260_wdt_ident;
303         wdd->ops = &asm9260_wdt_ops;
304         wdd->min_timeout = 1;
305         wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq);
306         wdd->parent = &pdev->dev;
307
308         watchdog_set_drvdata(wdd, priv);
309
310         /*
311          * If 'timeout-sec' unspecified in devicetree, assume a 30 second
312          * default, unless the max timeout is less than 30 seconds, then use
313          * the max instead.
314          */
315         wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT;
316         watchdog_init_timeout(wdd, 0, &pdev->dev);
317
318         asm9260_wdt_get_dt_mode(priv);
319
320         if (priv->mode != HW_RESET)
321                 priv->irq = platform_get_irq(pdev, 0);
322
323         if (priv->irq > 0) {
324                 /*
325                  * Not all supported platforms specify an interrupt for the
326                  * watchdog, so let's make it optional.
327                  */
328                 ret = devm_request_irq(&pdev->dev, priv->irq,
329                                        asm9260_wdt_irq, 0, pdev->name, priv);
330                 if (ret < 0)
331                         dev_warn(&pdev->dev, "failed to request IRQ\n");
332         }
333
334         watchdog_set_restart_priority(wdd, 128);
335
336         ret = watchdog_register_device(wdd);
337         if (ret)
338                 goto clk_off;
339
340         platform_set_drvdata(pdev, priv);
341
342         dev_info(&pdev->dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n",
343                  wdd->timeout, mode_name[priv->mode]);
344         return 0;
345
346 clk_off:
347         clk_disable_unprepare(priv->clk);
348         clk_disable_unprepare(priv->clk_ahb);
349         return ret;
350 }
351
352 static void asm9260_wdt_shutdown(struct platform_device *pdev)
353 {
354         struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
355
356         asm9260_wdt_disable(&priv->wdd);
357 }
358
359 static int asm9260_wdt_remove(struct platform_device *pdev)
360 {
361         struct asm9260_wdt_priv *priv = platform_get_drvdata(pdev);
362
363         asm9260_wdt_disable(&priv->wdd);
364
365         watchdog_unregister_device(&priv->wdd);
366
367         clk_disable_unprepare(priv->clk);
368         clk_disable_unprepare(priv->clk_ahb);
369
370         return 0;
371 }
372
373 static const struct of_device_id asm9260_wdt_of_match[] = {
374         { .compatible = "alphascale,asm9260-wdt"},
375         {},
376 };
377 MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);
378
379 static struct platform_driver asm9260_wdt_driver = {
380         .driver = {
381                 .name = "asm9260-wdt",
382                 .of_match_table = asm9260_wdt_of_match,
383         },
384         .probe = asm9260_wdt_probe,
385         .remove = asm9260_wdt_remove,
386         .shutdown = asm9260_wdt_shutdown,
387 };
388 module_platform_driver(asm9260_wdt_driver);
389
390 MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");
391 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");
392 MODULE_LICENSE("GPL");