xref: /linux/drivers/watchdog/m54xx_wdt.c (revision 4b4193256c8d3bc3a5397b5cd9494c2ad386317d)
188cce427SPhilippe De Muyter /*
29b9c63ffSPhilippe De Muyter  * drivers/watchdog/m54xx_wdt.c
388cce427SPhilippe De Muyter  *
49b9c63ffSPhilippe De Muyter  * Watchdog driver for ColdFire MCF547x & MCF548x processors
588cce427SPhilippe De Muyter  * Copyright 2010 (c) Philippe De Muyter <phdm@macqel.be>
688cce427SPhilippe De Muyter  *
788cce427SPhilippe De Muyter  * Adapted from the IXP4xx watchdog driver, which carries these notices:
888cce427SPhilippe De Muyter  *
988cce427SPhilippe De Muyter  *  Author: Deepak Saxena <dsaxena@plexity.net>
1088cce427SPhilippe De Muyter  *
1188cce427SPhilippe De Muyter  *  Copyright 2004 (c) MontaVista, Software, Inc.
1288cce427SPhilippe De Muyter  *  Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
1388cce427SPhilippe De Muyter  *
1488cce427SPhilippe De Muyter  * This file is licensed under  the terms of the GNU General Public
1588cce427SPhilippe De Muyter  * License version 2. This program is licensed "as is" without any
1688cce427SPhilippe De Muyter  * warranty of any kind, whether express or implied.
1788cce427SPhilippe De Muyter  */
1888cce427SPhilippe De Muyter 
1927c766aaSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2027c766aaSJoe Perches 
2188cce427SPhilippe De Muyter #include <linux/module.h>
2288cce427SPhilippe De Muyter #include <linux/moduleparam.h>
2388cce427SPhilippe De Muyter #include <linux/types.h>
2488cce427SPhilippe De Muyter #include <linux/kernel.h>
2588cce427SPhilippe De Muyter #include <linux/fs.h>
2688cce427SPhilippe De Muyter #include <linux/miscdevice.h>
2788cce427SPhilippe De Muyter #include <linux/watchdog.h>
2888cce427SPhilippe De Muyter #include <linux/init.h>
2988cce427SPhilippe De Muyter #include <linux/bitops.h>
3088cce427SPhilippe De Muyter #include <linux/ioport.h>
3188cce427SPhilippe De Muyter #include <linux/uaccess.h>
32072cb8b6SThomas Gleixner #include <linux/io.h>
3388cce427SPhilippe De Muyter 
3488cce427SPhilippe De Muyter #include <asm/coldfire.h>
359b9c63ffSPhilippe De Muyter #include <asm/m54xxsim.h>
369b9c63ffSPhilippe De Muyter #include <asm/m54xxgpt.h>
3788cce427SPhilippe De Muyter 
3886a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
3988cce427SPhilippe De Muyter static unsigned int heartbeat = 30;	/* (secs) Default is 0.5 minute */
4088cce427SPhilippe De Muyter static unsigned long wdt_status;
4188cce427SPhilippe De Muyter 
4288cce427SPhilippe De Muyter #define	WDT_IN_USE		0
4388cce427SPhilippe De Muyter #define	WDT_OK_TO_CLOSE		1
4488cce427SPhilippe De Muyter 
4588cce427SPhilippe De Muyter static void wdt_enable(void)
4688cce427SPhilippe De Muyter {
4788cce427SPhilippe De Muyter 	unsigned int gms0;
4888cce427SPhilippe De Muyter 
4988cce427SPhilippe De Muyter 	/* preserve GPIO usage, if any */
50944c3d81SGreg Ungerer 	gms0 = __raw_readl(MCF_GPT_GMS0);
5188cce427SPhilippe De Muyter 	if (gms0 & MCF_GPT_GMS_TMS_GPIO)
5288cce427SPhilippe De Muyter 		gms0 &= (MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_GPIO_MASK
5388cce427SPhilippe De Muyter 							| MCF_GPT_GMS_OD);
5488cce427SPhilippe De Muyter 	else
5588cce427SPhilippe De Muyter 		gms0 = MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_OD;
56944c3d81SGreg Ungerer 	__raw_writel(gms0, MCF_GPT_GMS0);
5788cce427SPhilippe De Muyter 	__raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) |
58944c3d81SGreg Ungerer 			MCF_GPT_GCIR_CNT(0xffff), MCF_GPT_GCIR0);
5988cce427SPhilippe De Muyter 	gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE;
60944c3d81SGreg Ungerer 	__raw_writel(gms0, MCF_GPT_GMS0);
6188cce427SPhilippe De Muyter }
6288cce427SPhilippe De Muyter 
6388cce427SPhilippe De Muyter static void wdt_disable(void)
6488cce427SPhilippe De Muyter {
6588cce427SPhilippe De Muyter 	unsigned int gms0;
6688cce427SPhilippe De Muyter 
6788cce427SPhilippe De Muyter 	/* disable watchdog */
68944c3d81SGreg Ungerer 	gms0 = __raw_readl(MCF_GPT_GMS0);
6988cce427SPhilippe De Muyter 	gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE);
70944c3d81SGreg Ungerer 	__raw_writel(gms0, MCF_GPT_GMS0);
7188cce427SPhilippe De Muyter }
7288cce427SPhilippe De Muyter 
7388cce427SPhilippe De Muyter static void wdt_keepalive(void)
7488cce427SPhilippe De Muyter {
7588cce427SPhilippe De Muyter 	unsigned int gms0;
7688cce427SPhilippe De Muyter 
77944c3d81SGreg Ungerer 	gms0 = __raw_readl(MCF_GPT_GMS0);
7888cce427SPhilippe De Muyter 	gms0 |= MCF_GPT_GMS_OCPW(0xA5);
79944c3d81SGreg Ungerer 	__raw_writel(gms0, MCF_GPT_GMS0);
8088cce427SPhilippe De Muyter }
8188cce427SPhilippe De Muyter 
829b9c63ffSPhilippe De Muyter static int m54xx_wdt_open(struct inode *inode, struct file *file)
8388cce427SPhilippe De Muyter {
8488cce427SPhilippe De Muyter 	if (test_and_set_bit(WDT_IN_USE, &wdt_status))
8588cce427SPhilippe De Muyter 		return -EBUSY;
8688cce427SPhilippe De Muyter 
8788cce427SPhilippe De Muyter 	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
8888cce427SPhilippe De Muyter 	wdt_enable();
89c5bf68feSKirill Smelkov 	return stream_open(inode, file);
9088cce427SPhilippe De Muyter }
9188cce427SPhilippe De Muyter 
929b9c63ffSPhilippe De Muyter static ssize_t m54xx_wdt_write(struct file *file, const char *data,
9388cce427SPhilippe De Muyter 						size_t len, loff_t *ppos)
9488cce427SPhilippe De Muyter {
9588cce427SPhilippe De Muyter 	if (len) {
9688cce427SPhilippe De Muyter 		if (!nowayout) {
9788cce427SPhilippe De Muyter 			size_t i;
9888cce427SPhilippe De Muyter 
9988cce427SPhilippe De Muyter 			clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
10088cce427SPhilippe De Muyter 
10188cce427SPhilippe De Muyter 			for (i = 0; i != len; i++) {
10288cce427SPhilippe De Muyter 				char c;
10388cce427SPhilippe De Muyter 
10488cce427SPhilippe De Muyter 				if (get_user(c, data + i))
10588cce427SPhilippe De Muyter 					return -EFAULT;
10688cce427SPhilippe De Muyter 				if (c == 'V')
10788cce427SPhilippe De Muyter 					set_bit(WDT_OK_TO_CLOSE, &wdt_status);
10888cce427SPhilippe De Muyter 			}
10988cce427SPhilippe De Muyter 		}
11088cce427SPhilippe De Muyter 		wdt_keepalive();
11188cce427SPhilippe De Muyter 	}
11288cce427SPhilippe De Muyter 	return len;
11388cce427SPhilippe De Muyter }
11488cce427SPhilippe De Muyter 
11588cce427SPhilippe De Muyter static const struct watchdog_info ident = {
11688cce427SPhilippe De Muyter 	.options	= WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
11788cce427SPhilippe De Muyter 				WDIOF_KEEPALIVEPING,
1189b9c63ffSPhilippe De Muyter 	.identity	= "Coldfire M54xx Watchdog",
11988cce427SPhilippe De Muyter };
12088cce427SPhilippe De Muyter 
1219b9c63ffSPhilippe De Muyter static long m54xx_wdt_ioctl(struct file *file, unsigned int cmd,
12288cce427SPhilippe De Muyter 							 unsigned long arg)
12388cce427SPhilippe De Muyter {
12488cce427SPhilippe De Muyter 	int ret = -ENOTTY;
12588cce427SPhilippe De Muyter 	int time;
12688cce427SPhilippe De Muyter 
12788cce427SPhilippe De Muyter 	switch (cmd) {
12888cce427SPhilippe De Muyter 	case WDIOC_GETSUPPORT:
12988cce427SPhilippe De Muyter 		ret = copy_to_user((struct watchdog_info *)arg, &ident,
13088cce427SPhilippe De Muyter 				   sizeof(ident)) ? -EFAULT : 0;
13188cce427SPhilippe De Muyter 		break;
13288cce427SPhilippe De Muyter 
13388cce427SPhilippe De Muyter 	case WDIOC_GETSTATUS:
13488cce427SPhilippe De Muyter 		ret = put_user(0, (int *)arg);
13588cce427SPhilippe De Muyter 		break;
13688cce427SPhilippe De Muyter 
13788cce427SPhilippe De Muyter 	case WDIOC_GETBOOTSTATUS:
13888cce427SPhilippe De Muyter 		ret = put_user(0, (int *)arg);
13988cce427SPhilippe De Muyter 		break;
14088cce427SPhilippe De Muyter 
14188cce427SPhilippe De Muyter 	case WDIOC_KEEPALIVE:
14288cce427SPhilippe De Muyter 		wdt_keepalive();
14388cce427SPhilippe De Muyter 		ret = 0;
14488cce427SPhilippe De Muyter 		break;
14588cce427SPhilippe De Muyter 
14688cce427SPhilippe De Muyter 	case WDIOC_SETTIMEOUT:
14788cce427SPhilippe De Muyter 		ret = get_user(time, (int *)arg);
14888cce427SPhilippe De Muyter 		if (ret)
14988cce427SPhilippe De Muyter 			break;
15088cce427SPhilippe De Muyter 
15188cce427SPhilippe De Muyter 		if (time <= 0 || time > 30) {
15288cce427SPhilippe De Muyter 			ret = -EINVAL;
15388cce427SPhilippe De Muyter 			break;
15488cce427SPhilippe De Muyter 		}
15588cce427SPhilippe De Muyter 
15688cce427SPhilippe De Muyter 		heartbeat = time;
15788cce427SPhilippe De Muyter 		wdt_enable();
158*bd490f82SGustavo A. R. Silva 		fallthrough;
15988cce427SPhilippe De Muyter 
16088cce427SPhilippe De Muyter 	case WDIOC_GETTIMEOUT:
16188cce427SPhilippe De Muyter 		ret = put_user(heartbeat, (int *)arg);
16288cce427SPhilippe De Muyter 		break;
16388cce427SPhilippe De Muyter 	}
16488cce427SPhilippe De Muyter 	return ret;
16588cce427SPhilippe De Muyter }
16688cce427SPhilippe De Muyter 
1679b9c63ffSPhilippe De Muyter static int m54xx_wdt_release(struct inode *inode, struct file *file)
16888cce427SPhilippe De Muyter {
16988cce427SPhilippe De Muyter 	if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
17088cce427SPhilippe De Muyter 		wdt_disable();
17188cce427SPhilippe De Muyter 	else {
17227c766aaSJoe Perches 		pr_crit("Device closed unexpectedly - timer will not stop\n");
17388cce427SPhilippe De Muyter 		wdt_keepalive();
17488cce427SPhilippe De Muyter 	}
17588cce427SPhilippe De Muyter 	clear_bit(WDT_IN_USE, &wdt_status);
17688cce427SPhilippe De Muyter 	clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
17788cce427SPhilippe De Muyter 
17888cce427SPhilippe De Muyter 	return 0;
17988cce427SPhilippe De Muyter }
18088cce427SPhilippe De Muyter 
18188cce427SPhilippe De Muyter 
1829b9c63ffSPhilippe De Muyter static const struct file_operations m54xx_wdt_fops = {
18388cce427SPhilippe De Muyter 	.owner		= THIS_MODULE,
18488cce427SPhilippe De Muyter 	.llseek		= no_llseek,
1859b9c63ffSPhilippe De Muyter 	.write		= m54xx_wdt_write,
1869b9c63ffSPhilippe De Muyter 	.unlocked_ioctl	= m54xx_wdt_ioctl,
187b6dfb247SArnd Bergmann 	.compat_ioctl	= compat_ptr_ioctl,
1889b9c63ffSPhilippe De Muyter 	.open		= m54xx_wdt_open,
1899b9c63ffSPhilippe De Muyter 	.release	= m54xx_wdt_release,
19088cce427SPhilippe De Muyter };
19188cce427SPhilippe De Muyter 
1929b9c63ffSPhilippe De Muyter static struct miscdevice m54xx_wdt_miscdev = {
19388cce427SPhilippe De Muyter 	.minor		= WATCHDOG_MINOR,
19488cce427SPhilippe De Muyter 	.name		= "watchdog",
1959b9c63ffSPhilippe De Muyter 	.fops		= &m54xx_wdt_fops,
19688cce427SPhilippe De Muyter };
19788cce427SPhilippe De Muyter 
1989b9c63ffSPhilippe De Muyter static int __init m54xx_wdt_init(void)
19988cce427SPhilippe De Muyter {
200944c3d81SGreg Ungerer 	if (!request_mem_region(MCF_GPT_GCIR0, 4, "Coldfire M54xx Watchdog")) {
20127c766aaSJoe Perches 		pr_warn("I/O region busy\n");
20288cce427SPhilippe De Muyter 		return -EBUSY;
20388cce427SPhilippe De Muyter 	}
20427c766aaSJoe Perches 	pr_info("driver is loaded\n");
20588cce427SPhilippe De Muyter 
2069b9c63ffSPhilippe De Muyter 	return misc_register(&m54xx_wdt_miscdev);
20788cce427SPhilippe De Muyter }
20888cce427SPhilippe De Muyter 
2099b9c63ffSPhilippe De Muyter static void __exit m54xx_wdt_exit(void)
21088cce427SPhilippe De Muyter {
2119b9c63ffSPhilippe De Muyter 	misc_deregister(&m54xx_wdt_miscdev);
212944c3d81SGreg Ungerer 	release_mem_region(MCF_GPT_GCIR0, 4);
21388cce427SPhilippe De Muyter }
21488cce427SPhilippe De Muyter 
2159b9c63ffSPhilippe De Muyter module_init(m54xx_wdt_init);
2169b9c63ffSPhilippe De Muyter module_exit(m54xx_wdt_exit);
21788cce427SPhilippe De Muyter 
21888cce427SPhilippe De Muyter MODULE_AUTHOR("Philippe De Muyter <phdm@macqel.be>");
2199b9c63ffSPhilippe De Muyter MODULE_DESCRIPTION("Coldfire M54xx Watchdog");
22088cce427SPhilippe De Muyter 
22188cce427SPhilippe De Muyter module_param(heartbeat, int, 0);
22288cce427SPhilippe De Muyter MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 30s)");
22388cce427SPhilippe De Muyter 
22486a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
22588cce427SPhilippe De Muyter MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
22688cce427SPhilippe De Muyter 
22788cce427SPhilippe De Muyter MODULE_LICENSE("GPL");
228