xref: /openbmc/linux/drivers/watchdog/ar7_wdt.c (revision e676c92e)
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 
66c283cf2cSMatteo Croce /* Pointer to the remapped WDT IO space */
67c283cf2cSMatteo Croce static struct ar7_wdt *ar7_wdt;
68c283cf2cSMatteo Croce 
69780019ddSFlorian Fainelli static struct clk *vbus_clk;
70780019ddSFlorian Fainelli 
ar7_wdt_kick(u32 value)71c283cf2cSMatteo Croce static void ar7_wdt_kick(u32 value)
72c283cf2cSMatteo Croce {
73c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->kick_lock, 0x5555);
74c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
75c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
76c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
77c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->kick, value);
78c283cf2cSMatteo Croce 			return;
79c283cf2cSMatteo Croce 		}
80c283cf2cSMatteo Croce 	}
8127c766aaSJoe Perches 	pr_err("failed to unlock WDT kick reg\n");
82c283cf2cSMatteo Croce }
83c283cf2cSMatteo Croce 
ar7_wdt_prescale(u32 value)84c283cf2cSMatteo Croce static void ar7_wdt_prescale(u32 value)
85c283cf2cSMatteo Croce {
86c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
87c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
88c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
89c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
90c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->prescale, value);
91c283cf2cSMatteo Croce 			return;
92c283cf2cSMatteo Croce 		}
93c283cf2cSMatteo Croce 	}
9427c766aaSJoe Perches 	pr_err("failed to unlock WDT prescale reg\n");
95c283cf2cSMatteo Croce }
96c283cf2cSMatteo Croce 
ar7_wdt_change(u32 value)97c283cf2cSMatteo Croce static void ar7_wdt_change(u32 value)
98c283cf2cSMatteo Croce {
99c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->change_lock, 0x6666);
100c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
101c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
102c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
103c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->change, value);
104c283cf2cSMatteo Croce 			return;
105c283cf2cSMatteo Croce 		}
106c283cf2cSMatteo Croce 	}
10727c766aaSJoe Perches 	pr_err("failed to unlock WDT change reg\n");
108c283cf2cSMatteo Croce }
109c283cf2cSMatteo Croce 
ar7_wdt_disable(u32 value)110c283cf2cSMatteo Croce static void ar7_wdt_disable(u32 value)
111c283cf2cSMatteo Croce {
112c283cf2cSMatteo Croce 	WRITE_REG(ar7_wdt->disable_lock, 0x7777);
113c283cf2cSMatteo Croce 	if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
114c283cf2cSMatteo Croce 		WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
115c283cf2cSMatteo Croce 		if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
116c283cf2cSMatteo Croce 			WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
117c283cf2cSMatteo Croce 			if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
118c283cf2cSMatteo Croce 				WRITE_REG(ar7_wdt->disable, value);
119c283cf2cSMatteo Croce 				return;
120c283cf2cSMatteo Croce 			}
121c283cf2cSMatteo Croce 		}
122c283cf2cSMatteo Croce 	}
12327c766aaSJoe Perches 	pr_err("failed to unlock WDT disable reg\n");
124c283cf2cSMatteo Croce }
125c283cf2cSMatteo Croce 
ar7_wdt_update_margin(int new_margin)126c283cf2cSMatteo Croce static void ar7_wdt_update_margin(int new_margin)
127c283cf2cSMatteo Croce {
128c283cf2cSMatteo Croce 	u32 change;
129780019ddSFlorian Fainelli 	u32 vbus_rate;
130c283cf2cSMatteo Croce 
131780019ddSFlorian Fainelli 	vbus_rate = clk_get_rate(vbus_clk);
132780019ddSFlorian Fainelli 	change = new_margin * (vbus_rate / prescale_value);
133670d59c0SAlan Cox 	if (change < 1)
134670d59c0SAlan Cox 		change = 1;
135670d59c0SAlan Cox 	if (change > 0xffff)
136670d59c0SAlan Cox 		change = 0xffff;
137c283cf2cSMatteo Croce 	ar7_wdt_change(change);
138780019ddSFlorian Fainelli 	margin = change * prescale_value / vbus_rate;
13927c766aaSJoe Perches 	pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
140780019ddSFlorian Fainelli 		margin, prescale_value, change, vbus_rate);
141c283cf2cSMatteo Croce }
142c283cf2cSMatteo Croce 
ar7_wdt_enable_wdt(void)143c283cf2cSMatteo Croce static void ar7_wdt_enable_wdt(void)
144c283cf2cSMatteo Croce {
14527c766aaSJoe Perches 	pr_debug("enabling watchdog timer\n");
146c283cf2cSMatteo Croce 	ar7_wdt_disable(1);
147c283cf2cSMatteo Croce 	ar7_wdt_kick(1);
148c283cf2cSMatteo Croce }
149c283cf2cSMatteo Croce 
ar7_wdt_disable_wdt(void)150c283cf2cSMatteo Croce static void ar7_wdt_disable_wdt(void)
151c283cf2cSMatteo Croce {
15227c766aaSJoe Perches 	pr_debug("disabling watchdog timer\n");
153c283cf2cSMatteo Croce 	ar7_wdt_disable(0);
154c283cf2cSMatteo Croce }
155c283cf2cSMatteo Croce 
ar7_wdt_open(struct inode * inode,struct file * file)156c283cf2cSMatteo Croce static int ar7_wdt_open(struct inode *inode, struct file *file)
157c283cf2cSMatteo Croce {
158c283cf2cSMatteo Croce 	/* only allow one at a time */
159670d59c0SAlan Cox 	if (test_and_set_bit(0, &wdt_is_open))
160c283cf2cSMatteo Croce 		return -EBUSY;
161c283cf2cSMatteo Croce 	ar7_wdt_enable_wdt();
162c283cf2cSMatteo Croce 	expect_close = 0;
163c283cf2cSMatteo Croce 
164c5bf68feSKirill Smelkov 	return stream_open(inode, file);
165c283cf2cSMatteo Croce }
166c283cf2cSMatteo Croce 
ar7_wdt_release(struct inode * inode,struct file * file)167c283cf2cSMatteo Croce static int ar7_wdt_release(struct inode *inode, struct file *file)
168c283cf2cSMatteo Croce {
169c283cf2cSMatteo Croce 	if (!expect_close)
17027c766aaSJoe Perches 		pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
171c283cf2cSMatteo Croce 	else if (!nowayout)
172c283cf2cSMatteo Croce 		ar7_wdt_disable_wdt();
173670d59c0SAlan Cox 	clear_bit(0, &wdt_is_open);
174c283cf2cSMatteo Croce 	return 0;
175c283cf2cSMatteo Croce }
176c283cf2cSMatteo Croce 
ar7_wdt_write(struct file * file,const char * data,size_t len,loff_t * ppos)177c283cf2cSMatteo Croce static ssize_t ar7_wdt_write(struct file *file, const char *data,
178c283cf2cSMatteo Croce 			     size_t len, loff_t *ppos)
179c283cf2cSMatteo Croce {
180c283cf2cSMatteo Croce 	/* check for a magic close character */
181c283cf2cSMatteo Croce 	if (len) {
182c283cf2cSMatteo Croce 		size_t i;
183c283cf2cSMatteo Croce 
184670d59c0SAlan Cox 		spin_lock(&wdt_lock);
185c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
186670d59c0SAlan Cox 		spin_unlock(&wdt_lock);
187c283cf2cSMatteo Croce 
188c283cf2cSMatteo Croce 		expect_close = 0;
189c283cf2cSMatteo Croce 		for (i = 0; i < len; ++i) {
190c283cf2cSMatteo Croce 			char c;
191c283cf2cSMatteo Croce 			if (get_user(c, data + i))
192c283cf2cSMatteo Croce 				return -EFAULT;
193c283cf2cSMatteo Croce 			if (c == 'V')
194c283cf2cSMatteo Croce 				expect_close = 1;
195c283cf2cSMatteo Croce 		}
196c283cf2cSMatteo Croce 
197c283cf2cSMatteo Croce 	}
198c283cf2cSMatteo Croce 	return len;
199c283cf2cSMatteo Croce }
200c283cf2cSMatteo Croce 
ar7_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)201670d59c0SAlan Cox static long ar7_wdt_ioctl(struct file *file,
202c283cf2cSMatteo Croce 					unsigned int cmd, unsigned long arg)
203c283cf2cSMatteo Croce {
20442747d71SWim Van Sebroeck 	static const struct watchdog_info ident = {
205c283cf2cSMatteo Croce 		.identity = LONGNAME,
206c283cf2cSMatteo Croce 		.firmware_version = 1,
207e73a7802SWim Van Sebroeck 		.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
208e73a7802SWim Van Sebroeck 						WDIOF_MAGICCLOSE),
209c283cf2cSMatteo Croce 	};
210c283cf2cSMatteo Croce 	int new_margin;
211c283cf2cSMatteo Croce 
212c283cf2cSMatteo Croce 	switch (cmd) {
213c283cf2cSMatteo Croce 	case WDIOC_GETSUPPORT:
214c283cf2cSMatteo Croce 		if (copy_to_user((struct watchdog_info *)arg, &ident,
215c283cf2cSMatteo Croce 				sizeof(ident)))
216c283cf2cSMatteo Croce 			return -EFAULT;
217c283cf2cSMatteo Croce 		return 0;
218c283cf2cSMatteo Croce 	case WDIOC_GETSTATUS:
219c283cf2cSMatteo Croce 	case WDIOC_GETBOOTSTATUS:
220c283cf2cSMatteo Croce 		if (put_user(0, (int *)arg))
221c283cf2cSMatteo Croce 			return -EFAULT;
222c283cf2cSMatteo Croce 		return 0;
223c283cf2cSMatteo Croce 	case WDIOC_KEEPALIVE:
224c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
225c283cf2cSMatteo Croce 		return 0;
226c283cf2cSMatteo Croce 	case WDIOC_SETTIMEOUT:
227c283cf2cSMatteo Croce 		if (get_user(new_margin, (int *)arg))
228c283cf2cSMatteo Croce 			return -EFAULT;
229c283cf2cSMatteo Croce 		if (new_margin < 1)
230c283cf2cSMatteo Croce 			return -EINVAL;
231c283cf2cSMatteo Croce 
232670d59c0SAlan Cox 		spin_lock(&wdt_lock);
233c283cf2cSMatteo Croce 		ar7_wdt_update_margin(new_margin);
234c283cf2cSMatteo Croce 		ar7_wdt_kick(1);
235670d59c0SAlan Cox 		spin_unlock(&wdt_lock);
236bd490f82SGustavo A. R. Silva 		fallthrough;
237c283cf2cSMatteo Croce 	case WDIOC_GETTIMEOUT:
238c283cf2cSMatteo Croce 		if (put_user(margin, (int *)arg))
239c283cf2cSMatteo Croce 			return -EFAULT;
240c283cf2cSMatteo Croce 		return 0;
2410c06090cSWim Van Sebroeck 	default:
2420c06090cSWim Van Sebroeck 		return -ENOTTY;
243c283cf2cSMatteo Croce 	}
244c283cf2cSMatteo Croce }
245c283cf2cSMatteo Croce 
246b47a166eSJan Engelhardt static const struct file_operations ar7_wdt_fops = {
247c283cf2cSMatteo Croce 	.owner		= THIS_MODULE,
248c283cf2cSMatteo Croce 	.write		= ar7_wdt_write,
249670d59c0SAlan Cox 	.unlocked_ioctl	= ar7_wdt_ioctl,
250b6dfb247SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
251c283cf2cSMatteo Croce 	.open		= ar7_wdt_open,
252c283cf2cSMatteo Croce 	.release	= ar7_wdt_release,
2536038f373SArnd Bergmann 	.llseek		= no_llseek,
254c283cf2cSMatteo Croce };
255c283cf2cSMatteo Croce 
256c283cf2cSMatteo Croce static struct miscdevice ar7_wdt_miscdev = {
257c283cf2cSMatteo Croce 	.minor		= WATCHDOG_MINOR,
258c283cf2cSMatteo Croce 	.name		= "watchdog",
259c283cf2cSMatteo Croce 	.fops		= &ar7_wdt_fops,
260c283cf2cSMatteo Croce };
261c283cf2cSMatteo Croce 
ar7_wdt_probe(struct platform_device * pdev)2622d991a16SBill Pemberton static int ar7_wdt_probe(struct platform_device *pdev)
263c283cf2cSMatteo Croce {
264c283cf2cSMatteo Croce 	int rc;
265c283cf2cSMatteo Croce 
26654ccba2fSCai Huoqing 	ar7_wdt = devm_platform_ioremap_resource_byname(pdev, "regs");
2674c271bb6SThierry Reding 	if (IS_ERR(ar7_wdt))
2684c271bb6SThierry Reding 		return PTR_ERR(ar7_wdt);
269c283cf2cSMatteo Croce 
270780019ddSFlorian Fainelli 	vbus_clk = clk_get(NULL, "vbus");
271780019ddSFlorian Fainelli 	if (IS_ERR(vbus_clk)) {
27227c766aaSJoe Perches 		pr_err("could not get vbus clock\n");
273ae21cc20SJulia Lawall 		return PTR_ERR(vbus_clk);
274780019ddSFlorian Fainelli 	}
275780019ddSFlorian Fainelli 
276c283cf2cSMatteo Croce 	ar7_wdt_disable_wdt();
277c283cf2cSMatteo Croce 	ar7_wdt_prescale(prescale_value);
278c283cf2cSMatteo Croce 	ar7_wdt_update_margin(margin);
279c283cf2cSMatteo Croce 
280c283cf2cSMatteo Croce 	rc = misc_register(&ar7_wdt_miscdev);
281c283cf2cSMatteo Croce 	if (rc) {
28227c766aaSJoe Perches 		pr_err("unable to register misc device\n");
283c283cf2cSMatteo Croce 		goto out;
284ae21cc20SJulia Lawall 	}
285ae21cc20SJulia Lawall 	return 0;
286c283cf2cSMatteo Croce 
287c283cf2cSMatteo Croce out:
288ae21cc20SJulia Lawall 	clk_put(vbus_clk);
289ae21cc20SJulia Lawall 	vbus_clk = NULL;
290c283cf2cSMatteo Croce 	return rc;
291c283cf2cSMatteo Croce }
292c283cf2cSMatteo Croce 
ar7_wdt_remove(struct platform_device * pdev)293*e676c92eSUwe Kleine-König static void ar7_wdt_remove(struct platform_device *pdev)
294c283cf2cSMatteo Croce {
295c283cf2cSMatteo Croce 	misc_deregister(&ar7_wdt_miscdev);
296ae21cc20SJulia Lawall 	clk_put(vbus_clk);
297ae21cc20SJulia Lawall 	vbus_clk = NULL;
29864d4062aSFlorian Fainelli }
29964d4062aSFlorian Fainelli 
ar7_wdt_shutdown(struct platform_device * pdev)30064d4062aSFlorian Fainelli static void ar7_wdt_shutdown(struct platform_device *pdev)
30164d4062aSFlorian Fainelli {
30264d4062aSFlorian Fainelli 	if (!nowayout)
30364d4062aSFlorian Fainelli 		ar7_wdt_disable_wdt();
30464d4062aSFlorian Fainelli }
30564d4062aSFlorian Fainelli 
30664d4062aSFlorian Fainelli static struct platform_driver ar7_wdt_driver = {
30764d4062aSFlorian Fainelli 	.probe = ar7_wdt_probe,
308*e676c92eSUwe Kleine-König 	.remove_new = ar7_wdt_remove,
30964d4062aSFlorian Fainelli 	.shutdown = ar7_wdt_shutdown,
31064d4062aSFlorian Fainelli 	.driver = {
31164d4062aSFlorian Fainelli 		.name = "ar7_wdt",
31264d4062aSFlorian Fainelli 	},
31364d4062aSFlorian Fainelli };
31464d4062aSFlorian Fainelli 
315b8ec6118SAxel Lin module_platform_driver(ar7_wdt_driver);
316