174ba9207SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2b3e8f2c1SFlorian Fainelli /*
3b3e8f2c1SFlorian Fainelli  * RDC321x watchdog driver
4b3e8f2c1SFlorian Fainelli  *
5842102f3SFlorian Fainelli  * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
6b3e8f2c1SFlorian Fainelli  *
7b3e8f2c1SFlorian Fainelli  * This driver is highly inspired from the cpu5_wdt driver
8b3e8f2c1SFlorian Fainelli  */
9b3e8f2c1SFlorian Fainelli 
10b3e8f2c1SFlorian Fainelli #include <linux/module.h>
11b3e8f2c1SFlorian Fainelli #include <linux/moduleparam.h>
12b3e8f2c1SFlorian Fainelli #include <linux/types.h>
13b3e8f2c1SFlorian Fainelli #include <linux/errno.h>
14b3e8f2c1SFlorian Fainelli #include <linux/miscdevice.h>
15b3e8f2c1SFlorian Fainelli #include <linux/fs.h>
16b3e8f2c1SFlorian Fainelli #include <linux/ioport.h>
17b3e8f2c1SFlorian Fainelli #include <linux/timer.h>
18b3e8f2c1SFlorian Fainelli #include <linux/completion.h>
19b3e8f2c1SFlorian Fainelli #include <linux/jiffies.h>
20b3e8f2c1SFlorian Fainelli #include <linux/platform_device.h>
21b3e8f2c1SFlorian Fainelli #include <linux/watchdog.h>
22b3e8f2c1SFlorian Fainelli #include <linux/io.h>
23b3e8f2c1SFlorian Fainelli #include <linux/uaccess.h>
24842102f3SFlorian Fainelli #include <linux/mfd/rdc321x.h>
25b3e8f2c1SFlorian Fainelli 
26b3e8f2c1SFlorian Fainelli #define RDC_WDT_MASK	0x80000000 /* Mask */
27b3e8f2c1SFlorian Fainelli #define RDC_WDT_EN	0x00800000 /* Enable bit */
28b3e8f2c1SFlorian Fainelli #define RDC_WDT_WTI	0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
29b3e8f2c1SFlorian Fainelli #define RDC_WDT_RST	0x00100000 /* Reset bit */
30b3e8f2c1SFlorian Fainelli #define RDC_WDT_WIF	0x00040000 /* WDT IRQ Flag */
31b3e8f2c1SFlorian Fainelli #define RDC_WDT_IRT	0x00000100 /* IRQ Routing table */
32b3e8f2c1SFlorian Fainelli #define RDC_WDT_CNT	0x00000001 /* WDT count */
33b3e8f2c1SFlorian Fainelli 
34b3e8f2c1SFlorian Fainelli #define RDC_CLS_TMR	0x80003844 /* Clear timer */
35b3e8f2c1SFlorian Fainelli 
36b3e8f2c1SFlorian Fainelli #define RDC_WDT_INTERVAL	(HZ/10+1)
37b3e8f2c1SFlorian Fainelli 
38b3e8f2c1SFlorian Fainelli static int ticks = 1000;
39b3e8f2c1SFlorian Fainelli 
40b3e8f2c1SFlorian Fainelli /* some device data */
41b3e8f2c1SFlorian Fainelli 
42b3e8f2c1SFlorian Fainelli static struct {
43b3e8f2c1SFlorian Fainelli 	struct completion stop;
44b3e8f2c1SFlorian Fainelli 	int running;
45b3e8f2c1SFlorian Fainelli 	struct timer_list timer;
46b3e8f2c1SFlorian Fainelli 	int queue;
47b3e8f2c1SFlorian Fainelli 	int default_ticks;
48b3e8f2c1SFlorian Fainelli 	unsigned long inuse;
49b3e8f2c1SFlorian Fainelli 	spinlock_t lock;
50842102f3SFlorian Fainelli 	struct pci_dev *sb_pdev;
51842102f3SFlorian Fainelli 	int base_reg;
52b3e8f2c1SFlorian Fainelli } rdc321x_wdt_device;
53b3e8f2c1SFlorian Fainelli 
54b3e8f2c1SFlorian Fainelli /* generic helper functions */
55b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_trigger(struct timer_list * unused)56e99e88a9SKees Cook static void rdc321x_wdt_trigger(struct timer_list *unused)
57b3e8f2c1SFlorian Fainelli {
58b3e8f2c1SFlorian Fainelli 	unsigned long flags;
59842102f3SFlorian Fainelli 	u32 val;
60b3e8f2c1SFlorian Fainelli 
61b3e8f2c1SFlorian Fainelli 	if (rdc321x_wdt_device.running)
62b3e8f2c1SFlorian Fainelli 		ticks--;
63b3e8f2c1SFlorian Fainelli 
64b3e8f2c1SFlorian Fainelli 	/* keep watchdog alive */
65b3e8f2c1SFlorian Fainelli 	spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
66842102f3SFlorian Fainelli 	pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
67842102f3SFlorian Fainelli 					rdc321x_wdt_device.base_reg, &val);
68842102f3SFlorian Fainelli 	val |= RDC_WDT_EN;
69842102f3SFlorian Fainelli 	pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
70842102f3SFlorian Fainelli 					rdc321x_wdt_device.base_reg, val);
71b3e8f2c1SFlorian Fainelli 	spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
72b3e8f2c1SFlorian Fainelli 
73b3e8f2c1SFlorian Fainelli 	/* requeue?? */
74b3e8f2c1SFlorian Fainelli 	if (rdc321x_wdt_device.queue && ticks)
75b3e8f2c1SFlorian Fainelli 		mod_timer(&rdc321x_wdt_device.timer,
76b3e8f2c1SFlorian Fainelli 				jiffies + RDC_WDT_INTERVAL);
77b3e8f2c1SFlorian Fainelli 	else {
78b3e8f2c1SFlorian Fainelli 		/* ticks doesn't matter anyway */
79b3e8f2c1SFlorian Fainelli 		complete(&rdc321x_wdt_device.stop);
80b3e8f2c1SFlorian Fainelli 	}
81b3e8f2c1SFlorian Fainelli 
82b3e8f2c1SFlorian Fainelli }
83b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_reset(void)84b3e8f2c1SFlorian Fainelli static void rdc321x_wdt_reset(void)
85b3e8f2c1SFlorian Fainelli {
86b3e8f2c1SFlorian Fainelli 	ticks = rdc321x_wdt_device.default_ticks;
87b3e8f2c1SFlorian Fainelli }
88b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_start(void)89b3e8f2c1SFlorian Fainelli static void rdc321x_wdt_start(void)
90b3e8f2c1SFlorian Fainelli {
91b3e8f2c1SFlorian Fainelli 	unsigned long flags;
92b3e8f2c1SFlorian Fainelli 
93b3e8f2c1SFlorian Fainelli 	if (!rdc321x_wdt_device.queue) {
94b3e8f2c1SFlorian Fainelli 		rdc321x_wdt_device.queue = 1;
95b3e8f2c1SFlorian Fainelli 
96b3e8f2c1SFlorian Fainelli 		/* Clear the timer */
97b3e8f2c1SFlorian Fainelli 		spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
98842102f3SFlorian Fainelli 		pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
99842102f3SFlorian Fainelli 				rdc321x_wdt_device.base_reg, RDC_CLS_TMR);
100b3e8f2c1SFlorian Fainelli 
101b3e8f2c1SFlorian Fainelli 		/* Enable watchdog and set the timeout to 81.92 us */
102842102f3SFlorian Fainelli 		pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
103842102f3SFlorian Fainelli 					rdc321x_wdt_device.base_reg,
104842102f3SFlorian Fainelli 					RDC_WDT_EN | RDC_WDT_CNT);
105b3e8f2c1SFlorian Fainelli 		spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
106b3e8f2c1SFlorian Fainelli 
107b3e8f2c1SFlorian Fainelli 		mod_timer(&rdc321x_wdt_device.timer,
108b3e8f2c1SFlorian Fainelli 				jiffies + RDC_WDT_INTERVAL);
109b3e8f2c1SFlorian Fainelli 	}
110b3e8f2c1SFlorian Fainelli 
111b3e8f2c1SFlorian Fainelli 	/* if process dies, counter is not decremented */
112b3e8f2c1SFlorian Fainelli 	rdc321x_wdt_device.running++;
113b3e8f2c1SFlorian Fainelli }
114b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_stop(void)115b3e8f2c1SFlorian Fainelli static int rdc321x_wdt_stop(void)
116b3e8f2c1SFlorian Fainelli {
117b3e8f2c1SFlorian Fainelli 	if (rdc321x_wdt_device.running)
118b3e8f2c1SFlorian Fainelli 		rdc321x_wdt_device.running = 0;
119b3e8f2c1SFlorian Fainelli 
120b3e8f2c1SFlorian Fainelli 	ticks = rdc321x_wdt_device.default_ticks;
121b3e8f2c1SFlorian Fainelli 
122b3e8f2c1SFlorian Fainelli 	return -EIO;
123b3e8f2c1SFlorian Fainelli }
124b3e8f2c1SFlorian Fainelli 
125b3e8f2c1SFlorian Fainelli /* filesystem operations */
rdc321x_wdt_open(struct inode * inode,struct file * file)126b3e8f2c1SFlorian Fainelli static int rdc321x_wdt_open(struct inode *inode, struct file *file)
127b3e8f2c1SFlorian Fainelli {
128b3e8f2c1SFlorian Fainelli 	if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
129b3e8f2c1SFlorian Fainelli 		return -EBUSY;
130b3e8f2c1SFlorian Fainelli 
131c5bf68feSKirill Smelkov 	return stream_open(inode, file);
132b3e8f2c1SFlorian Fainelli }
133b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_release(struct inode * inode,struct file * file)134b3e8f2c1SFlorian Fainelli static int rdc321x_wdt_release(struct inode *inode, struct file *file)
135b3e8f2c1SFlorian Fainelli {
136b3e8f2c1SFlorian Fainelli 	clear_bit(0, &rdc321x_wdt_device.inuse);
137b3e8f2c1SFlorian Fainelli 	return 0;
138b3e8f2c1SFlorian Fainelli }
139b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_ioctl(struct file * file,unsigned int cmd,unsigned long arg)1407275fc8cSWim Van Sebroeck static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,
1417275fc8cSWim Van Sebroeck 				unsigned long arg)
142b3e8f2c1SFlorian Fainelli {
143b3e8f2c1SFlorian Fainelli 	void __user *argp = (void __user *)arg;
144842102f3SFlorian Fainelli 	u32 value;
14542747d71SWim Van Sebroeck 	static const struct watchdog_info ident = {
146b3e8f2c1SFlorian Fainelli 		.options = WDIOF_CARDRESET,
147b3e8f2c1SFlorian Fainelli 		.identity = "RDC321x WDT",
148b3e8f2c1SFlorian Fainelli 	};
149b3e8f2c1SFlorian Fainelli 	unsigned long flags;
150b3e8f2c1SFlorian Fainelli 
151b3e8f2c1SFlorian Fainelli 	switch (cmd) {
152b3e8f2c1SFlorian Fainelli 	case WDIOC_KEEPALIVE:
153b3e8f2c1SFlorian Fainelli 		rdc321x_wdt_reset();
154b3e8f2c1SFlorian Fainelli 		break;
155b3e8f2c1SFlorian Fainelli 	case WDIOC_GETSTATUS:
156b3e8f2c1SFlorian Fainelli 		/* Read the value from the DATA register */
157b3e8f2c1SFlorian Fainelli 		spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
158842102f3SFlorian Fainelli 		pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
159842102f3SFlorian Fainelli 					rdc321x_wdt_device.base_reg, &value);
160b3e8f2c1SFlorian Fainelli 		spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
161842102f3SFlorian Fainelli 		if (copy_to_user(argp, &value, sizeof(u32)))
162b3e8f2c1SFlorian Fainelli 			return -EFAULT;
163b3e8f2c1SFlorian Fainelli 		break;
164b3e8f2c1SFlorian Fainelli 	case WDIOC_GETSUPPORT:
165b3e8f2c1SFlorian Fainelli 		if (copy_to_user(argp, &ident, sizeof(ident)))
166b3e8f2c1SFlorian Fainelli 			return -EFAULT;
167b3e8f2c1SFlorian Fainelli 		break;
168b3e8f2c1SFlorian Fainelli 	case WDIOC_SETOPTIONS:
169b3e8f2c1SFlorian Fainelli 		if (copy_from_user(&value, argp, sizeof(int)))
170b3e8f2c1SFlorian Fainelli 			return -EFAULT;
171b3e8f2c1SFlorian Fainelli 		switch (value) {
172b3e8f2c1SFlorian Fainelli 		case WDIOS_ENABLECARD:
173b3e8f2c1SFlorian Fainelli 			rdc321x_wdt_start();
174b3e8f2c1SFlorian Fainelli 			break;
175b3e8f2c1SFlorian Fainelli 		case WDIOS_DISABLECARD:
176b3e8f2c1SFlorian Fainelli 			return rdc321x_wdt_stop();
177b3e8f2c1SFlorian Fainelli 		default:
178b3e8f2c1SFlorian Fainelli 			return -EINVAL;
179b3e8f2c1SFlorian Fainelli 		}
180b3e8f2c1SFlorian Fainelli 		break;
181b3e8f2c1SFlorian Fainelli 	default:
182b3e8f2c1SFlorian Fainelli 		return -ENOTTY;
183b3e8f2c1SFlorian Fainelli 	}
184b3e8f2c1SFlorian Fainelli 	return 0;
185b3e8f2c1SFlorian Fainelli }
186b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_write(struct file * file,const char __user * buf,size_t count,loff_t * ppos)187b3e8f2c1SFlorian Fainelli static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
188b3e8f2c1SFlorian Fainelli 				size_t count, loff_t *ppos)
189b3e8f2c1SFlorian Fainelli {
190b3e8f2c1SFlorian Fainelli 	if (!count)
191b3e8f2c1SFlorian Fainelli 		return -EIO;
192b3e8f2c1SFlorian Fainelli 
193b3e8f2c1SFlorian Fainelli 	rdc321x_wdt_reset();
194b3e8f2c1SFlorian Fainelli 
195b3e8f2c1SFlorian Fainelli 	return count;
196b3e8f2c1SFlorian Fainelli }
197b3e8f2c1SFlorian Fainelli 
198b3e8f2c1SFlorian Fainelli static const struct file_operations rdc321x_wdt_fops = {
199b3e8f2c1SFlorian Fainelli 	.owner		= THIS_MODULE,
200b3e8f2c1SFlorian Fainelli 	.llseek		= no_llseek,
2017275fc8cSWim Van Sebroeck 	.unlocked_ioctl	= rdc321x_wdt_ioctl,
202b6dfb247SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
203b3e8f2c1SFlorian Fainelli 	.open		= rdc321x_wdt_open,
204b3e8f2c1SFlorian Fainelli 	.write		= rdc321x_wdt_write,
205b3e8f2c1SFlorian Fainelli 	.release	= rdc321x_wdt_release,
206b3e8f2c1SFlorian Fainelli };
207b3e8f2c1SFlorian Fainelli 
208b3e8f2c1SFlorian Fainelli static struct miscdevice rdc321x_wdt_misc = {
209b3e8f2c1SFlorian Fainelli 	.minor	= WATCHDOG_MINOR,
210b3e8f2c1SFlorian Fainelli 	.name	= "watchdog",
211b3e8f2c1SFlorian Fainelli 	.fops	= &rdc321x_wdt_fops,
212b3e8f2c1SFlorian Fainelli };
213b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_probe(struct platform_device * pdev)2142d991a16SBill Pemberton static int rdc321x_wdt_probe(struct platform_device *pdev)
215b3e8f2c1SFlorian Fainelli {
216b3e8f2c1SFlorian Fainelli 	int err;
217842102f3SFlorian Fainelli 	struct resource *r;
218842102f3SFlorian Fainelli 	struct rdc321x_wdt_pdata *pdata;
219842102f3SFlorian Fainelli 
220bc8fdfbeSJingoo Han 	pdata = dev_get_platdata(&pdev->dev);
221842102f3SFlorian Fainelli 	if (!pdata) {
222842102f3SFlorian Fainelli 		dev_err(&pdev->dev, "no platform data supplied\n");
223842102f3SFlorian Fainelli 		return -ENODEV;
224842102f3SFlorian Fainelli 	}
225842102f3SFlorian Fainelli 
2268deca39eSFlorian Fainelli 	r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg");
227842102f3SFlorian Fainelli 	if (!r) {
228842102f3SFlorian Fainelli 		dev_err(&pdev->dev, "failed to get wdt-reg resource\n");
229842102f3SFlorian Fainelli 		return -ENODEV;
230842102f3SFlorian Fainelli 	}
231842102f3SFlorian Fainelli 
232842102f3SFlorian Fainelli 	rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;
233842102f3SFlorian Fainelli 	rdc321x_wdt_device.base_reg = r->start;
2344b2e7f99SMadhuparna Bhowmik 	rdc321x_wdt_device.queue = 0;
2354b2e7f99SMadhuparna Bhowmik 	rdc321x_wdt_device.default_ticks = ticks;
236b3e8f2c1SFlorian Fainelli 
237b3e8f2c1SFlorian Fainelli 	err = misc_register(&rdc321x_wdt_misc);
238b3e8f2c1SFlorian Fainelli 	if (err < 0) {
239842102f3SFlorian Fainelli 		dev_err(&pdev->dev, "misc_register failed\n");
240b3e8f2c1SFlorian Fainelli 		return err;
241b3e8f2c1SFlorian Fainelli 	}
242b3e8f2c1SFlorian Fainelli 
243b3e8f2c1SFlorian Fainelli 	spin_lock_init(&rdc321x_wdt_device.lock);
244b3e8f2c1SFlorian Fainelli 
245b3e8f2c1SFlorian Fainelli 	/* Reset the watchdog */
246842102f3SFlorian Fainelli 	pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
247842102f3SFlorian Fainelli 				rdc321x_wdt_device.base_reg, RDC_WDT_RST);
248b3e8f2c1SFlorian Fainelli 
249b3e8f2c1SFlorian Fainelli 	init_completion(&rdc321x_wdt_device.stop);
250b3e8f2c1SFlorian Fainelli 
251b3e8f2c1SFlorian Fainelli 	clear_bit(0, &rdc321x_wdt_device.inuse);
252b3e8f2c1SFlorian Fainelli 
253e99e88a9SKees Cook 	timer_setup(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);
254b3e8f2c1SFlorian Fainelli 
255842102f3SFlorian Fainelli 	dev_info(&pdev->dev, "watchdog init success\n");
256b3e8f2c1SFlorian Fainelli 
257b3e8f2c1SFlorian Fainelli 	return 0;
258b3e8f2c1SFlorian Fainelli }
259b3e8f2c1SFlorian Fainelli 
rdc321x_wdt_remove(struct platform_device * pdev)260*6645579bSUwe Kleine-König static void rdc321x_wdt_remove(struct platform_device *pdev)
261b3e8f2c1SFlorian Fainelli {
262b3e8f2c1SFlorian Fainelli 	if (rdc321x_wdt_device.queue) {
263b3e8f2c1SFlorian Fainelli 		rdc321x_wdt_device.queue = 0;
264b3e8f2c1SFlorian Fainelli 		wait_for_completion(&rdc321x_wdt_device.stop);
265b3e8f2c1SFlorian Fainelli 	}
266b3e8f2c1SFlorian Fainelli 
267b3e8f2c1SFlorian Fainelli 	misc_deregister(&rdc321x_wdt_misc);
268b3e8f2c1SFlorian Fainelli }
269b3e8f2c1SFlorian Fainelli 
270b3e8f2c1SFlorian Fainelli static struct platform_driver rdc321x_wdt_driver = {
271b3e8f2c1SFlorian Fainelli 	.probe = rdc321x_wdt_probe,
272*6645579bSUwe Kleine-König 	.remove_new = rdc321x_wdt_remove,
273b3e8f2c1SFlorian Fainelli 	.driver = {
274b3e8f2c1SFlorian Fainelli 		.name = "rdc321x-wdt",
275b3e8f2c1SFlorian Fainelli 	},
276b3e8f2c1SFlorian Fainelli };
277b3e8f2c1SFlorian Fainelli 
278b8ec6118SAxel Lin module_platform_driver(rdc321x_wdt_driver);
279b3e8f2c1SFlorian Fainelli 
280b3e8f2c1SFlorian Fainelli MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
281b3e8f2c1SFlorian Fainelli MODULE_DESCRIPTION("RDC321x watchdog driver");
282b3e8f2c1SFlorian Fainelli MODULE_LICENSE("GPL");
283