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> 2380e45b1eSTimo Kokkonen #include <linux/kernel.h> 2480e45b1eSTimo Kokkonen #include <linux/fs.h> 2580e45b1eSTimo Kokkonen #include <linux/watchdog.h> 2680e45b1eSTimo Kokkonen #include <linux/platform_device.h> 2780e45b1eSTimo Kokkonen #include <linux/miscdevice.h> 2880e45b1eSTimo Kokkonen #include <linux/uaccess.h> 29*b07682b6SSantosh Shilimkar #include <linux/i2c/twl.h> 3080e45b1eSTimo Kokkonen 3180e45b1eSTimo Kokkonen #define TWL4030_WATCHDOG_CFG_REG_OFFS 0x3 3280e45b1eSTimo Kokkonen 3380e45b1eSTimo Kokkonen #define TWL4030_WDT_STATE_OPEN 0x1 3480e45b1eSTimo Kokkonen #define TWL4030_WDT_STATE_ACTIVE 0x8 3580e45b1eSTimo Kokkonen 3680e45b1eSTimo Kokkonen static struct platform_device *twl4030_wdt_dev; 3780e45b1eSTimo Kokkonen 3880e45b1eSTimo Kokkonen struct twl4030_wdt { 3980e45b1eSTimo Kokkonen struct miscdevice miscdev; 4080e45b1eSTimo Kokkonen int timer_margin; 4180e45b1eSTimo Kokkonen unsigned long state; 4280e45b1eSTimo Kokkonen }; 4380e45b1eSTimo Kokkonen 4480e45b1eSTimo Kokkonen static int nowayout = WATCHDOG_NOWAYOUT; 4580e45b1eSTimo Kokkonen module_param(nowayout, int, 0); 4680e45b1eSTimo Kokkonen MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 4780e45b1eSTimo Kokkonen "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 4880e45b1eSTimo Kokkonen 4980e45b1eSTimo Kokkonen static int twl4030_wdt_write(unsigned char val) 5080e45b1eSTimo Kokkonen { 5180e45b1eSTimo Kokkonen return twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, val, 5280e45b1eSTimo Kokkonen TWL4030_WATCHDOG_CFG_REG_OFFS); 5380e45b1eSTimo Kokkonen } 5480e45b1eSTimo Kokkonen 5580e45b1eSTimo Kokkonen static int twl4030_wdt_enable(struct twl4030_wdt *wdt) 5680e45b1eSTimo Kokkonen { 5780e45b1eSTimo Kokkonen return twl4030_wdt_write(wdt->timer_margin + 1); 5880e45b1eSTimo Kokkonen } 5980e45b1eSTimo Kokkonen 6080e45b1eSTimo Kokkonen static int twl4030_wdt_disable(struct twl4030_wdt *wdt) 6180e45b1eSTimo Kokkonen { 6280e45b1eSTimo Kokkonen return twl4030_wdt_write(0); 6380e45b1eSTimo Kokkonen } 6480e45b1eSTimo Kokkonen 6580e45b1eSTimo Kokkonen static int twl4030_wdt_set_timeout(struct twl4030_wdt *wdt, int timeout) 6680e45b1eSTimo Kokkonen { 6780e45b1eSTimo Kokkonen if (timeout < 0 || timeout > 30) { 6880e45b1eSTimo Kokkonen dev_warn(wdt->miscdev.parent, 6980e45b1eSTimo Kokkonen "Timeout can only be in the range [0-30] seconds"); 7080e45b1eSTimo Kokkonen return -EINVAL; 7180e45b1eSTimo Kokkonen } 7280e45b1eSTimo Kokkonen wdt->timer_margin = timeout; 7380e45b1eSTimo Kokkonen return twl4030_wdt_enable(wdt); 7480e45b1eSTimo Kokkonen } 7580e45b1eSTimo Kokkonen 7680e45b1eSTimo Kokkonen static ssize_t twl4030_wdt_write_fop(struct file *file, 7780e45b1eSTimo Kokkonen const char __user *data, size_t len, loff_t *ppos) 7880e45b1eSTimo Kokkonen { 7980e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = file->private_data; 8080e45b1eSTimo Kokkonen 8180e45b1eSTimo Kokkonen if (len) 8280e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 8380e45b1eSTimo Kokkonen 8480e45b1eSTimo Kokkonen return len; 8580e45b1eSTimo Kokkonen } 8680e45b1eSTimo Kokkonen 8780e45b1eSTimo Kokkonen static long twl4030_wdt_ioctl(struct file *file, 8880e45b1eSTimo Kokkonen unsigned int cmd, unsigned long arg) 8980e45b1eSTimo Kokkonen { 9080e45b1eSTimo Kokkonen void __user *argp = (void __user *)arg; 9180e45b1eSTimo Kokkonen int __user *p = argp; 9280e45b1eSTimo Kokkonen int new_margin; 9380e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = file->private_data; 9480e45b1eSTimo Kokkonen 9580e45b1eSTimo Kokkonen static const struct watchdog_info twl4030_wd_ident = { 9680e45b1eSTimo Kokkonen .identity = "TWL4030 Watchdog", 9780e45b1eSTimo Kokkonen .options = WDIOF_SETTIMEOUT, 9880e45b1eSTimo Kokkonen .firmware_version = 0, 9980e45b1eSTimo Kokkonen }; 10080e45b1eSTimo Kokkonen 10180e45b1eSTimo Kokkonen switch (cmd) { 10280e45b1eSTimo Kokkonen case WDIOC_GETSUPPORT: 10380e45b1eSTimo Kokkonen return copy_to_user(argp, &twl4030_wd_ident, 10480e45b1eSTimo Kokkonen sizeof(twl4030_wd_ident)) ? -EFAULT : 0; 10580e45b1eSTimo Kokkonen 10680e45b1eSTimo Kokkonen case WDIOC_GETSTATUS: 10780e45b1eSTimo Kokkonen case WDIOC_GETBOOTSTATUS: 10880e45b1eSTimo Kokkonen return put_user(0, p); 10980e45b1eSTimo Kokkonen 11080e45b1eSTimo Kokkonen case WDIOC_KEEPALIVE: 11180e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 11280e45b1eSTimo Kokkonen break; 11380e45b1eSTimo Kokkonen 11480e45b1eSTimo Kokkonen case WDIOC_SETTIMEOUT: 11580e45b1eSTimo Kokkonen if (get_user(new_margin, p)) 11680e45b1eSTimo Kokkonen return -EFAULT; 11780e45b1eSTimo Kokkonen if (twl4030_wdt_set_timeout(wdt, new_margin)) 11880e45b1eSTimo Kokkonen return -EINVAL; 11980e45b1eSTimo Kokkonen return put_user(wdt->timer_margin, p); 12080e45b1eSTimo Kokkonen 12180e45b1eSTimo Kokkonen case WDIOC_GETTIMEOUT: 12280e45b1eSTimo Kokkonen return put_user(wdt->timer_margin, p); 12380e45b1eSTimo Kokkonen 12480e45b1eSTimo Kokkonen default: 12580e45b1eSTimo Kokkonen return -ENOTTY; 12680e45b1eSTimo Kokkonen } 12780e45b1eSTimo Kokkonen 12880e45b1eSTimo Kokkonen return 0; 12980e45b1eSTimo Kokkonen } 13080e45b1eSTimo Kokkonen 13180e45b1eSTimo Kokkonen static int twl4030_wdt_open(struct inode *inode, struct file *file) 13280e45b1eSTimo Kokkonen { 13380e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(twl4030_wdt_dev); 13480e45b1eSTimo Kokkonen 13580e45b1eSTimo Kokkonen /* /dev/watchdog can only be opened once */ 13680e45b1eSTimo Kokkonen if (test_and_set_bit(0, &wdt->state)) 13780e45b1eSTimo Kokkonen return -EBUSY; 13880e45b1eSTimo Kokkonen 13980e45b1eSTimo Kokkonen wdt->state |= TWL4030_WDT_STATE_ACTIVE; 14080e45b1eSTimo Kokkonen file->private_data = (void *) wdt; 14180e45b1eSTimo Kokkonen 14280e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 14380e45b1eSTimo Kokkonen return nonseekable_open(inode, file); 14480e45b1eSTimo Kokkonen } 14580e45b1eSTimo Kokkonen 14680e45b1eSTimo Kokkonen static int twl4030_wdt_release(struct inode *inode, struct file *file) 14780e45b1eSTimo Kokkonen { 14880e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = file->private_data; 14980e45b1eSTimo Kokkonen if (nowayout) { 15080e45b1eSTimo Kokkonen dev_alert(wdt->miscdev.parent, 15180e45b1eSTimo Kokkonen "Unexpected close, watchdog still running!\n"); 15280e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 15380e45b1eSTimo Kokkonen } else { 15480e45b1eSTimo Kokkonen if (twl4030_wdt_disable(wdt)) 15580e45b1eSTimo Kokkonen return -EFAULT; 15680e45b1eSTimo Kokkonen wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; 15780e45b1eSTimo Kokkonen } 15880e45b1eSTimo Kokkonen 15980e45b1eSTimo Kokkonen clear_bit(0, &wdt->state); 16080e45b1eSTimo Kokkonen return 0; 16180e45b1eSTimo Kokkonen } 16280e45b1eSTimo Kokkonen 16380e45b1eSTimo Kokkonen static const struct file_operations twl4030_wdt_fops = { 16480e45b1eSTimo Kokkonen .owner = THIS_MODULE, 16580e45b1eSTimo Kokkonen .llseek = no_llseek, 16680e45b1eSTimo Kokkonen .open = twl4030_wdt_open, 16780e45b1eSTimo Kokkonen .release = twl4030_wdt_release, 16880e45b1eSTimo Kokkonen .unlocked_ioctl = twl4030_wdt_ioctl, 16980e45b1eSTimo Kokkonen .write = twl4030_wdt_write_fop, 17080e45b1eSTimo Kokkonen }; 17180e45b1eSTimo Kokkonen 17280e45b1eSTimo Kokkonen static int __devinit twl4030_wdt_probe(struct platform_device *pdev) 17380e45b1eSTimo Kokkonen { 17480e45b1eSTimo Kokkonen int ret = 0; 17580e45b1eSTimo Kokkonen struct twl4030_wdt *wdt; 17680e45b1eSTimo Kokkonen 17780e45b1eSTimo Kokkonen wdt = kzalloc(sizeof(struct twl4030_wdt), GFP_KERNEL); 17880e45b1eSTimo Kokkonen if (!wdt) 17980e45b1eSTimo Kokkonen return -ENOMEM; 18080e45b1eSTimo Kokkonen 18180e45b1eSTimo Kokkonen wdt->state = 0; 18280e45b1eSTimo Kokkonen wdt->timer_margin = 30; 18380e45b1eSTimo Kokkonen wdt->miscdev.parent = &pdev->dev; 18480e45b1eSTimo Kokkonen wdt->miscdev.fops = &twl4030_wdt_fops; 18580e45b1eSTimo Kokkonen wdt->miscdev.minor = WATCHDOG_MINOR; 18680e45b1eSTimo Kokkonen wdt->miscdev.name = "watchdog"; 18780e45b1eSTimo Kokkonen 18880e45b1eSTimo Kokkonen platform_set_drvdata(pdev, wdt); 18980e45b1eSTimo Kokkonen 19080e45b1eSTimo Kokkonen twl4030_wdt_dev = pdev; 19180e45b1eSTimo Kokkonen 19280e45b1eSTimo Kokkonen ret = misc_register(&wdt->miscdev); 19380e45b1eSTimo Kokkonen if (ret) { 19480e45b1eSTimo Kokkonen dev_err(wdt->miscdev.parent, 19580e45b1eSTimo Kokkonen "Failed to register misc device\n"); 19680e45b1eSTimo Kokkonen platform_set_drvdata(pdev, NULL); 19780e45b1eSTimo Kokkonen kfree(wdt); 19880e45b1eSTimo Kokkonen twl4030_wdt_dev = NULL; 19980e45b1eSTimo Kokkonen return ret; 20080e45b1eSTimo Kokkonen } 20180e45b1eSTimo Kokkonen return 0; 20280e45b1eSTimo Kokkonen } 20380e45b1eSTimo Kokkonen 20480e45b1eSTimo Kokkonen static int __devexit twl4030_wdt_remove(struct platform_device *pdev) 20580e45b1eSTimo Kokkonen { 20680e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 20780e45b1eSTimo Kokkonen 20880e45b1eSTimo Kokkonen if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 20980e45b1eSTimo Kokkonen if (twl4030_wdt_disable(wdt)) 21080e45b1eSTimo Kokkonen return -EFAULT; 21180e45b1eSTimo Kokkonen 21280e45b1eSTimo Kokkonen wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; 21380e45b1eSTimo Kokkonen misc_deregister(&wdt->miscdev); 21480e45b1eSTimo Kokkonen 21580e45b1eSTimo Kokkonen platform_set_drvdata(pdev, NULL); 21680e45b1eSTimo Kokkonen kfree(wdt); 21780e45b1eSTimo Kokkonen twl4030_wdt_dev = NULL; 21880e45b1eSTimo Kokkonen 21980e45b1eSTimo Kokkonen return 0; 22080e45b1eSTimo Kokkonen } 22180e45b1eSTimo Kokkonen 22280e45b1eSTimo Kokkonen #ifdef CONFIG_PM 22380e45b1eSTimo Kokkonen static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state) 22480e45b1eSTimo Kokkonen { 22580e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 22680e45b1eSTimo Kokkonen if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 22780e45b1eSTimo Kokkonen return twl4030_wdt_disable(wdt); 22880e45b1eSTimo Kokkonen 22980e45b1eSTimo Kokkonen return 0; 23080e45b1eSTimo Kokkonen } 23180e45b1eSTimo Kokkonen 23280e45b1eSTimo Kokkonen static int twl4030_wdt_resume(struct platform_device *pdev) 23380e45b1eSTimo Kokkonen { 23480e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 23580e45b1eSTimo Kokkonen if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 23680e45b1eSTimo Kokkonen return twl4030_wdt_enable(wdt); 23780e45b1eSTimo Kokkonen 23880e45b1eSTimo Kokkonen return 0; 23980e45b1eSTimo Kokkonen } 24080e45b1eSTimo Kokkonen #else 24180e45b1eSTimo Kokkonen #define twl4030_wdt_suspend NULL 24280e45b1eSTimo Kokkonen #define twl4030_wdt_resume NULL 24380e45b1eSTimo Kokkonen #endif 24480e45b1eSTimo Kokkonen 24580e45b1eSTimo Kokkonen static struct platform_driver twl4030_wdt_driver = { 24680e45b1eSTimo Kokkonen .probe = twl4030_wdt_probe, 24780e45b1eSTimo Kokkonen .remove = __devexit_p(twl4030_wdt_remove), 24880e45b1eSTimo Kokkonen .suspend = twl4030_wdt_suspend, 24980e45b1eSTimo Kokkonen .resume = twl4030_wdt_resume, 25080e45b1eSTimo Kokkonen .driver = { 25180e45b1eSTimo Kokkonen .owner = THIS_MODULE, 25280e45b1eSTimo Kokkonen .name = "twl4030_wdt", 25380e45b1eSTimo Kokkonen }, 25480e45b1eSTimo Kokkonen }; 25580e45b1eSTimo Kokkonen 25680e45b1eSTimo Kokkonen static int __devinit twl4030_wdt_init(void) 25780e45b1eSTimo Kokkonen { 25880e45b1eSTimo Kokkonen return platform_driver_register(&twl4030_wdt_driver); 25980e45b1eSTimo Kokkonen } 26080e45b1eSTimo Kokkonen module_init(twl4030_wdt_init); 26180e45b1eSTimo Kokkonen 26280e45b1eSTimo Kokkonen static void __devexit twl4030_wdt_exit(void) 26380e45b1eSTimo Kokkonen { 26480e45b1eSTimo Kokkonen platform_driver_unregister(&twl4030_wdt_driver); 26580e45b1eSTimo Kokkonen } 26680e45b1eSTimo Kokkonen module_exit(twl4030_wdt_exit); 26780e45b1eSTimo Kokkonen 26880e45b1eSTimo Kokkonen MODULE_AUTHOR("Nokia Corporation"); 26980e45b1eSTimo Kokkonen MODULE_LICENSE("GPL"); 27080e45b1eSTimo Kokkonen MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 27180e45b1eSTimo Kokkonen MODULE_ALIAS("platform:twl4030_wdt"); 27280e45b1eSTimo Kokkonen 273