diff options
author | Markus Mayer <markus.mayer@linaro.org> | 2013-11-22 17:56:03 -0500 |
---|---|---|
committer | Wim Van Sebroeck <wim@iguana.be> | 2014-01-28 15:13:50 -0500 |
commit | 6adb730dc2085c16c52a2f991cc1661e4a7fd6d5 (patch) | |
tree | 629e9b34c2d55eb452eeedcbe2a733c8272943e1 /drivers/watchdog/bcm_kona_wdt.c | |
parent | 2c34d59916bd82efe6544f39ec162e8c9236009d (diff) |
watchdog: bcm281xx: Watchdog Driver
This commit adds support for the watchdog timer used on the BCM281xx
family of SoCs.
Signed-off-by: Markus Mayer <markus.mayer@linaro.org>
Reviewed-by: Matt Porter <matt.porter@linaro.org>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>
Diffstat (limited to 'drivers/watchdog/bcm_kona_wdt.c')
-rw-r--r-- | drivers/watchdog/bcm_kona_wdt.c | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/drivers/watchdog/bcm_kona_wdt.c b/drivers/watchdog/bcm_kona_wdt.c new file mode 100644 index 000000000000..7e41a83eb45e --- /dev/null +++ b/drivers/watchdog/bcm_kona_wdt.c | |||
@@ -0,0 +1,268 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2013 Broadcom Corporation | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License as | ||
6 | * published by the Free Software Foundation version 2. | ||
7 | * | ||
8 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | ||
9 | * kind, whether express or implied; without even the implied warranty | ||
10 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | |||
14 | #include <linux/delay.h> | ||
15 | #include <linux/err.h> | ||
16 | #include <linux/io.h> | ||
17 | #include <linux/module.h> | ||
18 | #include <linux/of_address.h> | ||
19 | #include <linux/platform_device.h> | ||
20 | #include <linux/watchdog.h> | ||
21 | |||
22 | #define SECWDOG_CTRL_REG 0x00000000 | ||
23 | #define SECWDOG_COUNT_REG 0x00000004 | ||
24 | |||
25 | #define SECWDOG_RESERVED_MASK 0x1dffffff | ||
26 | #define SECWDOG_WD_LOAD_FLAG 0x10000000 | ||
27 | #define SECWDOG_EN_MASK 0x08000000 | ||
28 | #define SECWDOG_SRSTEN_MASK 0x04000000 | ||
29 | #define SECWDOG_RES_MASK 0x00f00000 | ||
30 | #define SECWDOG_COUNT_MASK 0x000fffff | ||
31 | |||
32 | #define SECWDOG_MAX_COUNT SECWDOG_COUNT_MASK | ||
33 | #define SECWDOG_CLKS_SHIFT 20 | ||
34 | #define SECWDOG_MAX_RES 15 | ||
35 | #define SECWDOG_DEFAULT_RESOLUTION 4 | ||
36 | #define SECWDOG_MAX_TRY 1000 | ||
37 | |||
38 | #define SECS_TO_TICKS(x, w) ((x) << (w)->resolution) | ||
39 | #define TICKS_TO_SECS(x, w) ((x) >> (w)->resolution) | ||
40 | |||
41 | #define BCM_KONA_WDT_NAME "bcm_kona_wdt" | ||
42 | |||
43 | struct bcm_kona_wdt { | ||
44 | void __iomem *base; | ||
45 | /* | ||
46 | * One watchdog tick is 1/(2^resolution) seconds. Resolution can take | ||
47 | * the values 0-15, meaning one tick can be 1s to 30.52us. Our default | ||
48 | * resolution of 4 means one tick is 62.5ms. | ||
49 | * | ||
50 | * The watchdog counter is 20 bits. Depending on resolution, the maximum | ||
51 | * counter value of 0xfffff expires after about 12 days (resolution 0) | ||
52 | * down to only 32s (resolution 15). The default resolution of 4 gives | ||
53 | * us a maximum of about 18 hours and 12 minutes before the watchdog | ||
54 | * times out. | ||
55 | */ | ||
56 | int resolution; | ||
57 | spinlock_t lock; | ||
58 | }; | ||
59 | |||
60 | static int secure_register_read(void __iomem *addr) | ||
61 | { | ||
62 | uint32_t val; | ||
63 | unsigned count = 0; | ||
64 | |||
65 | /* | ||
66 | * If the WD_LOAD_FLAG is set, the watchdog counter field is being | ||
67 | * updated in hardware. Once the WD timer is updated in hardware, it | ||
68 | * gets cleared. | ||
69 | */ | ||
70 | do { | ||
71 | if (unlikely(count > 1)) | ||
72 | udelay(5); | ||
73 | val = readl_relaxed(addr); | ||
74 | count++; | ||
75 | } while ((val & SECWDOG_WD_LOAD_FLAG) && count < SECWDOG_MAX_TRY); | ||
76 | |||
77 | /* This is the only place we return a negative value. */ | ||
78 | if (val & SECWDOG_WD_LOAD_FLAG) | ||
79 | return -ETIMEDOUT; | ||
80 | |||
81 | /* We always mask out reserved bits. */ | ||
82 | val &= SECWDOG_RESERVED_MASK; | ||
83 | |||
84 | return val; | ||
85 | } | ||
86 | |||
87 | static int bcm_kona_wdt_ctrl_reg_modify(struct bcm_kona_wdt *wdt, | ||
88 | unsigned mask, unsigned newval) | ||
89 | { | ||
90 | int val; | ||
91 | unsigned long flags; | ||
92 | int ret = 0; | ||
93 | |||
94 | spin_lock_irqsave(&wdt->lock, flags); | ||
95 | |||
96 | val = secure_register_read(wdt->base + SECWDOG_CTRL_REG); | ||
97 | if (val < 0) { | ||
98 | ret = val; | ||
99 | } else { | ||
100 | val &= ~mask; | ||
101 | val |= newval; | ||
102 | writel_relaxed(val, wdt->base + SECWDOG_CTRL_REG); | ||
103 | } | ||
104 | |||
105 | spin_unlock_irqrestore(&wdt->lock, flags); | ||
106 | |||
107 | return ret; | ||
108 | } | ||
109 | |||
110 | static int bcm_kona_wdt_set_resolution_reg(struct bcm_kona_wdt *wdt) | ||
111 | { | ||
112 | if (wdt->resolution > SECWDOG_MAX_RES) | ||
113 | return -EINVAL; | ||
114 | |||
115 | return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_RES_MASK, | ||
116 | wdt->resolution << SECWDOG_CLKS_SHIFT); | ||
117 | } | ||
118 | |||
119 | static int bcm_kona_wdt_set_timeout_reg(struct watchdog_device *wdog, | ||
120 | unsigned watchdog_flags) | ||
121 | { | ||
122 | struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog); | ||
123 | |||
124 | return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_COUNT_MASK, | ||
125 | SECS_TO_TICKS(wdog->timeout, wdt) | | ||
126 | watchdog_flags); | ||
127 | } | ||
128 | |||
129 | static int bcm_kona_wdt_set_timeout(struct watchdog_device *wdog, | ||
130 | unsigned int t) | ||
131 | { | ||
132 | wdog->timeout = t; | ||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | static unsigned int bcm_kona_wdt_get_timeleft(struct watchdog_device *wdog) | ||
137 | { | ||
138 | struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog); | ||
139 | int val; | ||
140 | unsigned long flags; | ||
141 | |||
142 | spin_lock_irqsave(&wdt->lock, flags); | ||
143 | val = secure_register_read(wdt->base + SECWDOG_COUNT_REG); | ||
144 | spin_unlock_irqrestore(&wdt->lock, flags); | ||
145 | |||
146 | if (val < 0) | ||
147 | return val; | ||
148 | |||
149 | return TICKS_TO_SECS(val & SECWDOG_COUNT_MASK, wdt); | ||
150 | } | ||
151 | |||
152 | static int bcm_kona_wdt_start(struct watchdog_device *wdog) | ||
153 | { | ||
154 | return bcm_kona_wdt_set_timeout_reg(wdog, | ||
155 | SECWDOG_EN_MASK | SECWDOG_SRSTEN_MASK); | ||
156 | } | ||
157 | |||
158 | static int bcm_kona_wdt_stop(struct watchdog_device *wdog) | ||
159 | { | ||
160 | struct bcm_kona_wdt *wdt = watchdog_get_drvdata(wdog); | ||
161 | |||
162 | return bcm_kona_wdt_ctrl_reg_modify(wdt, SECWDOG_EN_MASK | | ||
163 | SECWDOG_SRSTEN_MASK, 0); | ||
164 | } | ||
165 | |||
166 | static struct watchdog_ops bcm_kona_wdt_ops = { | ||
167 | .owner = THIS_MODULE, | ||
168 | .start = bcm_kona_wdt_start, | ||
169 | .stop = bcm_kona_wdt_stop, | ||
170 | .set_timeout = bcm_kona_wdt_set_timeout, | ||
171 | .get_timeleft = bcm_kona_wdt_get_timeleft, | ||
172 | }; | ||
173 | |||
174 | static struct watchdog_info bcm_kona_wdt_info = { | ||
175 | .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | | ||
176 | WDIOF_KEEPALIVEPING, | ||
177 | .identity = "Broadcom Kona Watchdog Timer", | ||
178 | }; | ||
179 | |||
180 | static struct watchdog_device bcm_kona_wdt_wdd = { | ||
181 | .info = &bcm_kona_wdt_info, | ||
182 | .ops = &bcm_kona_wdt_ops, | ||
183 | .min_timeout = 1, | ||
184 | .max_timeout = SECWDOG_MAX_COUNT >> SECWDOG_DEFAULT_RESOLUTION, | ||
185 | .timeout = SECWDOG_MAX_COUNT >> SECWDOG_DEFAULT_RESOLUTION, | ||
186 | }; | ||
187 | |||
188 | static void bcm_kona_wdt_shutdown(struct platform_device *pdev) | ||
189 | { | ||
190 | bcm_kona_wdt_stop(&bcm_kona_wdt_wdd); | ||
191 | } | ||
192 | |||
193 | static int bcm_kona_wdt_probe(struct platform_device *pdev) | ||
194 | { | ||
195 | struct device *dev = &pdev->dev; | ||
196 | struct bcm_kona_wdt *wdt; | ||
197 | struct resource *res; | ||
198 | int ret; | ||
199 | |||
200 | wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); | ||
201 | if (!wdt) | ||
202 | return -ENOMEM; | ||
203 | |||
204 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
205 | wdt->base = devm_ioremap_resource(dev, res); | ||
206 | if (IS_ERR(wdt->base)) | ||
207 | return -ENODEV; | ||
208 | |||
209 | wdt->resolution = SECWDOG_DEFAULT_RESOLUTION; | ||
210 | ret = bcm_kona_wdt_set_resolution_reg(wdt); | ||
211 | if (ret) { | ||
212 | dev_err(dev, "Failed to set resolution (error: %d)", ret); | ||
213 | return ret; | ||
214 | } | ||
215 | |||
216 | spin_lock_init(&wdt->lock); | ||
217 | platform_set_drvdata(pdev, wdt); | ||
218 | watchdog_set_drvdata(&bcm_kona_wdt_wdd, wdt); | ||
219 | |||
220 | ret = bcm_kona_wdt_set_timeout_reg(&bcm_kona_wdt_wdd, 0); | ||
221 | if (ret) { | ||
222 | dev_err(dev, "Failed set watchdog timeout"); | ||
223 | return ret; | ||
224 | } | ||
225 | |||
226 | ret = watchdog_register_device(&bcm_kona_wdt_wdd); | ||
227 | if (ret) { | ||
228 | dev_err(dev, "Failed to register watchdog device"); | ||
229 | return ret; | ||
230 | } | ||
231 | |||
232 | dev_dbg(dev, "Broadcom Kona Watchdog Timer"); | ||
233 | |||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static int bcm_kona_wdt_remove(struct platform_device *pdev) | ||
238 | { | ||
239 | bcm_kona_wdt_shutdown(pdev); | ||
240 | watchdog_unregister_device(&bcm_kona_wdt_wdd); | ||
241 | dev_dbg(&pdev->dev, "Watchdog driver disabled"); | ||
242 | |||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static const struct of_device_id bcm_kona_wdt_of_match[] = { | ||
247 | { .compatible = "brcm,kona-wdt", }, | ||
248 | {}, | ||
249 | }; | ||
250 | MODULE_DEVICE_TABLE(of, bcm_kona_wdt_of_match); | ||
251 | |||
252 | static struct platform_driver bcm_kona_wdt_driver = { | ||
253 | .driver = { | ||
254 | .name = BCM_KONA_WDT_NAME, | ||
255 | .owner = THIS_MODULE, | ||
256 | .of_match_table = bcm_kona_wdt_of_match, | ||
257 | }, | ||
258 | .probe = bcm_kona_wdt_probe, | ||
259 | .remove = bcm_kona_wdt_remove, | ||
260 | .shutdown = bcm_kona_wdt_shutdown, | ||
261 | }; | ||
262 | |||
263 | module_platform_driver(bcm_kona_wdt_driver); | ||
264 | |||
265 | MODULE_ALIAS("platform:" BCM_KONA_WDT_NAME); | ||
266 | MODULE_AUTHOR("Markus Mayer <mmayer@broadcom.com>"); | ||
267 | MODULE_DESCRIPTION("Broadcom Kona Watchdog Driver"); | ||
268 | MODULE_LICENSE("GPL v2"); | ||