1*80e45b1eSTimo Kokkonen /* 2*80e45b1eSTimo Kokkonen * Copyright (C) Nokia Corporation 3*80e45b1eSTimo Kokkonen * 4*80e45b1eSTimo Kokkonen * Written by Timo Kokkonen <timo.t.kokkonen at nokia.com> 5*80e45b1eSTimo Kokkonen * 6*80e45b1eSTimo Kokkonen * This program is free software; you can redistribute it and/or modify 7*80e45b1eSTimo Kokkonen * it under the terms of the GNU General Public License as published by 8*80e45b1eSTimo Kokkonen * the Free Software Foundation; either version 2 of the License, or 9*80e45b1eSTimo Kokkonen * (at your option) any later version. 10*80e45b1eSTimo Kokkonen * 11*80e45b1eSTimo Kokkonen * This program is distributed in the hope that it will be useful, 12*80e45b1eSTimo Kokkonen * but WITHOUT ANY WARRANTY; without even the implied warranty of 13*80e45b1eSTimo Kokkonen * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14*80e45b1eSTimo Kokkonen * GNU General Public License for more details. 15*80e45b1eSTimo Kokkonen * 16*80e45b1eSTimo Kokkonen * You should have received a copy of the GNU General Public License 17*80e45b1eSTimo Kokkonen * along with this program; if not, write to the Free Software 18*80e45b1eSTimo Kokkonen * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19*80e45b1eSTimo Kokkonen */ 20*80e45b1eSTimo Kokkonen 21*80e45b1eSTimo Kokkonen #include <linux/module.h> 22*80e45b1eSTimo Kokkonen #include <linux/types.h> 23*80e45b1eSTimo Kokkonen #include <linux/kernel.h> 24*80e45b1eSTimo Kokkonen #include <linux/fs.h> 25*80e45b1eSTimo Kokkonen #include <linux/watchdog.h> 26*80e45b1eSTimo Kokkonen #include <linux/platform_device.h> 27*80e45b1eSTimo Kokkonen #include <linux/miscdevice.h> 28*80e45b1eSTimo Kokkonen #include <linux/uaccess.h> 29*80e45b1eSTimo Kokkonen #include <linux/i2c/twl4030.h> 30*80e45b1eSTimo Kokkonen 31*80e45b1eSTimo Kokkonen #define TWL4030_WATCHDOG_CFG_REG_OFFS 0x3 32*80e45b1eSTimo Kokkonen 33*80e45b1eSTimo Kokkonen #define TWL4030_WDT_STATE_OPEN 0x1 34*80e45b1eSTimo Kokkonen #define TWL4030_WDT_STATE_ACTIVE 0x8 35*80e45b1eSTimo Kokkonen 36*80e45b1eSTimo Kokkonen static struct platform_device *twl4030_wdt_dev; 37*80e45b1eSTimo Kokkonen 38*80e45b1eSTimo Kokkonen struct twl4030_wdt { 39*80e45b1eSTimo Kokkonen struct miscdevice miscdev; 40*80e45b1eSTimo Kokkonen int timer_margin; 41*80e45b1eSTimo Kokkonen unsigned long state; 42*80e45b1eSTimo Kokkonen }; 43*80e45b1eSTimo Kokkonen 44*80e45b1eSTimo Kokkonen static int nowayout = WATCHDOG_NOWAYOUT; 45*80e45b1eSTimo Kokkonen module_param(nowayout, int, 0); 46*80e45b1eSTimo Kokkonen MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 47*80e45b1eSTimo Kokkonen "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 48*80e45b1eSTimo Kokkonen 49*80e45b1eSTimo Kokkonen static int twl4030_wdt_write(unsigned char val) 50*80e45b1eSTimo Kokkonen { 51*80e45b1eSTimo Kokkonen return twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, val, 52*80e45b1eSTimo Kokkonen TWL4030_WATCHDOG_CFG_REG_OFFS); 53*80e45b1eSTimo Kokkonen } 54*80e45b1eSTimo Kokkonen 55*80e45b1eSTimo Kokkonen static int twl4030_wdt_enable(struct twl4030_wdt *wdt) 56*80e45b1eSTimo Kokkonen { 57*80e45b1eSTimo Kokkonen return twl4030_wdt_write(wdt->timer_margin + 1); 58*80e45b1eSTimo Kokkonen } 59*80e45b1eSTimo Kokkonen 60*80e45b1eSTimo Kokkonen static int twl4030_wdt_disable(struct twl4030_wdt *wdt) 61*80e45b1eSTimo Kokkonen { 62*80e45b1eSTimo Kokkonen return twl4030_wdt_write(0); 63*80e45b1eSTimo Kokkonen } 64*80e45b1eSTimo Kokkonen 65*80e45b1eSTimo Kokkonen static int twl4030_wdt_set_timeout(struct twl4030_wdt *wdt, int timeout) 66*80e45b1eSTimo Kokkonen { 67*80e45b1eSTimo Kokkonen if (timeout < 0 || timeout > 30) { 68*80e45b1eSTimo Kokkonen dev_warn(wdt->miscdev.parent, 69*80e45b1eSTimo Kokkonen "Timeout can only be in the range [0-30] seconds"); 70*80e45b1eSTimo Kokkonen return -EINVAL; 71*80e45b1eSTimo Kokkonen } 72*80e45b1eSTimo Kokkonen wdt->timer_margin = timeout; 73*80e45b1eSTimo Kokkonen return twl4030_wdt_enable(wdt); 74*80e45b1eSTimo Kokkonen } 75*80e45b1eSTimo Kokkonen 76*80e45b1eSTimo Kokkonen static ssize_t twl4030_wdt_write_fop(struct file *file, 77*80e45b1eSTimo Kokkonen const char __user *data, size_t len, loff_t *ppos) 78*80e45b1eSTimo Kokkonen { 79*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = file->private_data; 80*80e45b1eSTimo Kokkonen 81*80e45b1eSTimo Kokkonen if (len) 82*80e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 83*80e45b1eSTimo Kokkonen 84*80e45b1eSTimo Kokkonen return len; 85*80e45b1eSTimo Kokkonen } 86*80e45b1eSTimo Kokkonen 87*80e45b1eSTimo Kokkonen static long twl4030_wdt_ioctl(struct file *file, 88*80e45b1eSTimo Kokkonen unsigned int cmd, unsigned long arg) 89*80e45b1eSTimo Kokkonen { 90*80e45b1eSTimo Kokkonen void __user *argp = (void __user *)arg; 91*80e45b1eSTimo Kokkonen int __user *p = argp; 92*80e45b1eSTimo Kokkonen int new_margin; 93*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = file->private_data; 94*80e45b1eSTimo Kokkonen 95*80e45b1eSTimo Kokkonen static const struct watchdog_info twl4030_wd_ident = { 96*80e45b1eSTimo Kokkonen .identity = "TWL4030 Watchdog", 97*80e45b1eSTimo Kokkonen .options = WDIOF_SETTIMEOUT, 98*80e45b1eSTimo Kokkonen .firmware_version = 0, 99*80e45b1eSTimo Kokkonen }; 100*80e45b1eSTimo Kokkonen 101*80e45b1eSTimo Kokkonen switch (cmd) { 102*80e45b1eSTimo Kokkonen case WDIOC_GETSUPPORT: 103*80e45b1eSTimo Kokkonen return copy_to_user(argp, &twl4030_wd_ident, 104*80e45b1eSTimo Kokkonen sizeof(twl4030_wd_ident)) ? -EFAULT : 0; 105*80e45b1eSTimo Kokkonen 106*80e45b1eSTimo Kokkonen case WDIOC_GETSTATUS: 107*80e45b1eSTimo Kokkonen case WDIOC_GETBOOTSTATUS: 108*80e45b1eSTimo Kokkonen return put_user(0, p); 109*80e45b1eSTimo Kokkonen 110*80e45b1eSTimo Kokkonen case WDIOC_KEEPALIVE: 111*80e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 112*80e45b1eSTimo Kokkonen break; 113*80e45b1eSTimo Kokkonen 114*80e45b1eSTimo Kokkonen case WDIOC_SETTIMEOUT: 115*80e45b1eSTimo Kokkonen if (get_user(new_margin, p)) 116*80e45b1eSTimo Kokkonen return -EFAULT; 117*80e45b1eSTimo Kokkonen if (twl4030_wdt_set_timeout(wdt, new_margin)) 118*80e45b1eSTimo Kokkonen return -EINVAL; 119*80e45b1eSTimo Kokkonen return put_user(wdt->timer_margin, p); 120*80e45b1eSTimo Kokkonen 121*80e45b1eSTimo Kokkonen case WDIOC_GETTIMEOUT: 122*80e45b1eSTimo Kokkonen return put_user(wdt->timer_margin, p); 123*80e45b1eSTimo Kokkonen 124*80e45b1eSTimo Kokkonen default: 125*80e45b1eSTimo Kokkonen return -ENOTTY; 126*80e45b1eSTimo Kokkonen } 127*80e45b1eSTimo Kokkonen 128*80e45b1eSTimo Kokkonen return 0; 129*80e45b1eSTimo Kokkonen } 130*80e45b1eSTimo Kokkonen 131*80e45b1eSTimo Kokkonen static int twl4030_wdt_open(struct inode *inode, struct file *file) 132*80e45b1eSTimo Kokkonen { 133*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(twl4030_wdt_dev); 134*80e45b1eSTimo Kokkonen 135*80e45b1eSTimo Kokkonen /* /dev/watchdog can only be opened once */ 136*80e45b1eSTimo Kokkonen if (test_and_set_bit(0, &wdt->state)) 137*80e45b1eSTimo Kokkonen return -EBUSY; 138*80e45b1eSTimo Kokkonen 139*80e45b1eSTimo Kokkonen wdt->state |= TWL4030_WDT_STATE_ACTIVE; 140*80e45b1eSTimo Kokkonen file->private_data = (void *) wdt; 141*80e45b1eSTimo Kokkonen 142*80e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 143*80e45b1eSTimo Kokkonen return nonseekable_open(inode, file); 144*80e45b1eSTimo Kokkonen } 145*80e45b1eSTimo Kokkonen 146*80e45b1eSTimo Kokkonen static int twl4030_wdt_release(struct inode *inode, struct file *file) 147*80e45b1eSTimo Kokkonen { 148*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = file->private_data; 149*80e45b1eSTimo Kokkonen if (nowayout) { 150*80e45b1eSTimo Kokkonen dev_alert(wdt->miscdev.parent, 151*80e45b1eSTimo Kokkonen "Unexpected close, watchdog still running!\n"); 152*80e45b1eSTimo Kokkonen twl4030_wdt_enable(wdt); 153*80e45b1eSTimo Kokkonen } else { 154*80e45b1eSTimo Kokkonen if (twl4030_wdt_disable(wdt)) 155*80e45b1eSTimo Kokkonen return -EFAULT; 156*80e45b1eSTimo Kokkonen wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; 157*80e45b1eSTimo Kokkonen } 158*80e45b1eSTimo Kokkonen 159*80e45b1eSTimo Kokkonen clear_bit(0, &wdt->state); 160*80e45b1eSTimo Kokkonen return 0; 161*80e45b1eSTimo Kokkonen } 162*80e45b1eSTimo Kokkonen 163*80e45b1eSTimo Kokkonen static const struct file_operations twl4030_wdt_fops = { 164*80e45b1eSTimo Kokkonen .owner = THIS_MODULE, 165*80e45b1eSTimo Kokkonen .llseek = no_llseek, 166*80e45b1eSTimo Kokkonen .open = twl4030_wdt_open, 167*80e45b1eSTimo Kokkonen .release = twl4030_wdt_release, 168*80e45b1eSTimo Kokkonen .unlocked_ioctl = twl4030_wdt_ioctl, 169*80e45b1eSTimo Kokkonen .write = twl4030_wdt_write_fop, 170*80e45b1eSTimo Kokkonen }; 171*80e45b1eSTimo Kokkonen 172*80e45b1eSTimo Kokkonen static int __devinit twl4030_wdt_probe(struct platform_device *pdev) 173*80e45b1eSTimo Kokkonen { 174*80e45b1eSTimo Kokkonen int ret = 0; 175*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt; 176*80e45b1eSTimo Kokkonen 177*80e45b1eSTimo Kokkonen wdt = kzalloc(sizeof(struct twl4030_wdt), GFP_KERNEL); 178*80e45b1eSTimo Kokkonen if (!wdt) 179*80e45b1eSTimo Kokkonen return -ENOMEM; 180*80e45b1eSTimo Kokkonen 181*80e45b1eSTimo Kokkonen wdt->state = 0; 182*80e45b1eSTimo Kokkonen wdt->timer_margin = 30; 183*80e45b1eSTimo Kokkonen wdt->miscdev.parent = &pdev->dev; 184*80e45b1eSTimo Kokkonen wdt->miscdev.fops = &twl4030_wdt_fops; 185*80e45b1eSTimo Kokkonen wdt->miscdev.minor = WATCHDOG_MINOR; 186*80e45b1eSTimo Kokkonen wdt->miscdev.name = "watchdog"; 187*80e45b1eSTimo Kokkonen 188*80e45b1eSTimo Kokkonen platform_set_drvdata(pdev, wdt); 189*80e45b1eSTimo Kokkonen 190*80e45b1eSTimo Kokkonen twl4030_wdt_dev = pdev; 191*80e45b1eSTimo Kokkonen 192*80e45b1eSTimo Kokkonen ret = misc_register(&wdt->miscdev); 193*80e45b1eSTimo Kokkonen if (ret) { 194*80e45b1eSTimo Kokkonen dev_err(wdt->miscdev.parent, 195*80e45b1eSTimo Kokkonen "Failed to register misc device\n"); 196*80e45b1eSTimo Kokkonen platform_set_drvdata(pdev, NULL); 197*80e45b1eSTimo Kokkonen kfree(wdt); 198*80e45b1eSTimo Kokkonen twl4030_wdt_dev = NULL; 199*80e45b1eSTimo Kokkonen return ret; 200*80e45b1eSTimo Kokkonen } 201*80e45b1eSTimo Kokkonen return 0; 202*80e45b1eSTimo Kokkonen } 203*80e45b1eSTimo Kokkonen 204*80e45b1eSTimo Kokkonen static int __devexit twl4030_wdt_remove(struct platform_device *pdev) 205*80e45b1eSTimo Kokkonen { 206*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 207*80e45b1eSTimo Kokkonen 208*80e45b1eSTimo Kokkonen if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 209*80e45b1eSTimo Kokkonen if (twl4030_wdt_disable(wdt)) 210*80e45b1eSTimo Kokkonen return -EFAULT; 211*80e45b1eSTimo Kokkonen 212*80e45b1eSTimo Kokkonen wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; 213*80e45b1eSTimo Kokkonen misc_deregister(&wdt->miscdev); 214*80e45b1eSTimo Kokkonen 215*80e45b1eSTimo Kokkonen platform_set_drvdata(pdev, NULL); 216*80e45b1eSTimo Kokkonen kfree(wdt); 217*80e45b1eSTimo Kokkonen twl4030_wdt_dev = NULL; 218*80e45b1eSTimo Kokkonen 219*80e45b1eSTimo Kokkonen return 0; 220*80e45b1eSTimo Kokkonen } 221*80e45b1eSTimo Kokkonen 222*80e45b1eSTimo Kokkonen #ifdef CONFIG_PM 223*80e45b1eSTimo Kokkonen static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state) 224*80e45b1eSTimo Kokkonen { 225*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 226*80e45b1eSTimo Kokkonen if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 227*80e45b1eSTimo Kokkonen return twl4030_wdt_disable(wdt); 228*80e45b1eSTimo Kokkonen 229*80e45b1eSTimo Kokkonen return 0; 230*80e45b1eSTimo Kokkonen } 231*80e45b1eSTimo Kokkonen 232*80e45b1eSTimo Kokkonen static int twl4030_wdt_resume(struct platform_device *pdev) 233*80e45b1eSTimo Kokkonen { 234*80e45b1eSTimo Kokkonen struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 235*80e45b1eSTimo Kokkonen if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 236*80e45b1eSTimo Kokkonen return twl4030_wdt_enable(wdt); 237*80e45b1eSTimo Kokkonen 238*80e45b1eSTimo Kokkonen return 0; 239*80e45b1eSTimo Kokkonen } 240*80e45b1eSTimo Kokkonen #else 241*80e45b1eSTimo Kokkonen #define twl4030_wdt_suspend NULL 242*80e45b1eSTimo Kokkonen #define twl4030_wdt_resume NULL 243*80e45b1eSTimo Kokkonen #endif 244*80e45b1eSTimo Kokkonen 245*80e45b1eSTimo Kokkonen static struct platform_driver twl4030_wdt_driver = { 246*80e45b1eSTimo Kokkonen .probe = twl4030_wdt_probe, 247*80e45b1eSTimo Kokkonen .remove = __devexit_p(twl4030_wdt_remove), 248*80e45b1eSTimo Kokkonen .suspend = twl4030_wdt_suspend, 249*80e45b1eSTimo Kokkonen .resume = twl4030_wdt_resume, 250*80e45b1eSTimo Kokkonen .driver = { 251*80e45b1eSTimo Kokkonen .owner = THIS_MODULE, 252*80e45b1eSTimo Kokkonen .name = "twl4030_wdt", 253*80e45b1eSTimo Kokkonen }, 254*80e45b1eSTimo Kokkonen }; 255*80e45b1eSTimo Kokkonen 256*80e45b1eSTimo Kokkonen static int __devinit twl4030_wdt_init(void) 257*80e45b1eSTimo Kokkonen { 258*80e45b1eSTimo Kokkonen return platform_driver_register(&twl4030_wdt_driver); 259*80e45b1eSTimo Kokkonen } 260*80e45b1eSTimo Kokkonen module_init(twl4030_wdt_init); 261*80e45b1eSTimo Kokkonen 262*80e45b1eSTimo Kokkonen static void __devexit twl4030_wdt_exit(void) 263*80e45b1eSTimo Kokkonen { 264*80e45b1eSTimo Kokkonen platform_driver_unregister(&twl4030_wdt_driver); 265*80e45b1eSTimo Kokkonen } 266*80e45b1eSTimo Kokkonen module_exit(twl4030_wdt_exit); 267*80e45b1eSTimo Kokkonen 268*80e45b1eSTimo Kokkonen MODULE_AUTHOR("Nokia Corporation"); 269*80e45b1eSTimo Kokkonen MODULE_LICENSE("GPL"); 270*80e45b1eSTimo Kokkonen MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 271*80e45b1eSTimo Kokkonen MODULE_ALIAS("platform:twl4030_wdt"); 272*80e45b1eSTimo Kokkonen 273