xref: /openbmc/linux/drivers/watchdog/ar7_wdt.c (revision 2d991a16)
1c283cf2cSMatteo Croce /*
2c283cf2cSMatteo Croce  * drivers/watchdog/ar7_wdt.c
3c283cf2cSMatteo Croce  *
4c283cf2cSMatteo Croce  * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
5c283cf2cSMatteo Croce  * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
6c283cf2cSMatteo Croce  *
7c283cf2cSMatteo Croce  * Some code taken from:
8c283cf2cSMatteo Croce  * National Semiconductor SCx200 Watchdog support
9c283cf2cSMatteo Croce  * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
10c283cf2cSMatteo Croce  *
11c283cf2cSMatteo Croce  * This program is free software; you can redistribute it and/or modify
12c283cf2cSMatteo Croce  * it under the terms of the GNU General Public License as published by
13c283cf2cSMatteo Croce  * the Free Software Foundation; either version 2 of the License, or
14c283cf2cSMatteo Croce  * (at your option) any later version.
15c283cf2cSMatteo Croce  *
16c283cf2cSMatteo Croce  * This program is distributed in the hope that it will be useful,
17c283cf2cSMatteo Croce  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18c283cf2cSMatteo Croce  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19c283cf2cSMatteo Croce  * GNU General Public License for more details.
20c283cf2cSMatteo Croce  *
21c283cf2cSMatteo Croce  * You should have received a copy of the GNU General Public License
22c283cf2cSMatteo Croce  * along with this program; if not, write to the Free Software
23c283cf2cSMatteo Croce  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
24c283cf2cSMatteo Croce  */
25c283cf2cSMatteo Croce 
2627c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2727c766aaSJoe Perches 
28c283cf2cSMatteo Croce #include <linux/module.h>
29c283cf2cSMatteo Croce #include <linux/moduleparam.h>
30c283cf2cSMatteo Croce #include <linux/errno.h>
31c283cf2cSMatteo Croce #include <linux/init.h>
32c283cf2cSMatteo Croce #include <linux/miscdevice.h>
3364d4062aSFlorian Fainelli #include <linux/platform_device.h>
34c283cf2cSMatteo Croce #include <linux/watchdog.h>
35c283cf2cSMatteo Croce #include <linux/fs.h>
36c283cf2cSMatteo Croce #include <linux/ioport.h>
37c283cf2cSMatteo Croce #include <linux/io.h>
38c283cf2cSMatteo Croce #include <linux/uaccess.h>
39780019ddSFlorian Fainelli #include <linux/clk.h>
40c283cf2cSMatteo Croce 
41c283cf2cSMatteo Croce #include <asm/addrspace.h>
42c5e7f5a3SFlorian Fainelli #include <asm/mach-ar7/ar7.h>
43c283cf2cSMatteo Croce 
44c283cf2cSMatteo Croce #define LONGNAME "TI AR7 Watchdog Timer"
45c283cf2cSMatteo Croce 
46c283cf2cSMatteo Croce MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
47c283cf2cSMatteo Croce MODULE_DESCRIPTION(LONGNAME);
48c283cf2cSMatteo Croce MODULE_LICENSE("GPL");
49c283cf2cSMatteo Croce MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
50c283cf2cSMatteo Croce 
51c283cf2cSMatteo Croce static int margin = 60;
52c283cf2cSMatteo Croce module_param(margin, int, 0);
53c283cf2cSMatteo Croce MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
54c283cf2cSMatteo Croce 
5586a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
5686a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
57c283cf2cSMatteo Croce MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
58c283cf2cSMatteo Croce 
59c283cf2cSMatteo Croce #define READ_REG(x) readl((void __iomem *)&(x))
60c283cf2cSMatteo Croce #define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
61c283cf2cSMatteo Croce 
62c283cf2cSMatteo Croce struct ar7_wdt {
63c283cf2cSMatteo Croce 	u32 kick_lock;
64c283cf2cSMatteo Croce 	u32 kick;
65c283cf2cSMatteo Croce 	u32 change_lock;
66c283cf2cSMatteo Croce 	u32 change;
67c283cf2cSMatteo Croce 	u32 disable_lock;
68c283cf2cSMatteo Croce 	u32 disable;
69c283cf2cSMatteo Croce 	u32 prescale_lock;
70c283cf2cSMatteo Croce 	u32 prescale;
71c283cf2cSMatteo Croce };
72c283cf2cSMatteo Croce 
73670d59c0SAlan Cox static unsigned long wdt_is_open;
74c283cf2cSMatteo Croce static unsigned expect_close;
751334f329SAxel Lin static DEFINE_SPINLOCK(wdt_lock);
76c283cf2cSMatteo Croce 
77c283cf2cSMatteo Croce /* XXX currently fixed, allows max margin ~68.72 secs */
78c283cf2cSMatteo Croce #define prescale_value 0xffff
79c283cf2cSMatteo Croce 
8064d4062aSFlorian Fainelli /* Resource of the WDT registers */
8164d4062aSFlorian Fainelli static struct resource *ar7_regs_wdt;
82c283cf2cSMatteo Croce /* Pointer to the remapped WDT IO space */
83c283cf2cSMatteo Croce static struct ar7_wdt *ar7_wdt;
84c283cf2cSMatteo Croce 
85780019ddSFlorian Fainelli static struct clk *vbus_clk;
86780019ddSFlorian Fainelli 
87c283cf2cSMatteo Croce static void ar7_wdt_kick(u32 value)
88c283cf2cSMatteo Croce {
89c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->kick_lock, 0x5555);
90c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
91c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
92c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
93c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->kick, value);
94c283cf2cSMatteo Croce 			return;
95c283cf2cSMatteo Croce 		}
96c283cf2cSMatteo Croce 	}
9727c766aaSJoe Perches 	pr_err("failed to unlock WDT kick reg\n");
98c283cf2cSMatteo Croce }
99c283cf2cSMatteo Croce 
100c283cf2cSMatteo Croce static void ar7_wdt_prescale(u32 value)
101c283cf2cSMatteo Croce {
102c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
103c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
104c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
105c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
106c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->prescale, value);
107c283cf2cSMatteo Croce 			return;
108c283cf2cSMatteo Croce 		}
109c283cf2cSMatteo Croce 	}
11027c766aaSJoe Perches 	pr_err("failed to unlock WDT prescale reg\n");
111c283cf2cSMatteo Croce }
112c283cf2cSMatteo Croce 
113c283cf2cSMatteo Croce static void ar7_wdt_change(u32 value)
114c283cf2cSMatteo Croce {
115c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->change_lock, 0x6666);
116c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
117c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
118c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
119c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->change, value);
120c283cf2cSMatteo Croce 			return;
121c283cf2cSMatteo Croce 		}
122c283cf2cSMatteo Croce 	}
12327c766aaSJoe Perches 	pr_err("failed to unlock WDT change reg\n");
124c283cf2cSMatteo Croce }
125c283cf2cSMatteo Croce 
126c283cf2cSMatteo Croce static void ar7_wdt_disable(u32 value)
127c283cf2cSMatteo Croce {
128c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->disable_lock, 0x7777);
129c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
130c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
131c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
132c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
133c283cf2cSMatteo Croce 			if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
134c283cf2cSMatteo Croce 				WRITE_REG(ar7_wdt->disable, value);
135c283cf2cSMatteo Croce 				return;
136c283cf2cSMatteo Croce 			}
137c283cf2cSMatteo Croce 		}
138c283cf2cSMatteo Croce 	}
13927c766aaSJoe Perches 	pr_err("failed to unlock WDT disable reg\n");
140c283cf2cSMatteo Croce }
141c283cf2cSMatteo Croce 
142c283cf2cSMatteo Croce static void ar7_wdt_update_margin(int new_margin)
143c283cf2cSMatteo Croce {
144c283cf2cSMatteo Croce 	u32 change;
145780019ddSFlorian Fainelli 	u32 vbus_rate;
146c283cf2cSMatteo Croce 
147780019ddSFlorian Fainelli 	vbus_rate = clk_get_rate(vbus_clk);
148780019ddSFlorian Fainelli 	change = new_margin * (vbus_rate / prescale_value);
149670d59c0SAlan Cox 	if (change < 1)
150670d59c0SAlan Cox 		change = 1;
151670d59c0SAlan Cox 	if (change > 0xffff)
152670d59c0SAlan Cox 		change = 0xffff;
153c283cf2cSMatteo Croce 	ar7_wdt_change(change);
154780019ddSFlorian Fainelli 	margin = change * prescale_value / vbus_rate;
15527c766aaSJoe Perches 	pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
156780019ddSFlorian Fainelli 		margin, prescale_value, change, vbus_rate);
157c283cf2cSMatteo Croce }
158c283cf2cSMatteo Croce 
159c283cf2cSMatteo Croce static void ar7_wdt_enable_wdt(void)
160c283cf2cSMatteo Croce {
16127c766aaSJoe Perches 	pr_debug("enabling watchdog timer\n");
162c283cf2cSMatteo Croce 	ar7_wdt_disable(1);
163c283cf2cSMatteo Croce 	ar7_wdt_kick(1);
164c283cf2cSMatteo Croce }
165c283cf2cSMatteo Croce 
166c283cf2cSMatteo Croce static void ar7_wdt_disable_wdt(void)
167c283cf2cSMatteo Croce {
16827c766aaSJoe Perches 	pr_debug("disabling watchdog timer\n");
169c283cf2cSMatteo Croce 	ar7_wdt_disable(0);
170c283cf2cSMatteo Croce }
171c283cf2cSMatteo Croce 
172c283cf2cSMatteo Croce static int ar7_wdt_open(struct inode *inode, struct file *file)
173c283cf2cSMatteo Croce {
174c283cf2cSMatteo Croce 	/* only allow one at a time */
175670d59c0SAlan Cox 	if (test_and_set_bit(0, &wdt_is_open))
176c283cf2cSMatteo Croce 		return -EBUSY;
177c283cf2cSMatteo Croce 	ar7_wdt_enable_wdt();
178c283cf2cSMatteo Croce 	expect_close = 0;
179c283cf2cSMatteo Croce 
180c283cf2cSMatteo Croce 	return nonseekable_open(inode, file);
181c283cf2cSMatteo Croce }
182c283cf2cSMatteo Croce 
183c283cf2cSMatteo Croce static int ar7_wdt_release(struct inode *inode, struct file *file)
184c283cf2cSMatteo Croce {
185c283cf2cSMatteo Croce 	if (!expect_close)
18627c766aaSJoe Perches 		pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
187c283cf2cSMatteo Croce 	else if (!nowayout)
188c283cf2cSMatteo Croce 		ar7_wdt_disable_wdt();
189670d59c0SAlan Cox 	clear_bit(0, &wdt_is_open);
190c283cf2cSMatteo Croce 	return 0;
191c283cf2cSMatteo Croce }
192c283cf2cSMatteo Croce 
193c283cf2cSMatteo Croce static ssize_t ar7_wdt_write(struct file *file, const char *data,
194c283cf2cSMatteo Croce 			     size_t len, loff_t *ppos)
195c283cf2cSMatteo Croce {
196c283cf2cSMatteo Croce 	/* check for a magic close character */
197c283cf2cSMatteo Croce 	if (len) {
198c283cf2cSMatteo Croce 		size_t i;
199c283cf2cSMatteo Croce 
200670d59c0SAlan Cox 		spin_lock(&wdt_lock);
201c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
202670d59c0SAlan Cox 		spin_unlock(&wdt_lock);
203c283cf2cSMatteo Croce 
204c283cf2cSMatteo Croce 		expect_close = 0;
205c283cf2cSMatteo Croce 		for (i = 0; i < len; ++i) {
206c283cf2cSMatteo Croce 			char c;
207c283cf2cSMatteo Croce 			if (get_user(c, data + i))
208c283cf2cSMatteo Croce 				return -EFAULT;
209c283cf2cSMatteo Croce 			if (c == 'V')
210c283cf2cSMatteo Croce 				expect_close = 1;
211c283cf2cSMatteo Croce 		}
212c283cf2cSMatteo Croce 
213c283cf2cSMatteo Croce 	}
214c283cf2cSMatteo Croce 	return len;
215c283cf2cSMatteo Croce }
216c283cf2cSMatteo Croce 
217670d59c0SAlan Cox static long ar7_wdt_ioctl(struct file *file,
218c283cf2cSMatteo Croce 					unsigned int cmd, unsigned long arg)
219c283cf2cSMatteo Croce {
22042747d71SWim Van Sebroeck 	static const struct watchdog_info ident = {
221c283cf2cSMatteo Croce 		.identity = LONGNAME,
222c283cf2cSMatteo Croce 		.firmware_version = 1,
223e73a7802SWim Van Sebroeck 		.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
224e73a7802SWim Van Sebroeck 						WDIOF_MAGICCLOSE),
225c283cf2cSMatteo Croce 	};
226c283cf2cSMatteo Croce 	int new_margin;
227c283cf2cSMatteo Croce 
228c283cf2cSMatteo Croce 	switch (cmd) {
229c283cf2cSMatteo Croce 	case WDIOC_GETSUPPORT:
230c283cf2cSMatteo Croce 		if (copy_to_user((struct watchdog_info *)arg, &ident,
231c283cf2cSMatteo Croce 				sizeof(ident)))
232c283cf2cSMatteo Croce 			return -EFAULT;
233c283cf2cSMatteo Croce 		return 0;
234c283cf2cSMatteo Croce 	case WDIOC_GETSTATUS:
235c283cf2cSMatteo Croce 	case WDIOC_GETBOOTSTATUS:
236c283cf2cSMatteo Croce 		if (put_user(0, (int *)arg))
237c283cf2cSMatteo Croce 			return -EFAULT;
238c283cf2cSMatteo Croce 		return 0;
239c283cf2cSMatteo Croce 	case WDIOC_KEEPALIVE:
240c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
241c283cf2cSMatteo Croce 		return 0;
242c283cf2cSMatteo Croce 	case WDIOC_SETTIMEOUT:
243c283cf2cSMatteo Croce 		if (get_user(new_margin, (int *)arg))
244c283cf2cSMatteo Croce 			return -EFAULT;
245c283cf2cSMatteo Croce 		if (new_margin < 1)
246c283cf2cSMatteo Croce 			return -EINVAL;
247c283cf2cSMatteo Croce 
248670d59c0SAlan Cox 		spin_lock(&wdt_lock);
249c283cf2cSMatteo Croce 		ar7_wdt_update_margin(new_margin);
250c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
251670d59c0SAlan Cox 		spin_unlock(&wdt_lock);
252c283cf2cSMatteo Croce 
253c283cf2cSMatteo Croce 	case WDIOC_GETTIMEOUT:
254c283cf2cSMatteo Croce 		if (put_user(margin, (int *)arg))
255c283cf2cSMatteo Croce 			return -EFAULT;
256c283cf2cSMatteo Croce 		return 0;
2570c06090cSWim Van Sebroeck 	default:
2580c06090cSWim Van Sebroeck 		return -ENOTTY;
259c283cf2cSMatteo Croce 	}
260c283cf2cSMatteo Croce }
261c283cf2cSMatteo Croce 
262b47a166eSJan Engelhardt static const struct file_operations ar7_wdt_fops = {
263c283cf2cSMatteo Croce 	.owner		= THIS_MODULE,
264c283cf2cSMatteo Croce 	.write		= ar7_wdt_write,
265670d59c0SAlan Cox 	.unlocked_ioctl	= ar7_wdt_ioctl,
266c283cf2cSMatteo Croce 	.open		= ar7_wdt_open,
267c283cf2cSMatteo Croce 	.release	= ar7_wdt_release,
2686038f373SArnd Bergmann 	.llseek		= no_llseek,
269c283cf2cSMatteo Croce };
270c283cf2cSMatteo Croce 
271c283cf2cSMatteo Croce static struct miscdevice ar7_wdt_miscdev = {
272c283cf2cSMatteo Croce 	.minor		= WATCHDOG_MINOR,
273c283cf2cSMatteo Croce 	.name		= "watchdog",
274c283cf2cSMatteo Croce 	.fops		= &ar7_wdt_fops,
275c283cf2cSMatteo Croce };
276c283cf2cSMatteo Croce 
2772d991a16SBill Pemberton static int ar7_wdt_probe(struct platform_device *pdev)
278c283cf2cSMatteo Croce {
279c283cf2cSMatteo Croce 	int rc;
280c283cf2cSMatteo Croce 
28164d4062aSFlorian Fainelli 	ar7_regs_wdt =
28264d4062aSFlorian Fainelli 		platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
28364d4062aSFlorian Fainelli 	if (!ar7_regs_wdt) {
28427c766aaSJoe Perches 		pr_err("could not get registers resource\n");
285ae21cc20SJulia Lawall 		return -ENODEV;
286c283cf2cSMatteo Croce 	}
287c283cf2cSMatteo Croce 
288ae21cc20SJulia Lawall 	ar7_wdt = devm_request_and_ioremap(&pdev->dev, ar7_regs_wdt);
28964d4062aSFlorian Fainelli 	if (!ar7_wdt) {
29027c766aaSJoe Perches 		pr_err("could not ioremap registers\n");
291ae21cc20SJulia Lawall 		return -ENXIO;
29264d4062aSFlorian Fainelli 	}
293c283cf2cSMatteo Croce 
294780019ddSFlorian Fainelli 	vbus_clk = clk_get(NULL, "vbus");
295780019ddSFlorian Fainelli 	if (IS_ERR(vbus_clk)) {
29627c766aaSJoe Perches 		pr_err("could not get vbus clock\n");
297ae21cc20SJulia Lawall 		return PTR_ERR(vbus_clk);
298780019ddSFlorian Fainelli 	}
299780019ddSFlorian Fainelli 
300c283cf2cSMatteo Croce 	ar7_wdt_disable_wdt();
301c283cf2cSMatteo Croce 	ar7_wdt_prescale(prescale_value);
302c283cf2cSMatteo Croce 	ar7_wdt_update_margin(margin);
303c283cf2cSMatteo Croce 
304c283cf2cSMatteo Croce 	rc = misc_register(&ar7_wdt_miscdev);
305c283cf2cSMatteo Croce 	if (rc) {
30627c766aaSJoe Perches 		pr_err("unable to register misc device\n");
307c283cf2cSMatteo Croce 		goto out;
308ae21cc20SJulia Lawall 	}
309ae21cc20SJulia Lawall 	return 0;
310c283cf2cSMatteo Croce 
311c283cf2cSMatteo Croce out:
312ae21cc20SJulia Lawall 	clk_put(vbus_clk);
313ae21cc20SJulia Lawall 	vbus_clk = NULL;
314c283cf2cSMatteo Croce 	return rc;
315c283cf2cSMatteo Croce }
316c283cf2cSMatteo Croce 
31764d4062aSFlorian Fainelli static int __devexit ar7_wdt_remove(struct platform_device *pdev)
318c283cf2cSMatteo Croce {
319c283cf2cSMatteo Croce 	misc_deregister(&ar7_wdt_miscdev);
320ae21cc20SJulia Lawall 	clk_put(vbus_clk);
321ae21cc20SJulia Lawall 	vbus_clk = NULL;
32264d4062aSFlorian Fainelli 	return 0;
32364d4062aSFlorian Fainelli }
32464d4062aSFlorian Fainelli 
32564d4062aSFlorian Fainelli static void ar7_wdt_shutdown(struct platform_device *pdev)
32664d4062aSFlorian Fainelli {
32764d4062aSFlorian Fainelli 	if (!nowayout)
32864d4062aSFlorian Fainelli 		ar7_wdt_disable_wdt();
32964d4062aSFlorian Fainelli }
33064d4062aSFlorian Fainelli 
33164d4062aSFlorian Fainelli static struct platform_driver ar7_wdt_driver = {
33264d4062aSFlorian Fainelli 	.probe = ar7_wdt_probe,
33382268714SBill Pemberton 	.remove = ar7_wdt_remove,
33464d4062aSFlorian Fainelli 	.shutdown = ar7_wdt_shutdown,
33564d4062aSFlorian Fainelli 	.driver = {
33664d4062aSFlorian Fainelli 		.owner = THIS_MODULE,
33764d4062aSFlorian Fainelli 		.name = "ar7_wdt",
33864d4062aSFlorian Fainelli 	},
33964d4062aSFlorian Fainelli };
34064d4062aSFlorian Fainelli 
341b8ec6118SAxel Lin module_platform_driver(ar7_wdt_driver);
342