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