xref: /OK3568_Linux_fs/kernel/drivers/watchdog/ar7_wdt.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * drivers/watchdog/ar7_wdt.c
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright (C) 2007 Nicolas Thill <nico@openwrt.org>
6*4882a593Smuzhiyun  * Copyright (c) 2005 Enrik Berkhan <Enrik.Berkhan@akk.org>
7*4882a593Smuzhiyun  *
8*4882a593Smuzhiyun  * Some code taken from:
9*4882a593Smuzhiyun  * National Semiconductor SCx200 Watchdog support
10*4882a593Smuzhiyun  * Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
11*4882a593Smuzhiyun  *
12*4882a593Smuzhiyun  */
13*4882a593Smuzhiyun 
14*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
15*4882a593Smuzhiyun 
16*4882a593Smuzhiyun #include <linux/module.h>
17*4882a593Smuzhiyun #include <linux/moduleparam.h>
18*4882a593Smuzhiyun #include <linux/errno.h>
19*4882a593Smuzhiyun #include <linux/miscdevice.h>
20*4882a593Smuzhiyun #include <linux/platform_device.h>
21*4882a593Smuzhiyun #include <linux/watchdog.h>
22*4882a593Smuzhiyun #include <linux/fs.h>
23*4882a593Smuzhiyun #include <linux/ioport.h>
24*4882a593Smuzhiyun #include <linux/io.h>
25*4882a593Smuzhiyun #include <linux/uaccess.h>
26*4882a593Smuzhiyun #include <linux/clk.h>
27*4882a593Smuzhiyun 
28*4882a593Smuzhiyun #include <asm/addrspace.h>
29*4882a593Smuzhiyun #include <asm/mach-ar7/ar7.h>
30*4882a593Smuzhiyun 
31*4882a593Smuzhiyun #define LONGNAME "TI AR7 Watchdog Timer"
32*4882a593Smuzhiyun 
33*4882a593Smuzhiyun MODULE_AUTHOR("Nicolas Thill <nico@openwrt.org>");
34*4882a593Smuzhiyun MODULE_DESCRIPTION(LONGNAME);
35*4882a593Smuzhiyun MODULE_LICENSE("GPL");
36*4882a593Smuzhiyun 
37*4882a593Smuzhiyun static int margin = 60;
38*4882a593Smuzhiyun module_param(margin, int, 0);
39*4882a593Smuzhiyun MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
40*4882a593Smuzhiyun 
41*4882a593Smuzhiyun static bool nowayout = WATCHDOG_NOWAYOUT;
42*4882a593Smuzhiyun module_param(nowayout, bool, 0);
43*4882a593Smuzhiyun MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
44*4882a593Smuzhiyun 
45*4882a593Smuzhiyun #define READ_REG(x) readl((void __iomem *)&(x))
46*4882a593Smuzhiyun #define WRITE_REG(x, v) writel((v), (void __iomem *)&(x))
47*4882a593Smuzhiyun 
48*4882a593Smuzhiyun struct ar7_wdt {
49*4882a593Smuzhiyun 	u32 kick_lock;
50*4882a593Smuzhiyun 	u32 kick;
51*4882a593Smuzhiyun 	u32 change_lock;
52*4882a593Smuzhiyun 	u32 change;
53*4882a593Smuzhiyun 	u32 disable_lock;
54*4882a593Smuzhiyun 	u32 disable;
55*4882a593Smuzhiyun 	u32 prescale_lock;
56*4882a593Smuzhiyun 	u32 prescale;
57*4882a593Smuzhiyun };
58*4882a593Smuzhiyun 
59*4882a593Smuzhiyun static unsigned long wdt_is_open;
60*4882a593Smuzhiyun static unsigned expect_close;
61*4882a593Smuzhiyun static DEFINE_SPINLOCK(wdt_lock);
62*4882a593Smuzhiyun 
63*4882a593Smuzhiyun /* XXX currently fixed, allows max margin ~68.72 secs */
64*4882a593Smuzhiyun #define prescale_value 0xffff
65*4882a593Smuzhiyun 
66*4882a593Smuzhiyun /* Resource of the WDT registers */
67*4882a593Smuzhiyun static struct resource *ar7_regs_wdt;
68*4882a593Smuzhiyun /* Pointer to the remapped WDT IO space */
69*4882a593Smuzhiyun static struct ar7_wdt *ar7_wdt;
70*4882a593Smuzhiyun 
71*4882a593Smuzhiyun static struct clk *vbus_clk;
72*4882a593Smuzhiyun 
ar7_wdt_kick(u32 value)73*4882a593Smuzhiyun static void ar7_wdt_kick(u32 value)
74*4882a593Smuzhiyun {
75*4882a593Smuzhiyun 	WRITE_REG(ar7_wdt->kick_lock, 0x5555);
76*4882a593Smuzhiyun 	if ((READ_REG(ar7_wdt->kick_lock) & 3) == 1) {
77*4882a593Smuzhiyun 		WRITE_REG(ar7_wdt->kick_lock, 0xaaaa);
78*4882a593Smuzhiyun 		if ((READ_REG(ar7_wdt->kick_lock) & 3) == 3) {
79*4882a593Smuzhiyun 			WRITE_REG(ar7_wdt->kick, value);
80*4882a593Smuzhiyun 			return;
81*4882a593Smuzhiyun 		}
82*4882a593Smuzhiyun 	}
83*4882a593Smuzhiyun 	pr_err("failed to unlock WDT kick reg\n");
84*4882a593Smuzhiyun }
85*4882a593Smuzhiyun 
ar7_wdt_prescale(u32 value)86*4882a593Smuzhiyun static void ar7_wdt_prescale(u32 value)
87*4882a593Smuzhiyun {
88*4882a593Smuzhiyun 	WRITE_REG(ar7_wdt->prescale_lock, 0x5a5a);
89*4882a593Smuzhiyun 	if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 1) {
90*4882a593Smuzhiyun 		WRITE_REG(ar7_wdt->prescale_lock, 0xa5a5);
91*4882a593Smuzhiyun 		if ((READ_REG(ar7_wdt->prescale_lock) & 3) == 3) {
92*4882a593Smuzhiyun 			WRITE_REG(ar7_wdt->prescale, value);
93*4882a593Smuzhiyun 			return;
94*4882a593Smuzhiyun 		}
95*4882a593Smuzhiyun 	}
96*4882a593Smuzhiyun 	pr_err("failed to unlock WDT prescale reg\n");
97*4882a593Smuzhiyun }
98*4882a593Smuzhiyun 
ar7_wdt_change(u32 value)99*4882a593Smuzhiyun static void ar7_wdt_change(u32 value)
100*4882a593Smuzhiyun {
101*4882a593Smuzhiyun 	WRITE_REG(ar7_wdt->change_lock, 0x6666);
102*4882a593Smuzhiyun 	if ((READ_REG(ar7_wdt->change_lock) & 3) == 1) {
103*4882a593Smuzhiyun 		WRITE_REG(ar7_wdt->change_lock, 0xbbbb);
104*4882a593Smuzhiyun 		if ((READ_REG(ar7_wdt->change_lock) & 3) == 3) {
105*4882a593Smuzhiyun 			WRITE_REG(ar7_wdt->change, value);
106*4882a593Smuzhiyun 			return;
107*4882a593Smuzhiyun 		}
108*4882a593Smuzhiyun 	}
109*4882a593Smuzhiyun 	pr_err("failed to unlock WDT change reg\n");
110*4882a593Smuzhiyun }
111*4882a593Smuzhiyun 
ar7_wdt_disable(u32 value)112*4882a593Smuzhiyun static void ar7_wdt_disable(u32 value)
113*4882a593Smuzhiyun {
114*4882a593Smuzhiyun 	WRITE_REG(ar7_wdt->disable_lock, 0x7777);
115*4882a593Smuzhiyun 	if ((READ_REG(ar7_wdt->disable_lock) & 3) == 1) {
116*4882a593Smuzhiyun 		WRITE_REG(ar7_wdt->disable_lock, 0xcccc);
117*4882a593Smuzhiyun 		if ((READ_REG(ar7_wdt->disable_lock) & 3) == 2) {
118*4882a593Smuzhiyun 			WRITE_REG(ar7_wdt->disable_lock, 0xdddd);
119*4882a593Smuzhiyun 			if ((READ_REG(ar7_wdt->disable_lock) & 3) == 3) {
120*4882a593Smuzhiyun 				WRITE_REG(ar7_wdt->disable, value);
121*4882a593Smuzhiyun 				return;
122*4882a593Smuzhiyun 			}
123*4882a593Smuzhiyun 		}
124*4882a593Smuzhiyun 	}
125*4882a593Smuzhiyun 	pr_err("failed to unlock WDT disable reg\n");
126*4882a593Smuzhiyun }
127*4882a593Smuzhiyun 
ar7_wdt_update_margin(int new_margin)128*4882a593Smuzhiyun static void ar7_wdt_update_margin(int new_margin)
129*4882a593Smuzhiyun {
130*4882a593Smuzhiyun 	u32 change;
131*4882a593Smuzhiyun 	u32 vbus_rate;
132*4882a593Smuzhiyun 
133*4882a593Smuzhiyun 	vbus_rate = clk_get_rate(vbus_clk);
134*4882a593Smuzhiyun 	change = new_margin * (vbus_rate / prescale_value);
135*4882a593Smuzhiyun 	if (change < 1)
136*4882a593Smuzhiyun 		change = 1;
137*4882a593Smuzhiyun 	if (change > 0xffff)
138*4882a593Smuzhiyun 		change = 0xffff;
139*4882a593Smuzhiyun 	ar7_wdt_change(change);
140*4882a593Smuzhiyun 	margin = change * prescale_value / vbus_rate;
141*4882a593Smuzhiyun 	pr_info("timer margin %d seconds (prescale %d, change %d, freq %d)\n",
142*4882a593Smuzhiyun 		margin, prescale_value, change, vbus_rate);
143*4882a593Smuzhiyun }
144*4882a593Smuzhiyun 
ar7_wdt_enable_wdt(void)145*4882a593Smuzhiyun static void ar7_wdt_enable_wdt(void)
146*4882a593Smuzhiyun {
147*4882a593Smuzhiyun 	pr_debug("enabling watchdog timer\n");
148*4882a593Smuzhiyun 	ar7_wdt_disable(1);
149*4882a593Smuzhiyun 	ar7_wdt_kick(1);
150*4882a593Smuzhiyun }
151*4882a593Smuzhiyun 
ar7_wdt_disable_wdt(void)152*4882a593Smuzhiyun static void ar7_wdt_disable_wdt(void)
153*4882a593Smuzhiyun {
154*4882a593Smuzhiyun 	pr_debug("disabling watchdog timer\n");
155*4882a593Smuzhiyun 	ar7_wdt_disable(0);
156*4882a593Smuzhiyun }
157*4882a593Smuzhiyun 
ar7_wdt_open(struct inode * inode,struct file * file)158*4882a593Smuzhiyun static int ar7_wdt_open(struct inode *inode, struct file *file)
159*4882a593Smuzhiyun {
160*4882a593Smuzhiyun 	/* only allow one at a time */
161*4882a593Smuzhiyun 	if (test_and_set_bit(0, &wdt_is_open))
162*4882a593Smuzhiyun 		return -EBUSY;
163*4882a593Smuzhiyun 	ar7_wdt_enable_wdt();
164*4882a593Smuzhiyun 	expect_close = 0;
165*4882a593Smuzhiyun 
166*4882a593Smuzhiyun 	return stream_open(inode, file);
167*4882a593Smuzhiyun }
168*4882a593Smuzhiyun 
ar7_wdt_release(struct inode * inode,struct file * file)169*4882a593Smuzhiyun static int ar7_wdt_release(struct inode *inode, struct file *file)
170*4882a593Smuzhiyun {
171*4882a593Smuzhiyun 	if (!expect_close)
172*4882a593Smuzhiyun 		pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
173*4882a593Smuzhiyun 	else if (!nowayout)
174*4882a593Smuzhiyun 		ar7_wdt_disable_wdt();
175*4882a593Smuzhiyun 	clear_bit(0, &wdt_is_open);
176*4882a593Smuzhiyun 	return 0;
177*4882a593Smuzhiyun }
178*4882a593Smuzhiyun 
ar7_wdt_write(struct file * file,const char * data,size_t len,loff_t * ppos)179*4882a593Smuzhiyun static ssize_t ar7_wdt_write(struct file *file, const char *data,
180*4882a593Smuzhiyun 			     size_t len, loff_t *ppos)
181*4882a593Smuzhiyun {
182*4882a593Smuzhiyun 	/* check for a magic close character */
183*4882a593Smuzhiyun 	if (len) {
184*4882a593Smuzhiyun 		size_t i;
185*4882a593Smuzhiyun 
186*4882a593Smuzhiyun 		spin_lock(&wdt_lock);
187*4882a593Smuzhiyun 		ar7_wdt_kick(1);
188*4882a593Smuzhiyun 		spin_unlock(&wdt_lock);
189*4882a593Smuzhiyun 
190*4882a593Smuzhiyun 		expect_close = 0;
191*4882a593Smuzhiyun 		for (i = 0; i < len; ++i) {
192*4882a593Smuzhiyun 			char c;
193*4882a593Smuzhiyun 			if (get_user(c, data + i))
194*4882a593Smuzhiyun 				return -EFAULT;
195*4882a593Smuzhiyun 			if (c == 'V')
196*4882a593Smuzhiyun 				expect_close = 1;
197*4882a593Smuzhiyun 		}
198*4882a593Smuzhiyun 
199*4882a593Smuzhiyun 	}
200*4882a593Smuzhiyun 	return len;
201*4882a593Smuzhiyun }
202*4882a593Smuzhiyun 
ar7_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)203*4882a593Smuzhiyun static long ar7_wdt_ioctl(struct file *file,
204*4882a593Smuzhiyun 					unsigned int cmd, unsigned long arg)
205*4882a593Smuzhiyun {
206*4882a593Smuzhiyun 	static const struct watchdog_info ident = {
207*4882a593Smuzhiyun 		.identity = LONGNAME,
208*4882a593Smuzhiyun 		.firmware_version = 1,
209*4882a593Smuzhiyun 		.options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
210*4882a593Smuzhiyun 						WDIOF_MAGICCLOSE),
211*4882a593Smuzhiyun 	};
212*4882a593Smuzhiyun 	int new_margin;
213*4882a593Smuzhiyun 
214*4882a593Smuzhiyun 	switch (cmd) {
215*4882a593Smuzhiyun 	case WDIOC_GETSUPPORT:
216*4882a593Smuzhiyun 		if (copy_to_user((struct watchdog_info *)arg, &ident,
217*4882a593Smuzhiyun 				sizeof(ident)))
218*4882a593Smuzhiyun 			return -EFAULT;
219*4882a593Smuzhiyun 		return 0;
220*4882a593Smuzhiyun 	case WDIOC_GETSTATUS:
221*4882a593Smuzhiyun 	case WDIOC_GETBOOTSTATUS:
222*4882a593Smuzhiyun 		if (put_user(0, (int *)arg))
223*4882a593Smuzhiyun 			return -EFAULT;
224*4882a593Smuzhiyun 		return 0;
225*4882a593Smuzhiyun 	case WDIOC_KEEPALIVE:
226*4882a593Smuzhiyun 		ar7_wdt_kick(1);
227*4882a593Smuzhiyun 		return 0;
228*4882a593Smuzhiyun 	case WDIOC_SETTIMEOUT:
229*4882a593Smuzhiyun 		if (get_user(new_margin, (int *)arg))
230*4882a593Smuzhiyun 			return -EFAULT;
231*4882a593Smuzhiyun 		if (new_margin < 1)
232*4882a593Smuzhiyun 			return -EINVAL;
233*4882a593Smuzhiyun 
234*4882a593Smuzhiyun 		spin_lock(&wdt_lock);
235*4882a593Smuzhiyun 		ar7_wdt_update_margin(new_margin);
236*4882a593Smuzhiyun 		ar7_wdt_kick(1);
237*4882a593Smuzhiyun 		spin_unlock(&wdt_lock);
238*4882a593Smuzhiyun 		fallthrough;
239*4882a593Smuzhiyun 	case WDIOC_GETTIMEOUT:
240*4882a593Smuzhiyun 		if (put_user(margin, (int *)arg))
241*4882a593Smuzhiyun 			return -EFAULT;
242*4882a593Smuzhiyun 		return 0;
243*4882a593Smuzhiyun 	default:
244*4882a593Smuzhiyun 		return -ENOTTY;
245*4882a593Smuzhiyun 	}
246*4882a593Smuzhiyun }
247*4882a593Smuzhiyun 
248*4882a593Smuzhiyun static const struct file_operations ar7_wdt_fops = {
249*4882a593Smuzhiyun 	.owner		= THIS_MODULE,
250*4882a593Smuzhiyun 	.write		= ar7_wdt_write,
251*4882a593Smuzhiyun 	.unlocked_ioctl	= ar7_wdt_ioctl,
252*4882a593Smuzhiyun 	.compat_ioctl	= compat_ptr_ioctl,
253*4882a593Smuzhiyun 	.open		= ar7_wdt_open,
254*4882a593Smuzhiyun 	.release	= ar7_wdt_release,
255*4882a593Smuzhiyun 	.llseek		= no_llseek,
256*4882a593Smuzhiyun };
257*4882a593Smuzhiyun 
258*4882a593Smuzhiyun static struct miscdevice ar7_wdt_miscdev = {
259*4882a593Smuzhiyun 	.minor		= WATCHDOG_MINOR,
260*4882a593Smuzhiyun 	.name		= "watchdog",
261*4882a593Smuzhiyun 	.fops		= &ar7_wdt_fops,
262*4882a593Smuzhiyun };
263*4882a593Smuzhiyun 
ar7_wdt_probe(struct platform_device * pdev)264*4882a593Smuzhiyun static int ar7_wdt_probe(struct platform_device *pdev)
265*4882a593Smuzhiyun {
266*4882a593Smuzhiyun 	int rc;
267*4882a593Smuzhiyun 
268*4882a593Smuzhiyun 	ar7_regs_wdt =
269*4882a593Smuzhiyun 		platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs");
270*4882a593Smuzhiyun 	ar7_wdt = devm_ioremap_resource(&pdev->dev, ar7_regs_wdt);
271*4882a593Smuzhiyun 	if (IS_ERR(ar7_wdt))
272*4882a593Smuzhiyun 		return PTR_ERR(ar7_wdt);
273*4882a593Smuzhiyun 
274*4882a593Smuzhiyun 	vbus_clk = clk_get(NULL, "vbus");
275*4882a593Smuzhiyun 	if (IS_ERR(vbus_clk)) {
276*4882a593Smuzhiyun 		pr_err("could not get vbus clock\n");
277*4882a593Smuzhiyun 		return PTR_ERR(vbus_clk);
278*4882a593Smuzhiyun 	}
279*4882a593Smuzhiyun 
280*4882a593Smuzhiyun 	ar7_wdt_disable_wdt();
281*4882a593Smuzhiyun 	ar7_wdt_prescale(prescale_value);
282*4882a593Smuzhiyun 	ar7_wdt_update_margin(margin);
283*4882a593Smuzhiyun 
284*4882a593Smuzhiyun 	rc = misc_register(&ar7_wdt_miscdev);
285*4882a593Smuzhiyun 	if (rc) {
286*4882a593Smuzhiyun 		pr_err("unable to register misc device\n");
287*4882a593Smuzhiyun 		goto out;
288*4882a593Smuzhiyun 	}
289*4882a593Smuzhiyun 	return 0;
290*4882a593Smuzhiyun 
291*4882a593Smuzhiyun out:
292*4882a593Smuzhiyun 	clk_put(vbus_clk);
293*4882a593Smuzhiyun 	vbus_clk = NULL;
294*4882a593Smuzhiyun 	return rc;
295*4882a593Smuzhiyun }
296*4882a593Smuzhiyun 
ar7_wdt_remove(struct platform_device * pdev)297*4882a593Smuzhiyun static int ar7_wdt_remove(struct platform_device *pdev)
298*4882a593Smuzhiyun {
299*4882a593Smuzhiyun 	misc_deregister(&ar7_wdt_miscdev);
300*4882a593Smuzhiyun 	clk_put(vbus_clk);
301*4882a593Smuzhiyun 	vbus_clk = NULL;
302*4882a593Smuzhiyun 	return 0;
303*4882a593Smuzhiyun }
304*4882a593Smuzhiyun 
ar7_wdt_shutdown(struct platform_device * pdev)305*4882a593Smuzhiyun static void ar7_wdt_shutdown(struct platform_device *pdev)
306*4882a593Smuzhiyun {
307*4882a593Smuzhiyun 	if (!nowayout)
308*4882a593Smuzhiyun 		ar7_wdt_disable_wdt();
309*4882a593Smuzhiyun }
310*4882a593Smuzhiyun 
311*4882a593Smuzhiyun static struct platform_driver ar7_wdt_driver = {
312*4882a593Smuzhiyun 	.probe = ar7_wdt_probe,
313*4882a593Smuzhiyun 	.remove = ar7_wdt_remove,
314*4882a593Smuzhiyun 	.shutdown = ar7_wdt_shutdown,
315*4882a593Smuzhiyun 	.driver = {
316*4882a593Smuzhiyun 		.name = "ar7_wdt",
317*4882a593Smuzhiyun 	},
318*4882a593Smuzhiyun };
319*4882a593Smuzhiyun 
320*4882a593Smuzhiyun module_platform_driver(ar7_wdt_driver);
321