• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *      sunxi Watchdog Driver
4  *
5  *      Copyright (c) 2013 Carlo Caione
6  *                    2012 Henrik Nordstrom
7  *
8  *      Based on xen_wdt.c
9  *      (c) Copyright 2010 Novell, Inc.
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/of.h>
21 #include <linux/of_device.h>
22 #include <linux/platform_device.h>
23 #include <linux/types.h>
24 #include <linux/watchdog.h>
25 
26 #define WDT_MAX_TIMEOUT         16
27 #define WDT_MIN_TIMEOUT         1
28 #define WDT_TIMEOUT_MASK        0x0F
29 
30 #define WDT_CTRL_RELOAD         ((1 << 0) | (0x0a57 << 1))
31 
32 #define WDT_MODE_EN             (1 << 0)
33 #define KEY_FIELD_MAGIC		(0x16AA0000)
34 
35 
36 #define DRV_NAME		"sunxi-wdt"
37 #define DRV_VERSION		"1.0.1"
38 
39 static bool nowayout = WATCHDOG_NOWAYOUT;
40 static unsigned int timeout;
41 
42 /*
43  * This structure stores the register offsets for different variants
44  * of Allwinner's watchdog hardware.
45  */
46 struct sunxi_wdt_reg {
47 	u8 wdt_ctrl;  /* Offset to WDOG_CTRL_REG */
48 	u8 wdt_cfg;   /* Offset to WDOG_CFG_REG */
49 	u8 wdt_mode;  /* Offset to WDOG_MODE_REG */
50 	u8 wdt_timeout_shift;  /* Bit offset of WDOG_INTV_VALUE in WDOG_MODE_REG */
51 	u8 wdt_reset_mask;  /* Bit mask of WDOG_CONFIG in WDOG_CFG_REG */
52 	u8 wdt_reset_val;   /* Value to reset whole system of WDOG_CONFIG in WDOG_CFG_REG */
53 };
54 
55 struct sunxi_wdt_dev {
56 	struct watchdog_device wdt_dev;
57 	void __iomem *wdt_base;
58 	const struct sunxi_wdt_reg *wdt_regs;
59 };
60 
61 /*
62  * wdt_timeout_map maps the watchdog timer interval value in seconds to
63  * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3
64  *
65  * [timeout seconds] = register value
66  *
67  */
68 
69 static const int wdt_timeout_map[] = {
70 	[1] = 0x1,  /* 1s  */
71 	[2] = 0x2,  /* 2s  */
72 	[3] = 0x3,  /* 3s  */
73 	[4] = 0x4,  /* 4s  */
74 	[5] = 0x5,  /* 5s  */
75 	[6] = 0x6,  /* 6s  */
76 	[8] = 0x7,  /* 8s  */
77 	[10] = 0x8, /* 10s */
78 	[12] = 0x9, /* 12s */
79 	[14] = 0xA, /* 14s */
80 	[16] = 0xB, /* 16s */
81 };
82 
83 
sunxi_wdt_restart(struct watchdog_device * wdt_dev,unsigned long action,void * data)84 static int sunxi_wdt_restart(struct watchdog_device *wdt_dev,
85 			     unsigned long action, void *data)
86 {
87 	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
88 	void __iomem *wdt_base = sunxi_wdt->wdt_base;
89 	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
90 	u32 val;
91 
92 	/* Set system reset function */
93 	val = readl(wdt_base + regs->wdt_cfg);
94 	val &= ~(regs->wdt_reset_mask);
95 	val |= regs->wdt_reset_val | KEY_FIELD_MAGIC;
96 	writel(val, wdt_base + regs->wdt_cfg);
97 
98 	/* Set lowest timeout and enable watchdog */
99 	val = readl(wdt_base + regs->wdt_mode);
100 	val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
101 	val |= WDT_MODE_EN | KEY_FIELD_MAGIC;
102 	writel(val, wdt_base + regs->wdt_mode);
103 
104 	/*
105 	 * Restart the watchdog. The default (and lowest) interval
106 	 * value for the watchdog is 0.5s.
107 	 */
108 	writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
109 
110 	while (1) {
111 		mdelay(5);
112 	}
113 	return 0;
114 }
115 
sunxi_wdt_ping(struct watchdog_device * wdt_dev)116 static int sunxi_wdt_ping(struct watchdog_device *wdt_dev)
117 {
118 	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
119 	void __iomem *wdt_base = sunxi_wdt->wdt_base;
120 	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
121 
122 	writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl);
123 
124 	return 0;
125 }
126 
sunxi_wdt_set_timeout(struct watchdog_device * wdt_dev,unsigned int timeout)127 static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev,
128 		unsigned int timeout)
129 {
130 	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
131 	void __iomem *wdt_base = sunxi_wdt->wdt_base;
132 	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
133 	u32 reg;
134 
135 	if (wdt_timeout_map[timeout] == 0)
136 		timeout++;
137 
138 	sunxi_wdt->wdt_dev.timeout = timeout;
139 
140 	reg = readl(wdt_base + regs->wdt_mode);
141 	reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift);
142 	reg |= (wdt_timeout_map[timeout] << regs->wdt_timeout_shift) |
143 		KEY_FIELD_MAGIC;
144 	writel(reg, wdt_base + regs->wdt_mode);
145 
146 	sunxi_wdt_ping(wdt_dev);
147 
148 	return 0;
149 }
150 
sunxi_wdt_stop(struct watchdog_device * wdt_dev)151 static int sunxi_wdt_stop(struct watchdog_device *wdt_dev)
152 {
153 	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
154 	void __iomem *wdt_base = sunxi_wdt->wdt_base;
155 	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
156 
157 	writel((0 | KEY_FIELD_MAGIC), wdt_base + regs->wdt_mode);
158 
159 	return 0;
160 }
161 
sunxi_wdt_start(struct watchdog_device * wdt_dev)162 static int sunxi_wdt_start(struct watchdog_device *wdt_dev)
163 {
164 	u32 reg;
165 	struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev);
166 	void __iomem *wdt_base = sunxi_wdt->wdt_base;
167 	const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs;
168 	int ret;
169 
170 	ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev,
171 			sunxi_wdt->wdt_dev.timeout);
172 	if (ret < 0)
173 		return ret;
174 
175 	/* Set system reset function */
176 	reg = readl(wdt_base + regs->wdt_cfg);
177 	reg &= ~(regs->wdt_reset_mask);
178 	reg |= regs->wdt_reset_val | KEY_FIELD_MAGIC;
179 	writel(reg, wdt_base + regs->wdt_cfg);
180 
181 	/* Enable watchdog */
182 	reg = readl(wdt_base + regs->wdt_mode);
183 	reg |= WDT_MODE_EN | KEY_FIELD_MAGIC;
184 	writel(reg, wdt_base + regs->wdt_mode);
185 
186 	return 0;
187 }
188 
189 #ifdef CONFIG_PM
sunxi_wdt_suspend(struct platform_device * pdev,pm_message_t state)190 static int sunxi_wdt_suspend(struct platform_device *pdev, pm_message_t state)
191 {
192 	struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev);
193 	if (watchdog_active(&sunxi_wdt->wdt_dev))
194 		sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
195 
196 	return 0;
197 }
198 
sunxi_wdt_resume(struct platform_device * pdev)199 static int sunxi_wdt_resume(struct platform_device *pdev)
200 {
201 	struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev);
202 	if (watchdog_active(&sunxi_wdt->wdt_dev)) {
203 		sunxi_wdt_start(&sunxi_wdt->wdt_dev);
204 	}
205 
206 	return 0;
207 }
208 #endif
209 
210 static const struct watchdog_info sunxi_wdt_info = {
211 	.identity	= DRV_NAME,
212 	.options	= WDIOF_SETTIMEOUT |
213 			  WDIOF_KEEPALIVEPING |
214 			  WDIOF_MAGICCLOSE,
215 };
216 
217 static const struct watchdog_ops sunxi_wdt_ops = {
218 	.owner		= THIS_MODULE,
219 	.start		= sunxi_wdt_start,
220 	.stop		= sunxi_wdt_stop,
221 	.ping		= sunxi_wdt_ping,
222 	.set_timeout	= sunxi_wdt_set_timeout,
223 	.restart	= sunxi_wdt_restart,
224 };
225 
226 static const struct sunxi_wdt_reg sun4i_wdt_reg = {
227 	.wdt_ctrl = 0x00,
228 	.wdt_cfg = 0x04,
229 	.wdt_mode = 0x04,
230 	.wdt_timeout_shift = 3,
231 	.wdt_reset_mask = 0x02,
232 	.wdt_reset_val = 0x02,
233 };
234 
235 static const struct sunxi_wdt_reg sun6i_wdt_reg = {
236 	.wdt_ctrl = 0x10,
237 	.wdt_cfg = 0x14,
238 	.wdt_mode = 0x18,
239 	.wdt_timeout_shift = 4,
240 	.wdt_reset_mask = 0x03,
241 	.wdt_reset_val = 0x01,
242 };
243 
244 static const struct sunxi_wdt_reg sun50i_wdt_reg = {
245 	.wdt_ctrl = 0xb0,
246 	.wdt_cfg = 0xb4,
247 	.wdt_mode = 0xb8,
248 	.wdt_timeout_shift = 4,
249 	.wdt_reset_mask = 0x03,
250 	.wdt_reset_val = 0x01,
251 };
252 
253 static const struct of_device_id sunxi_wdt_dt_ids[] = {
254 	{ .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg },
255 	{ .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg },
256 	{ .compatible = "allwinner,sun50i-wdt", .data = &sun50i_wdt_reg },
257 	{ /* sentinel */ }
258 };
259 MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids);
260 
sunxi_wdt_probe(struct platform_device * pdev)261 static int sunxi_wdt_probe(struct platform_device *pdev)
262 {
263 	struct device *dev = &pdev->dev;
264 	struct sunxi_wdt_dev *sunxi_wdt;
265 	int err;
266 
267 	sunxi_wdt = devm_kzalloc(dev, sizeof(*sunxi_wdt), GFP_KERNEL);
268 	if (!sunxi_wdt)
269 		return -ENOMEM;
270 
271 	platform_set_drvdata(pdev, sunxi_wdt);
272 	sunxi_wdt->wdt_regs = of_device_get_match_data(dev);
273 	if (!sunxi_wdt->wdt_regs)
274 		return -ENODEV;
275 
276 	sunxi_wdt->wdt_base = devm_platform_ioremap_resource(pdev, 0);
277 	if (IS_ERR(sunxi_wdt->wdt_base))
278 		return PTR_ERR(sunxi_wdt->wdt_base);
279 
280 	sunxi_wdt->wdt_dev.info = &sunxi_wdt_info;
281 	sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops;
282 	sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
283 	sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
284 	sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
285 	sunxi_wdt->wdt_dev.parent = dev;
286 
287 	watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, dev);
288 	watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout);
289 	watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128);
290 
291 	watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt);
292 
293 	sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
294 
295 	watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev);
296 	err = devm_watchdog_register_device(dev, &sunxi_wdt->wdt_dev);
297 	if (unlikely(err))
298 		return err;
299 
300 	dev_info(dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)",
301 		 sunxi_wdt->wdt_dev.timeout, nowayout);
302 
303 	return 0;
304 }
305 
sunxi_wdt_shutdown(struct platform_device * pdev)306 static void sunxi_wdt_shutdown(struct platform_device *pdev)
307 {
308 	struct sunxi_wdt_dev *sunxi_wdt = platform_get_drvdata(pdev);
309 
310 	sunxi_wdt_stop(&sunxi_wdt->wdt_dev);
311 }
312 
313 static struct platform_driver sunxi_wdt_driver = {
314 	.probe		= sunxi_wdt_probe,
315 	.shutdown       = sunxi_wdt_shutdown,
316 	.driver		= {
317 		.name		= DRV_NAME,
318 		.of_match_table	= sunxi_wdt_dt_ids,
319 	},
320 #ifdef CONFIG_PM
321 	.suspend        = sunxi_wdt_suspend,
322 	.resume         = sunxi_wdt_resume,
323 #endif
324 };
325 
326 module_platform_driver(sunxi_wdt_driver);
327 
328 module_param(timeout, uint, 0);
329 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds");
330 
331 module_param(nowayout, bool, 0);
332 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
333 		"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
334 
335 MODULE_LICENSE("GPL");
336 MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>");
337 MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>");
338 MODULE_DESCRIPTION("sunxi WatchDog Timer Driver");
339 MODULE_VERSION(DRV_VERSION);
340