xref: /openbmc/linux/drivers/watchdog/ar7_wdt.c (revision 2e62c498)
12e62c498SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0+
2c283cf2cSMatteo Croce /*
3c283cf2cSMatteo Croce  * drivers/watchdog/ar7_wdt.c
4c283cf2cSMatteo Croce  *
5c283cf2cSMatteo Croce  * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
6c283cf2cSMatteo Croce  * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
7c283cf2cSMatteo Croce  *
8c283cf2cSMatteo Croce  * Some code taken from:
9c283cf2cSMatteo Croce  * National Semiconductor SCx200 Watchdog support
10c283cf2cSMatteo Croce  * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
11c283cf2cSMatteo Croce  *
12c283cf2cSMatteo Croce  */
13c283cf2cSMatteo Croce 
1427c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1527c766aaSJoe Perches 
16c283cf2cSMatteo Croce #include <linux/module.h>
17c283cf2cSMatteo Croce #include <linux/moduleparam.h>
18c283cf2cSMatteo Croce #include <linux/errno.h>
19c283cf2cSMatteo Croce #include <linux/miscdevice.h>
2064d4062aSFlorian Fainelli #include <linux/platform_device.h>
21c283cf2cSMatteo Croce #include <linux/watchdog.h>
22c283cf2cSMatteo Croce #include <linux/fs.h>
23c283cf2cSMatteo Croce #include <linux/ioport.h>
24c283cf2cSMatteo Croce #include <linux/io.h>
25c283cf2cSMatteo Croce #include <linux/uaccess.h>
26780019ddSFlorian Fainelli #include <linux/clk.h>
27c283cf2cSMatteo Croce 
28c283cf2cSMatteo Croce #include <asm/addrspace.h>
29c5e7f5a3SFlorian Fainelli #include <asm/mach-ar7/ar7.h>
30c283cf2cSMatteo Croce 
31c283cf2cSMatteo Croce #define LONGNAME "TI AR7 Watchdog Timer"
32c283cf2cSMatteo Croce 
33c283cf2cSMatteo Croce MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
34c283cf2cSMatteo Croce MODULE_DESCRIPTION(LONGNAME);
35c283cf2cSMatteo Croce MODULE_LICENSE("GPL");
36c283cf2cSMatteo Croce 
37c283cf2cSMatteo Croce static int margin = 60;
38c283cf2cSMatteo Croce module_param(margin, int, 0);
39c283cf2cSMatteo Croce MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
40c283cf2cSMatteo Croce 
4186a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
4286a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
43c283cf2cSMatteo Croce MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
44c283cf2cSMatteo Croce 
45c283cf2cSMatteo Croce #define READ_REG(x) readl((void __iomem *)&(x))
46c283cf2cSMatteo Croce #define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
47c283cf2cSMatteo Croce 
48c283cf2cSMatteo Croce struct ar7_wdt {
49c283cf2cSMatteo Croce 	u32 kick_lock;
50c283cf2cSMatteo Croce 	u32 kick;
51c283cf2cSMatteo Croce 	u32 change_lock;
52c283cf2cSMatteo Croce 	u32 change;
53c283cf2cSMatteo Croce 	u32 disable_lock;
54c283cf2cSMatteo Croce 	u32 disable;
55c283cf2cSMatteo Croce 	u32 prescale_lock;
56c283cf2cSMatteo Croce 	u32 prescale;
57c283cf2cSMatteo Croce };
58c283cf2cSMatteo Croce 
59670d59c0SAlan Cox static unsigned long wdt_is_open;
60c283cf2cSMatteo Croce static unsigned expect_close;
611334f329SAxel Lin static DEFINE_SPINLOCK(wdt_lock);
62c283cf2cSMatteo Croce 
63c283cf2cSMatteo Croce /* XXX currently fixed, allows max margin ~68.72 secs */
64c283cf2cSMatteo Croce #define prescale_value 0xffff
65c283cf2cSMatteo Croce 
6664d4062aSFlorian Fainelli /* Resource of the WDT registers */
6764d4062aSFlorian Fainelli static struct resource *ar7_regs_wdt;
68c283cf2cSMatteo Croce /* Pointer to the remapped WDT IO space */
69c283cf2cSMatteo Croce static struct ar7_wdt *ar7_wdt;
70c283cf2cSMatteo Croce 
71780019ddSFlorian Fainelli static struct clk *vbus_clk;
72780019ddSFlorian Fainelli 
73c283cf2cSMatteo Croce static void ar7_wdt_kick(u32 value)
74c283cf2cSMatteo Croce {
75c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->kick_lock, 0x5555);
76c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
77c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
78c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
79c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->kick, value);
80c283cf2cSMatteo Croce 			return;
81c283cf2cSMatteo Croce 		}
82c283cf2cSMatteo Croce 	}
8327c766aaSJoe Perches 	pr_err("failed to unlock WDT kick reg\n");
84c283cf2cSMatteo Croce }
85c283cf2cSMatteo Croce 
86c283cf2cSMatteo Croce static void ar7_wdt_prescale(u32 value)
87c283cf2cSMatteo Croce {
88c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
89c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
90c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
91c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
92c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->prescale, value);
93c283cf2cSMatteo Croce 			return;
94c283cf2cSMatteo Croce 		}
95c283cf2cSMatteo Croce 	}
9627c766aaSJoe Perches 	pr_err("failed to unlock WDT prescale reg\n");
97c283cf2cSMatteo Croce }
98c283cf2cSMatteo Croce 
99c283cf2cSMatteo Croce static void ar7_wdt_change(u32 value)
100c283cf2cSMatteo Croce {
101c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->change_lock, 0x6666);
102c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
103c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
104c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
105c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->change, value);
106c283cf2cSMatteo Croce 			return;
107c283cf2cSMatteo Croce 		}
108c283cf2cSMatteo Croce 	}
10927c766aaSJoe Perches 	pr_err("failed to unlock WDT change reg\n");
110c283cf2cSMatteo Croce }
111c283cf2cSMatteo Croce 
112c283cf2cSMatteo Croce static void ar7_wdt_disable(u32 value)
113c283cf2cSMatteo Croce {
114c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->disable_lock, 0x7777);
115c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
116c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
117c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
118c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
119c283cf2cSMatteo Croce 			if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
120c283cf2cSMatteo Croce 				WRITE_REG(ar7_wdt->disable, value);
121c283cf2cSMatteo Croce 				return;
122c283cf2cSMatteo Croce 			}
123c283cf2cSMatteo Croce 		}
124c283cf2cSMatteo Croce 	}
12527c766aaSJoe Perches 	pr_err("failed to unlock WDT disable reg\n");
126c283cf2cSMatteo Croce }
127c283cf2cSMatteo Croce 
128c283cf2cSMatteo Croce static void ar7_wdt_update_margin(int new_margin)
129c283cf2cSMatteo Croce {
130c283cf2cSMatteo Croce 	u32 change;
131780019ddSFlorian Fainelli 	u32 vbus_rate;
132c283cf2cSMatteo Croce 
133780019ddSFlorian Fainelli 	vbus_rate = clk_get_rate(vbus_clk);
134780019ddSFlorian Fainelli 	change = new_margin * (vbus_rate / prescale_value);
135670d59c0SAlan Cox 	if (change < 1)
136670d59c0SAlan Cox 		change = 1;
137670d59c0SAlan Cox 	if (change > 0xffff)
138670d59c0SAlan Cox 		change = 0xffff;
139c283cf2cSMatteo Croce 	ar7_wdt_change(change);
140780019ddSFlorian Fainelli 	margin = change * prescale_value / vbus_rate;
14127c766aaSJoe Perches 	pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
142780019ddSFlorian Fainelli 		margin, prescale_value, change, vbus_rate);
143c283cf2cSMatteo Croce }
144c283cf2cSMatteo Croce 
145c283cf2cSMatteo Croce static void ar7_wdt_enable_wdt(void)
146c283cf2cSMatteo Croce {
14727c766aaSJoe Perches 	pr_debug("enabling watchdog timer\n");
148c283cf2cSMatteo Croce 	ar7_wdt_disable(1);
149c283cf2cSMatteo Croce 	ar7_wdt_kick(1);
150c283cf2cSMatteo Croce }
151c283cf2cSMatteo Croce 
152c283cf2cSMatteo Croce static void ar7_wdt_disable_wdt(void)
153c283cf2cSMatteo Croce {
15427c766aaSJoe Perches 	pr_debug("disabling watchdog timer\n");
155c283cf2cSMatteo Croce 	ar7_wdt_disable(0);
156c283cf2cSMatteo Croce }
157c283cf2cSMatteo Croce 
158c283cf2cSMatteo Croce static int ar7_wdt_open(struct inode *inode, struct file *file)
159c283cf2cSMatteo Croce {
160c283cf2cSMatteo Croce 	/* only allow one at a time */
161670d59c0SAlan Cox 	if (test_and_set_bit(0, &wdt_is_open))
162c283cf2cSMatteo Croce 		return -EBUSY;
163c283cf2cSMatteo Croce 	ar7_wdt_enable_wdt();
164c283cf2cSMatteo Croce 	expect_close = 0;
165c283cf2cSMatteo Croce 
166c283cf2cSMatteo Croce 	return nonseekable_open(inode, file);
167c283cf2cSMatteo Croce }
168c283cf2cSMatteo Croce 
169c283cf2cSMatteo Croce static int ar7_wdt_release(struct inode *inode, struct file *file)
170c283cf2cSMatteo Croce {
171c283cf2cSMatteo Croce 	if (!expect_close)
17227c766aaSJoe Perches 		pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
173c283cf2cSMatteo Croce 	else if (!nowayout)
174c283cf2cSMatteo Croce 		ar7_wdt_disable_wdt();
175670d59c0SAlan Cox 	clear_bit(0, &wdt_is_open);
176c283cf2cSMatteo Croce 	return 0;
177c283cf2cSMatteo Croce }
178c283cf2cSMatteo Croce 
179c283cf2cSMatteo Croce static ssize_t ar7_wdt_write(struct file *file, const char *data,
180c283cf2cSMatteo Croce 			     size_t len, loff_t *ppos)
181c283cf2cSMatteo Croce {
182c283cf2cSMatteo Croce 	/* check for a magic close character */
183c283cf2cSMatteo Croce 	if (len) {
184c283cf2cSMatteo Croce 		size_t i;
185c283cf2cSMatteo Croce 
186670d59c0SAlan Cox 		spin_lock(&wdt_lock);
187c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
188670d59c0SAlan Cox 		spin_unlock(&wdt_lock);
189c283cf2cSMatteo Croce 
190c283cf2cSMatteo Croce 		expect_close = 0;
191c283cf2cSMatteo Croce 		for (i = 0; i < len; ++i) {
192c283cf2cSMatteo Croce 			char c;
193c283cf2cSMatteo Croce 			if (get_user(c, data + i))
194c283cf2cSMatteo Croce 				return -EFAULT;
195c283cf2cSMatteo Croce 			if (c == 'V')
196c283cf2cSMatteo Croce 				expect_close = 1;
197c283cf2cSMatteo Croce 		}
198c283cf2cSMatteo Croce 
199c283cf2cSMatteo Croce 	}
200c283cf2cSMatteo Croce 	return len;
201c283cf2cSMatteo Croce }
202c283cf2cSMatteo Croce 
203670d59c0SAlan Cox static long ar7_wdt_ioctl(struct file *file,
204c283cf2cSMatteo Croce 					unsigned int cmd, unsigned long arg)
205c283cf2cSMatteo Croce {
20642747d71SWim Van Sebroeck 	static const struct watchdog_info ident = {
207c283cf2cSMatteo Croce 		.identity = LONGNAME,
208c283cf2cSMatteo Croce 		.firmware_version = 1,
209e73a7802SWim Van Sebroeck 		.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
210e73a7802SWim Van Sebroeck 						WDIOF_MAGICCLOSE),
211c283cf2cSMatteo Croce 	};
212c283cf2cSMatteo Croce 	int new_margin;
213c283cf2cSMatteo Croce 
214c283cf2cSMatteo Croce 	switch (cmd) {
215c283cf2cSMatteo Croce 	case WDIOC_GETSUPPORT:
216c283cf2cSMatteo Croce 		if (copy_to_user((struct watchdog_info *)arg, &ident,
217c283cf2cSMatteo Croce 				sizeof(ident)))
218c283cf2cSMatteo Croce 			return -EFAULT;
219c283cf2cSMatteo Croce 		return 0;
220c283cf2cSMatteo Croce 	case WDIOC_GETSTATUS:
221c283cf2cSMatteo Croce 	case WDIOC_GETBOOTSTATUS:
222c283cf2cSMatteo Croce 		if (put_user(0, (int *)arg))
223c283cf2cSMatteo Croce 			return -EFAULT;
224c283cf2cSMatteo Croce 		return 0;
225c283cf2cSMatteo Croce 	case WDIOC_KEEPALIVE:
226c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
227c283cf2cSMatteo Croce 		return 0;
228c283cf2cSMatteo Croce 	case WDIOC_SETTIMEOUT:
229c283cf2cSMatteo Croce 		if (get_user(new_margin, (int *)arg))
230c283cf2cSMatteo Croce 			return -EFAULT;
231c283cf2cSMatteo Croce 		if (new_margin < 1)
232c283cf2cSMatteo Croce 			return -EINVAL;
233c283cf2cSMatteo Croce 
234670d59c0SAlan Cox 		spin_lock(&wdt_lock);
235c283cf2cSMatteo Croce 		ar7_wdt_update_margin(new_margin);
236c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
237670d59c0SAlan Cox 		spin_unlock(&wdt_lock);
238c283cf2cSMatteo Croce 
239c283cf2cSMatteo Croce 	case WDIOC_GETTIMEOUT:
240c283cf2cSMatteo Croce 		if (put_user(margin, (int *)arg))
241c283cf2cSMatteo Croce 			return -EFAULT;
242c283cf2cSMatteo Croce 		return 0;
2430c06090cSWim Van Sebroeck 	default:
2440c06090cSWim Van Sebroeck 		return -ENOTTY;
245c283cf2cSMatteo Croce 	}
246c283cf2cSMatteo Croce }
247c283cf2cSMatteo Croce 
248b47a166eSJan Engelhardt static const struct file_operations ar7_wdt_fops = {
249c283cf2cSMatteo Croce 	.owner		= THIS_MODULE,
250c283cf2cSMatteo Croce 	.write		= ar7_wdt_write,
251670d59c0SAlan Cox 	.unlocked_ioctl	= ar7_wdt_ioctl,
252c283cf2cSMatteo Croce 	.open		= ar7_wdt_open,
253c283cf2cSMatteo Croce 	.release	= ar7_wdt_release,
2546038f373SArnd Bergmann 	.llseek		= no_llseek,
255c283cf2cSMatteo Croce };
256c283cf2cSMatteo Croce 
257c283cf2cSMatteo Croce static struct miscdevice ar7_wdt_miscdev = {
258c283cf2cSMatteo Croce 	.minor		= WATCHDOG_MINOR,
259c283cf2cSMatteo Croce 	.name		= "watchdog",
260c283cf2cSMatteo Croce 	.fops		= &ar7_wdt_fops,
261c283cf2cSMatteo Croce };
262c283cf2cSMatteo Croce 
2632d991a16SBill Pemberton static int ar7_wdt_probe(struct platform_device *pdev)
264c283cf2cSMatteo Croce {
265c283cf2cSMatteo Croce 	int rc;
266c283cf2cSMatteo Croce 
26764d4062aSFlorian Fainelli 	ar7_regs_wdt =
26864d4062aSFlorian Fainelli 		platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
2694c271bb6SThierry Reding 	ar7_wdt = devm_ioremap_resource(&pdev->dev, ar7_regs_wdt);
2704c271bb6SThierry Reding 	if (IS_ERR(ar7_wdt))
2714c271bb6SThierry Reding 		return PTR_ERR(ar7_wdt);
272c283cf2cSMatteo Croce 
273780019ddSFlorian Fainelli 	vbus_clk = clk_get(NULL, "vbus");
274780019ddSFlorian Fainelli 	if (IS_ERR(vbus_clk)) {
27527c766aaSJoe Perches 		pr_err("could not get vbus clock\n");
276ae21cc20SJulia Lawall 		return PTR_ERR(vbus_clk);
277780019ddSFlorian Fainelli 	}
278780019ddSFlorian Fainelli 
279c283cf2cSMatteo Croce 	ar7_wdt_disable_wdt();
280c283cf2cSMatteo Croce 	ar7_wdt_prescale(prescale_value);
281c283cf2cSMatteo Croce 	ar7_wdt_update_margin(margin);
282c283cf2cSMatteo Croce 
283c283cf2cSMatteo Croce 	rc = misc_register(&ar7_wdt_miscdev);
284c283cf2cSMatteo Croce 	if (rc) {
28527c766aaSJoe Perches 		pr_err("unable to register misc device\n");
286c283cf2cSMatteo Croce 		goto out;
287ae21cc20SJulia Lawall 	}
288ae21cc20SJulia Lawall 	return 0;
289c283cf2cSMatteo Croce 
290c283cf2cSMatteo Croce out:
291ae21cc20SJulia Lawall 	clk_put(vbus_clk);
292ae21cc20SJulia Lawall 	vbus_clk = NULL;
293c283cf2cSMatteo Croce 	return rc;
294c283cf2cSMatteo Croce }
295c283cf2cSMatteo Croce 
2964b12b896SBill Pemberton static int ar7_wdt_remove(struct platform_device *pdev)
297c283cf2cSMatteo Croce {
298c283cf2cSMatteo Croce 	misc_deregister(&ar7_wdt_miscdev);
299ae21cc20SJulia Lawall 	clk_put(vbus_clk);
300ae21cc20SJulia Lawall 	vbus_clk = NULL;
30164d4062aSFlorian Fainelli 	return 0;
30264d4062aSFlorian Fainelli }
30364d4062aSFlorian Fainelli 
30464d4062aSFlorian Fainelli static void ar7_wdt_shutdown(struct platform_device *pdev)
30564d4062aSFlorian Fainelli {
30664d4062aSFlorian Fainelli 	if (!nowayout)
30764d4062aSFlorian Fainelli 		ar7_wdt_disable_wdt();
30864d4062aSFlorian Fainelli }
30964d4062aSFlorian Fainelli 
31064d4062aSFlorian Fainelli static struct platform_driver ar7_wdt_driver = {
31164d4062aSFlorian Fainelli 	.probe = ar7_wdt_probe,
31282268714SBill Pemberton 	.remove = ar7_wdt_remove,
31364d4062aSFlorian Fainelli 	.shutdown = ar7_wdt_shutdown,
31464d4062aSFlorian Fainelli 	.driver = {
31564d4062aSFlorian Fainelli 		.name = "ar7_wdt",
31664d4062aSFlorian Fainelli 	},
31764d4062aSFlorian Fainelli };
31864d4062aSFlorian Fainelli 
319b8ec6118SAxel Lin module_platform_driver(ar7_wdt_driver);
320