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 
asm9260_wdt_feed(struct watchdog_device * wdd)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 
asm9260_wdt_gettimeleft(struct watchdog_device * wdd)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 
asm9260_wdt_updatetimeout(struct watchdog_device * wdd)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 
asm9260_wdt_enable(struct watchdog_device * wdd)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 
asm9260_wdt_disable(struct watchdog_device * wdd)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 
asm9260_wdt_settimeout(struct watchdog_device * wdd,unsigned int to)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 
asm9260_wdt_sys_reset(struct asm9260_wdt_priv * priv)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 
asm9260_wdt_irq(int irq,void * devid)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 
asm9260_restart(struct watchdog_device * wdd,unsigned long action,void * data)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 
asm9260_clk_disable_unprepare(void * data)198 static void asm9260_clk_disable_unprepare(void *data)
199 {
200 	clk_disable_unprepare(data);
201 }
202 
asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv * priv)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 
asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv * priv)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 
asm9260_wdt_probe(struct platform_device * pdev)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");
377