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