Merge branch 'acpi-ec'
[linux-drm-fsl-dcu.git] / drivers / watchdog / meson_wdt.c
1 /*
2  *      Meson Watchdog Driver
3  *
4  *      Copyright (c) 2014 Carlo Caione
5  *
6  *      This program is free software; you can redistribute it and/or
7  *      modify it under the terms of the GNU General Public License
8  *      as published by the Free Software Foundation; either version
9  *      2 of the License, or (at your option) any later version.
10  */
11
12 #include <linux/clk.h>
13 #include <linux/delay.h>
14 #include <linux/err.h>
15 #include <linux/init.h>
16 #include <linux/io.h>
17 #include <linux/kernel.h>
18 #include <linux/module.h>
19 #include <linux/moduleparam.h>
20 #include <linux/notifier.h>
21 #include <linux/of.h>
22 #include <linux/platform_device.h>
23 #include <linux/reboot.h>
24 #include <linux/types.h>
25 #include <linux/watchdog.h>
26
27 #define DRV_NAME                "meson_wdt"
28
29 #define MESON_WDT_TC            0x00
30 #define MESON_WDT_TC_EN         BIT(22)
31 #define MESON_WDT_TC_TM_MASK    0x3fffff
32 #define MESON_WDT_DC_RESET      (3 << 24)
33
34 #define MESON_WDT_RESET         0x04
35
36 #define MESON_WDT_TIMEOUT       30
37 #define MESON_WDT_MIN_TIMEOUT   1
38 #define MESON_WDT_MAX_TIMEOUT   (MESON_WDT_TC_TM_MASK / 100000)
39
40 #define MESON_SEC_TO_TC(s)      ((s) * 100000)
41
42 static bool nowayout = WATCHDOG_NOWAYOUT;
43 static unsigned int timeout = MESON_WDT_TIMEOUT;
44
45 struct meson_wdt_dev {
46         struct watchdog_device wdt_dev;
47         void __iomem *wdt_base;
48         struct notifier_block restart_handler;
49 };
50
51 static int meson_restart_handle(struct notifier_block *this, unsigned long mode,
52                                 void *cmd)
53 {
54         u32 tc_reboot = MESON_WDT_DC_RESET | MESON_WDT_TC_EN;
55         struct meson_wdt_dev *meson_wdt = container_of(this,
56                                                        struct meson_wdt_dev,
57                                                        restart_handler);
58
59         while (1) {
60                 writel(tc_reboot, meson_wdt->wdt_base + MESON_WDT_TC);
61                 mdelay(5);
62         }
63
64         return NOTIFY_DONE;
65 }
66
67 static int meson_wdt_ping(struct watchdog_device *wdt_dev)
68 {
69         struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
70
71         writel(0, meson_wdt->wdt_base + MESON_WDT_RESET);
72
73         return 0;
74 }
75
76 static void meson_wdt_change_timeout(struct watchdog_device *wdt_dev,
77                                      unsigned int timeout)
78 {
79         struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
80         u32 reg;
81
82         reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
83         reg &= ~MESON_WDT_TC_TM_MASK;
84         reg |= MESON_SEC_TO_TC(timeout);
85         writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
86 }
87
88 static int meson_wdt_set_timeout(struct watchdog_device *wdt_dev,
89                                  unsigned int timeout)
90 {
91         wdt_dev->timeout = timeout;
92
93         meson_wdt_change_timeout(wdt_dev, timeout);
94         meson_wdt_ping(wdt_dev);
95
96         return 0;
97 }
98
99 static int meson_wdt_stop(struct watchdog_device *wdt_dev)
100 {
101         struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
102         u32 reg;
103
104         reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
105         reg &= ~MESON_WDT_TC_EN;
106         writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
107
108         return 0;
109 }
110
111 static int meson_wdt_start(struct watchdog_device *wdt_dev)
112 {
113         struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev);
114         u32 reg;
115
116         meson_wdt_change_timeout(wdt_dev, meson_wdt->wdt_dev.timeout);
117         meson_wdt_ping(wdt_dev);
118
119         reg = readl(meson_wdt->wdt_base + MESON_WDT_TC);
120         reg |= MESON_WDT_TC_EN;
121         writel(reg, meson_wdt->wdt_base + MESON_WDT_TC);
122
123         return 0;
124 }
125
126 static const struct watchdog_info meson_wdt_info = {
127         .identity       = DRV_NAME,
128         .options        = WDIOF_SETTIMEOUT |
129                           WDIOF_KEEPALIVEPING |
130                           WDIOF_MAGICCLOSE,
131 };
132
133 static const struct watchdog_ops meson_wdt_ops = {
134         .owner          = THIS_MODULE,
135         .start          = meson_wdt_start,
136         .stop           = meson_wdt_stop,
137         .ping           = meson_wdt_ping,
138         .set_timeout    = meson_wdt_set_timeout,
139 };
140
141 static int meson_wdt_probe(struct platform_device *pdev)
142 {
143         struct resource *res;
144         struct meson_wdt_dev *meson_wdt;
145         int err;
146
147         meson_wdt = devm_kzalloc(&pdev->dev, sizeof(*meson_wdt), GFP_KERNEL);
148         if (!meson_wdt)
149                 return -ENOMEM;
150
151         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
152         meson_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res);
153         if (IS_ERR(meson_wdt->wdt_base))
154                 return PTR_ERR(meson_wdt->wdt_base);
155
156         meson_wdt->wdt_dev.parent = &pdev->dev;
157         meson_wdt->wdt_dev.info = &meson_wdt_info;
158         meson_wdt->wdt_dev.ops = &meson_wdt_ops;
159         meson_wdt->wdt_dev.timeout = MESON_WDT_TIMEOUT;
160         meson_wdt->wdt_dev.max_timeout = MESON_WDT_MAX_TIMEOUT;
161         meson_wdt->wdt_dev.min_timeout = MESON_WDT_MIN_TIMEOUT;
162
163         watchdog_set_drvdata(&meson_wdt->wdt_dev, meson_wdt);
164
165         watchdog_init_timeout(&meson_wdt->wdt_dev, timeout, &pdev->dev);
166         watchdog_set_nowayout(&meson_wdt->wdt_dev, nowayout);
167
168         meson_wdt_stop(&meson_wdt->wdt_dev);
169
170         err = watchdog_register_device(&meson_wdt->wdt_dev);
171         if (err)
172                 return err;
173
174         platform_set_drvdata(pdev, meson_wdt);
175
176         meson_wdt->restart_handler.notifier_call = meson_restart_handle;
177         meson_wdt->restart_handler.priority = 128;
178         err = register_restart_handler(&meson_wdt->restart_handler);
179         if (err)
180                 dev_err(&pdev->dev,
181                         "cannot register restart handler (err=%d)\n", err);
182
183         dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
184                  meson_wdt->wdt_dev.timeout, nowayout);
185
186         return 0;
187 }
188
189 static int meson_wdt_remove(struct platform_device *pdev)
190 {
191         struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev);
192
193         unregister_restart_handler(&meson_wdt->restart_handler);
194
195         watchdog_unregister_device(&meson_wdt->wdt_dev);
196
197         return 0;
198 }
199
200 static void meson_wdt_shutdown(struct platform_device *pdev)
201 {
202         struct meson_wdt_dev *meson_wdt = platform_get_drvdata(pdev);
203
204         meson_wdt_stop(&meson_wdt->wdt_dev);
205 }
206
207 static const struct of_device_id meson_wdt_dt_ids[] = {
208         { .compatible = "amlogic,meson6-wdt" },
209         { /* sentinel */ }
210 };
211 MODULE_DEVICE_TABLE(of, meson_wdt_dt_ids);
212
213 static struct platform_driver meson_wdt_driver = {
214         .probe          = meson_wdt_probe,
215         .remove         = meson_wdt_remove,
216         .shutdown       = meson_wdt_shutdown,
217         .driver         = {
218                 .name           = DRV_NAME,
219                 .of_match_table = meson_wdt_dt_ids,
220         },
221 };
222
223 module_platform_driver(meson_wdt_driver);
224
225 module_param(timeout, uint, 0);
226 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
227
228 module_param(nowayout, bool, 0);
229 MODULE_PARM_DESC(nowayout,
230                  "Watchdog cannot be stopped once started (default="
231                  __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
232
233 MODULE_LICENSE("GPL");
234 MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
235 MODULE_DESCRIPTION("Meson Watchdog Timer Driver");