xref: /openbmc/linux/drivers/watchdog/twl4030_wdt.c (revision 2d991a164a61858012651e13c59521975504e260)
180e45b1eSTimo Kokkonen /*
280e45b1eSTimo Kokkonen  * Copyright (C) Nokia Corporation
380e45b1eSTimo Kokkonen  *
480e45b1eSTimo Kokkonen  * Written by Timo Kokkonen <timo.t.kokkonen at nokia.com>
580e45b1eSTimo Kokkonen  *
680e45b1eSTimo Kokkonen  * This program is free software; you can redistribute it and/or modify
780e45b1eSTimo Kokkonen  * it under the terms of the GNU General Public License as published by
880e45b1eSTimo Kokkonen  * the Free Software Foundation; either version 2 of the License, or
980e45b1eSTimo Kokkonen  * (at your option) any later version.
1080e45b1eSTimo Kokkonen  *
1180e45b1eSTimo Kokkonen  * This program is distributed in the hope that it will be useful,
1280e45b1eSTimo Kokkonen  * but WITHOUT ANY WARRANTY; without even the implied warranty of
1380e45b1eSTimo Kokkonen  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1480e45b1eSTimo Kokkonen  * GNU General Public License for more details.
1580e45b1eSTimo Kokkonen  *
1680e45b1eSTimo Kokkonen  * You should have received a copy of the GNU General Public License
1780e45b1eSTimo Kokkonen  * along with this program; if not, write to the Free Software
1880e45b1eSTimo Kokkonen  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1980e45b1eSTimo Kokkonen  */
2080e45b1eSTimo Kokkonen 
2180e45b1eSTimo Kokkonen #include <linux/module.h>
2280e45b1eSTimo Kokkonen #include <linux/types.h>
235a0e3ad6STejun Heo #include <linux/slab.h>
2480e45b1eSTimo Kokkonen #include <linux/kernel.h>
2580e45b1eSTimo Kokkonen #include <linux/fs.h>
2680e45b1eSTimo Kokkonen #include <linux/watchdog.h>
2780e45b1eSTimo Kokkonen #include <linux/platform_device.h>
2880e45b1eSTimo Kokkonen #include <linux/miscdevice.h>
2980e45b1eSTimo Kokkonen #include <linux/uaccess.h>
30b07682b6SSantosh Shilimkar #include <linux/i2c/twl.h>
3180e45b1eSTimo Kokkonen 
3280e45b1eSTimo Kokkonen #define TWL4030_WATCHDOG_CFG_REG_OFFS	0x3
3380e45b1eSTimo Kokkonen 
3480e45b1eSTimo Kokkonen #define TWL4030_WDT_STATE_OPEN		0x1
3580e45b1eSTimo Kokkonen #define TWL4030_WDT_STATE_ACTIVE	0x8
3680e45b1eSTimo Kokkonen 
3780e45b1eSTimo Kokkonen static struct platform_device *twl4030_wdt_dev;
3880e45b1eSTimo Kokkonen 
3980e45b1eSTimo Kokkonen struct twl4030_wdt {
4080e45b1eSTimo Kokkonen 	struct miscdevice	miscdev;
4180e45b1eSTimo Kokkonen 	int			timer_margin;
4280e45b1eSTimo Kokkonen 	unsigned long		state;
4380e45b1eSTimo Kokkonen };
4480e45b1eSTimo Kokkonen 
4586a1e189SWim Van Sebroeck static bool nowayout = WATCHDOG_NOWAYOUT;
4686a1e189SWim Van Sebroeck module_param(nowayout, bool, 0);
4780e45b1eSTimo Kokkonen MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
4880e45b1eSTimo Kokkonen 	"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
4980e45b1eSTimo Kokkonen 
5080e45b1eSTimo Kokkonen static int twl4030_wdt_write(unsigned char val)
5180e45b1eSTimo Kokkonen {
52fc7b92fcSBalaji T K 	return twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, val,
5380e45b1eSTimo Kokkonen 					TWL4030_WATCHDOG_CFG_REG_OFFS);
5480e45b1eSTimo Kokkonen }
5580e45b1eSTimo Kokkonen 
5680e45b1eSTimo Kokkonen static int twl4030_wdt_enable(struct twl4030_wdt *wdt)
5780e45b1eSTimo Kokkonen {
5880e45b1eSTimo Kokkonen 	return twl4030_wdt_write(wdt->timer_margin + 1);
5980e45b1eSTimo Kokkonen }
6080e45b1eSTimo Kokkonen 
6180e45b1eSTimo Kokkonen static int twl4030_wdt_disable(struct twl4030_wdt *wdt)
6280e45b1eSTimo Kokkonen {
6380e45b1eSTimo Kokkonen 	return twl4030_wdt_write(0);
6480e45b1eSTimo Kokkonen }
6580e45b1eSTimo Kokkonen 
6680e45b1eSTimo Kokkonen static int twl4030_wdt_set_timeout(struct twl4030_wdt *wdt, int timeout)
6780e45b1eSTimo Kokkonen {
6880e45b1eSTimo Kokkonen 	if (timeout < 0 || timeout > 30) {
6980e45b1eSTimo Kokkonen 		dev_warn(wdt->miscdev.parent,
7080e45b1eSTimo Kokkonen 			"Timeout can only be in the range [0-30] seconds");
7180e45b1eSTimo Kokkonen 		return -EINVAL;
7280e45b1eSTimo Kokkonen 	}
7380e45b1eSTimo Kokkonen 	wdt->timer_margin = timeout;
7480e45b1eSTimo Kokkonen 	return twl4030_wdt_enable(wdt);
7580e45b1eSTimo Kokkonen }
7680e45b1eSTimo Kokkonen 
7780e45b1eSTimo Kokkonen static ssize_t twl4030_wdt_write_fop(struct file *file,
7880e45b1eSTimo Kokkonen 		const char __user *data, size_t len, loff_t *ppos)
7980e45b1eSTimo Kokkonen {
8080e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt = file->private_data;
8180e45b1eSTimo Kokkonen 
8280e45b1eSTimo Kokkonen 	if (len)
8380e45b1eSTimo Kokkonen 		twl4030_wdt_enable(wdt);
8480e45b1eSTimo Kokkonen 
8580e45b1eSTimo Kokkonen 	return len;
8680e45b1eSTimo Kokkonen }
8780e45b1eSTimo Kokkonen 
8880e45b1eSTimo Kokkonen static long twl4030_wdt_ioctl(struct file *file,
8980e45b1eSTimo Kokkonen 		unsigned int cmd, unsigned long arg)
9080e45b1eSTimo Kokkonen {
9180e45b1eSTimo Kokkonen 	void __user *argp = (void __user *)arg;
9280e45b1eSTimo Kokkonen 	int __user *p = argp;
9380e45b1eSTimo Kokkonen 	int new_margin;
9480e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt = file->private_data;
9580e45b1eSTimo Kokkonen 
9680e45b1eSTimo Kokkonen 	static const struct watchdog_info twl4030_wd_ident = {
9780e45b1eSTimo Kokkonen 		.identity = "TWL4030 Watchdog",
9880e45b1eSTimo Kokkonen 		.options = WDIOF_SETTIMEOUT,
9980e45b1eSTimo Kokkonen 		.firmware_version = 0,
10080e45b1eSTimo Kokkonen 	};
10180e45b1eSTimo Kokkonen 
10280e45b1eSTimo Kokkonen 	switch (cmd) {
10380e45b1eSTimo Kokkonen 	case WDIOC_GETSUPPORT:
10480e45b1eSTimo Kokkonen 		return copy_to_user(argp, &twl4030_wd_ident,
10580e45b1eSTimo Kokkonen 				sizeof(twl4030_wd_ident)) ? -EFAULT : 0;
10680e45b1eSTimo Kokkonen 
10780e45b1eSTimo Kokkonen 	case WDIOC_GETSTATUS:
10880e45b1eSTimo Kokkonen 	case WDIOC_GETBOOTSTATUS:
10980e45b1eSTimo Kokkonen 		return put_user(0, p);
11080e45b1eSTimo Kokkonen 
11180e45b1eSTimo Kokkonen 	case WDIOC_KEEPALIVE:
11280e45b1eSTimo Kokkonen 		twl4030_wdt_enable(wdt);
11380e45b1eSTimo Kokkonen 		break;
11480e45b1eSTimo Kokkonen 
11580e45b1eSTimo Kokkonen 	case WDIOC_SETTIMEOUT:
11680e45b1eSTimo Kokkonen 		if (get_user(new_margin, p))
11780e45b1eSTimo Kokkonen 			return -EFAULT;
11880e45b1eSTimo Kokkonen 		if (twl4030_wdt_set_timeout(wdt, new_margin))
11980e45b1eSTimo Kokkonen 			return -EINVAL;
12080e45b1eSTimo Kokkonen 		return put_user(wdt->timer_margin, p);
12180e45b1eSTimo Kokkonen 
12280e45b1eSTimo Kokkonen 	case WDIOC_GETTIMEOUT:
12380e45b1eSTimo Kokkonen 		return put_user(wdt->timer_margin, p);
12480e45b1eSTimo Kokkonen 
12580e45b1eSTimo Kokkonen 	default:
12680e45b1eSTimo Kokkonen 		return -ENOTTY;
12780e45b1eSTimo Kokkonen 	}
12880e45b1eSTimo Kokkonen 
12980e45b1eSTimo Kokkonen 	return 0;
13080e45b1eSTimo Kokkonen }
13180e45b1eSTimo Kokkonen 
13280e45b1eSTimo Kokkonen static int twl4030_wdt_open(struct inode *inode, struct file *file)
13380e45b1eSTimo Kokkonen {
13480e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt = platform_get_drvdata(twl4030_wdt_dev);
13580e45b1eSTimo Kokkonen 
13680e45b1eSTimo Kokkonen 	/* /dev/watchdog can only be opened once */
13780e45b1eSTimo Kokkonen 	if (test_and_set_bit(0, &wdt->state))
13880e45b1eSTimo Kokkonen 		return -EBUSY;
13980e45b1eSTimo Kokkonen 
14080e45b1eSTimo Kokkonen 	wdt->state |= TWL4030_WDT_STATE_ACTIVE;
14180e45b1eSTimo Kokkonen 	file->private_data = (void *) wdt;
14280e45b1eSTimo Kokkonen 
14380e45b1eSTimo Kokkonen 	twl4030_wdt_enable(wdt);
14480e45b1eSTimo Kokkonen 	return nonseekable_open(inode, file);
14580e45b1eSTimo Kokkonen }
14680e45b1eSTimo Kokkonen 
14780e45b1eSTimo Kokkonen static int twl4030_wdt_release(struct inode *inode, struct file *file)
14880e45b1eSTimo Kokkonen {
14980e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt = file->private_data;
15080e45b1eSTimo Kokkonen 	if (nowayout) {
15180e45b1eSTimo Kokkonen 		dev_alert(wdt->miscdev.parent,
15280e45b1eSTimo Kokkonen 		       "Unexpected close, watchdog still running!\n");
15380e45b1eSTimo Kokkonen 		twl4030_wdt_enable(wdt);
15480e45b1eSTimo Kokkonen 	} else {
15580e45b1eSTimo Kokkonen 		if (twl4030_wdt_disable(wdt))
15680e45b1eSTimo Kokkonen 			return -EFAULT;
15780e45b1eSTimo Kokkonen 		wdt->state &= ~TWL4030_WDT_STATE_ACTIVE;
15880e45b1eSTimo Kokkonen 	}
15980e45b1eSTimo Kokkonen 
16080e45b1eSTimo Kokkonen 	clear_bit(0, &wdt->state);
16180e45b1eSTimo Kokkonen 	return 0;
16280e45b1eSTimo Kokkonen }
16380e45b1eSTimo Kokkonen 
16480e45b1eSTimo Kokkonen static const struct file_operations twl4030_wdt_fops = {
16580e45b1eSTimo Kokkonen 	.owner		= THIS_MODULE,
16680e45b1eSTimo Kokkonen 	.llseek		= no_llseek,
16780e45b1eSTimo Kokkonen 	.open		= twl4030_wdt_open,
16880e45b1eSTimo Kokkonen 	.release	= twl4030_wdt_release,
16980e45b1eSTimo Kokkonen 	.unlocked_ioctl	= twl4030_wdt_ioctl,
17080e45b1eSTimo Kokkonen 	.write		= twl4030_wdt_write_fop,
17180e45b1eSTimo Kokkonen };
17280e45b1eSTimo Kokkonen 
173*2d991a16SBill Pemberton static int twl4030_wdt_probe(struct platform_device *pdev)
17480e45b1eSTimo Kokkonen {
17580e45b1eSTimo Kokkonen 	int ret = 0;
17680e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt;
17780e45b1eSTimo Kokkonen 
17880e45b1eSTimo Kokkonen 	wdt = kzalloc(sizeof(struct twl4030_wdt), GFP_KERNEL);
17980e45b1eSTimo Kokkonen 	if (!wdt)
18080e45b1eSTimo Kokkonen 		return -ENOMEM;
18180e45b1eSTimo Kokkonen 
18280e45b1eSTimo Kokkonen 	wdt->state		= 0;
18380e45b1eSTimo Kokkonen 	wdt->timer_margin	= 30;
18480e45b1eSTimo Kokkonen 	wdt->miscdev.parent	= &pdev->dev;
18580e45b1eSTimo Kokkonen 	wdt->miscdev.fops	= &twl4030_wdt_fops;
18680e45b1eSTimo Kokkonen 	wdt->miscdev.minor	= WATCHDOG_MINOR;
18780e45b1eSTimo Kokkonen 	wdt->miscdev.name	= "watchdog";
18880e45b1eSTimo Kokkonen 
18980e45b1eSTimo Kokkonen 	platform_set_drvdata(pdev, wdt);
19080e45b1eSTimo Kokkonen 
19180e45b1eSTimo Kokkonen 	twl4030_wdt_dev = pdev;
19280e45b1eSTimo Kokkonen 
193bb6f3607SAmeya Palande 	twl4030_wdt_disable(wdt);
194bb6f3607SAmeya Palande 
19580e45b1eSTimo Kokkonen 	ret = misc_register(&wdt->miscdev);
19680e45b1eSTimo Kokkonen 	if (ret) {
19780e45b1eSTimo Kokkonen 		dev_err(wdt->miscdev.parent,
19880e45b1eSTimo Kokkonen 			"Failed to register misc device\n");
19980e45b1eSTimo Kokkonen 		platform_set_drvdata(pdev, NULL);
20080e45b1eSTimo Kokkonen 		kfree(wdt);
20180e45b1eSTimo Kokkonen 		twl4030_wdt_dev = NULL;
20280e45b1eSTimo Kokkonen 		return ret;
20380e45b1eSTimo Kokkonen 	}
20480e45b1eSTimo Kokkonen 	return 0;
20580e45b1eSTimo Kokkonen }
20680e45b1eSTimo Kokkonen 
20780e45b1eSTimo Kokkonen static int __devexit twl4030_wdt_remove(struct platform_device *pdev)
20880e45b1eSTimo Kokkonen {
20980e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt = platform_get_drvdata(pdev);
21080e45b1eSTimo Kokkonen 
21180e45b1eSTimo Kokkonen 	if (wdt->state & TWL4030_WDT_STATE_ACTIVE)
21280e45b1eSTimo Kokkonen 		if (twl4030_wdt_disable(wdt))
21380e45b1eSTimo Kokkonen 			return -EFAULT;
21480e45b1eSTimo Kokkonen 
21580e45b1eSTimo Kokkonen 	wdt->state &= ~TWL4030_WDT_STATE_ACTIVE;
21680e45b1eSTimo Kokkonen 	misc_deregister(&wdt->miscdev);
21780e45b1eSTimo Kokkonen 
21880e45b1eSTimo Kokkonen 	platform_set_drvdata(pdev, NULL);
21980e45b1eSTimo Kokkonen 	kfree(wdt);
22080e45b1eSTimo Kokkonen 	twl4030_wdt_dev = NULL;
22180e45b1eSTimo Kokkonen 
22280e45b1eSTimo Kokkonen 	return 0;
22380e45b1eSTimo Kokkonen }
22480e45b1eSTimo Kokkonen 
22580e45b1eSTimo Kokkonen #ifdef CONFIG_PM
22680e45b1eSTimo Kokkonen static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state)
22780e45b1eSTimo Kokkonen {
22880e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt = platform_get_drvdata(pdev);
22980e45b1eSTimo Kokkonen 	if (wdt->state & TWL4030_WDT_STATE_ACTIVE)
23080e45b1eSTimo Kokkonen 		return twl4030_wdt_disable(wdt);
23180e45b1eSTimo Kokkonen 
23280e45b1eSTimo Kokkonen 	return 0;
23380e45b1eSTimo Kokkonen }
23480e45b1eSTimo Kokkonen 
23580e45b1eSTimo Kokkonen static int twl4030_wdt_resume(struct platform_device *pdev)
23680e45b1eSTimo Kokkonen {
23780e45b1eSTimo Kokkonen 	struct twl4030_wdt *wdt = platform_get_drvdata(pdev);
23880e45b1eSTimo Kokkonen 	if (wdt->state & TWL4030_WDT_STATE_ACTIVE)
23980e45b1eSTimo Kokkonen 		return twl4030_wdt_enable(wdt);
24080e45b1eSTimo Kokkonen 
24180e45b1eSTimo Kokkonen 	return 0;
24280e45b1eSTimo Kokkonen }
24380e45b1eSTimo Kokkonen #else
24480e45b1eSTimo Kokkonen #define twl4030_wdt_suspend        NULL
24580e45b1eSTimo Kokkonen #define twl4030_wdt_resume         NULL
24680e45b1eSTimo Kokkonen #endif
24780e45b1eSTimo Kokkonen 
24880e45b1eSTimo Kokkonen static struct platform_driver twl4030_wdt_driver = {
24980e45b1eSTimo Kokkonen 	.probe		= twl4030_wdt_probe,
25082268714SBill Pemberton 	.remove		= twl4030_wdt_remove,
25180e45b1eSTimo Kokkonen 	.suspend	= twl4030_wdt_suspend,
25280e45b1eSTimo Kokkonen 	.resume		= twl4030_wdt_resume,
25380e45b1eSTimo Kokkonen 	.driver		= {
25480e45b1eSTimo Kokkonen 		.owner	= THIS_MODULE,
25580e45b1eSTimo Kokkonen 		.name	= "twl4030_wdt",
25680e45b1eSTimo Kokkonen 	},
25780e45b1eSTimo Kokkonen };
25880e45b1eSTimo Kokkonen 
259b8ec6118SAxel Lin module_platform_driver(twl4030_wdt_driver);
26080e45b1eSTimo Kokkonen 
26180e45b1eSTimo Kokkonen MODULE_AUTHOR("Nokia Corporation");
26280e45b1eSTimo Kokkonen MODULE_LICENSE("GPL");
26380e45b1eSTimo Kokkonen MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
26480e45b1eSTimo Kokkonen MODULE_ALIAS("platform:twl4030_wdt");
26580e45b1eSTimo Kokkonen 
266