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