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/slab.h> 24 #include <linux/kernel.h> 25 #include <linux/fs.h> 26 #include <linux/watchdog.h> 27 #include <linux/platform_device.h> 28 #include <linux/miscdevice.h> 29 #include <linux/uaccess.h> 30 #include <linux/i2c/twl.h> 31 32 #define TWL4030_WATCHDOG_CFG_REG_OFFS 0x3 33 34 #define TWL4030_WDT_STATE_OPEN 0x1 35 #define TWL4030_WDT_STATE_ACTIVE 0x8 36 37 static struct platform_device *twl4030_wdt_dev; 38 39 struct twl4030_wdt { 40 struct miscdevice miscdev; 41 int timer_margin; 42 unsigned long state; 43 }; 44 45 static bool nowayout = WATCHDOG_NOWAYOUT; 46 module_param(nowayout, bool, 0); 47 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 48 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 49 50 static int twl4030_wdt_write(unsigned char val) 51 { 52 return twl_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, val, 53 TWL4030_WATCHDOG_CFG_REG_OFFS); 54 } 55 56 static int twl4030_wdt_enable(struct twl4030_wdt *wdt) 57 { 58 return twl4030_wdt_write(wdt->timer_margin + 1); 59 } 60 61 static int twl4030_wdt_disable(struct twl4030_wdt *wdt) 62 { 63 return twl4030_wdt_write(0); 64 } 65 66 static int twl4030_wdt_set_timeout(struct twl4030_wdt *wdt, int timeout) 67 { 68 if (timeout < 0 || timeout > 30) { 69 dev_warn(wdt->miscdev.parent, 70 "Timeout can only be in the range [0-30] seconds"); 71 return -EINVAL; 72 } 73 wdt->timer_margin = timeout; 74 return twl4030_wdt_enable(wdt); 75 } 76 77 static ssize_t twl4030_wdt_write_fop(struct file *file, 78 const char __user *data, size_t len, loff_t *ppos) 79 { 80 struct twl4030_wdt *wdt = file->private_data; 81 82 if (len) 83 twl4030_wdt_enable(wdt); 84 85 return len; 86 } 87 88 static long twl4030_wdt_ioctl(struct file *file, 89 unsigned int cmd, unsigned long arg) 90 { 91 void __user *argp = (void __user *)arg; 92 int __user *p = argp; 93 int new_margin; 94 struct twl4030_wdt *wdt = file->private_data; 95 96 static const struct watchdog_info twl4030_wd_ident = { 97 .identity = "TWL4030 Watchdog", 98 .options = WDIOF_SETTIMEOUT, 99 .firmware_version = 0, 100 }; 101 102 switch (cmd) { 103 case WDIOC_GETSUPPORT: 104 return copy_to_user(argp, &twl4030_wd_ident, 105 sizeof(twl4030_wd_ident)) ? -EFAULT : 0; 106 107 case WDIOC_GETSTATUS: 108 case WDIOC_GETBOOTSTATUS: 109 return put_user(0, p); 110 111 case WDIOC_KEEPALIVE: 112 twl4030_wdt_enable(wdt); 113 break; 114 115 case WDIOC_SETTIMEOUT: 116 if (get_user(new_margin, p)) 117 return -EFAULT; 118 if (twl4030_wdt_set_timeout(wdt, new_margin)) 119 return -EINVAL; 120 return put_user(wdt->timer_margin, p); 121 122 case WDIOC_GETTIMEOUT: 123 return put_user(wdt->timer_margin, p); 124 125 default: 126 return -ENOTTY; 127 } 128 129 return 0; 130 } 131 132 static int twl4030_wdt_open(struct inode *inode, struct file *file) 133 { 134 struct twl4030_wdt *wdt = platform_get_drvdata(twl4030_wdt_dev); 135 136 /* /dev/watchdog can only be opened once */ 137 if (test_and_set_bit(0, &wdt->state)) 138 return -EBUSY; 139 140 wdt->state |= TWL4030_WDT_STATE_ACTIVE; 141 file->private_data = (void *) wdt; 142 143 twl4030_wdt_enable(wdt); 144 return nonseekable_open(inode, file); 145 } 146 147 static int twl4030_wdt_release(struct inode *inode, struct file *file) 148 { 149 struct twl4030_wdt *wdt = file->private_data; 150 if (nowayout) { 151 dev_alert(wdt->miscdev.parent, 152 "Unexpected close, watchdog still running!\n"); 153 twl4030_wdt_enable(wdt); 154 } else { 155 if (twl4030_wdt_disable(wdt)) 156 return -EFAULT; 157 wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; 158 } 159 160 clear_bit(0, &wdt->state); 161 return 0; 162 } 163 164 static const struct file_operations twl4030_wdt_fops = { 165 .owner = THIS_MODULE, 166 .llseek = no_llseek, 167 .open = twl4030_wdt_open, 168 .release = twl4030_wdt_release, 169 .unlocked_ioctl = twl4030_wdt_ioctl, 170 .write = twl4030_wdt_write_fop, 171 }; 172 173 static int twl4030_wdt_probe(struct platform_device *pdev) 174 { 175 int ret = 0; 176 struct twl4030_wdt *wdt; 177 178 wdt = kzalloc(sizeof(struct twl4030_wdt), GFP_KERNEL); 179 if (!wdt) 180 return -ENOMEM; 181 182 wdt->state = 0; 183 wdt->timer_margin = 30; 184 wdt->miscdev.parent = &pdev->dev; 185 wdt->miscdev.fops = &twl4030_wdt_fops; 186 wdt->miscdev.minor = WATCHDOG_MINOR; 187 wdt->miscdev.name = "watchdog"; 188 189 platform_set_drvdata(pdev, wdt); 190 191 twl4030_wdt_dev = pdev; 192 193 twl4030_wdt_disable(wdt); 194 195 ret = misc_register(&wdt->miscdev); 196 if (ret) { 197 dev_err(wdt->miscdev.parent, 198 "Failed to register misc device\n"); 199 platform_set_drvdata(pdev, NULL); 200 kfree(wdt); 201 twl4030_wdt_dev = NULL; 202 return ret; 203 } 204 return 0; 205 } 206 207 static int twl4030_wdt_remove(struct platform_device *pdev) 208 { 209 struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 210 211 if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 212 if (twl4030_wdt_disable(wdt)) 213 return -EFAULT; 214 215 wdt->state &= ~TWL4030_WDT_STATE_ACTIVE; 216 misc_deregister(&wdt->miscdev); 217 218 platform_set_drvdata(pdev, NULL); 219 kfree(wdt); 220 twl4030_wdt_dev = NULL; 221 222 return 0; 223 } 224 225 #ifdef CONFIG_PM 226 static int twl4030_wdt_suspend(struct platform_device *pdev, pm_message_t state) 227 { 228 struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 229 if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 230 return twl4030_wdt_disable(wdt); 231 232 return 0; 233 } 234 235 static int twl4030_wdt_resume(struct platform_device *pdev) 236 { 237 struct twl4030_wdt *wdt = platform_get_drvdata(pdev); 238 if (wdt->state & TWL4030_WDT_STATE_ACTIVE) 239 return twl4030_wdt_enable(wdt); 240 241 return 0; 242 } 243 #else 244 #define twl4030_wdt_suspend NULL 245 #define twl4030_wdt_resume NULL 246 #endif 247 248 static struct platform_driver twl4030_wdt_driver = { 249 .probe = twl4030_wdt_probe, 250 .remove = twl4030_wdt_remove, 251 .suspend = twl4030_wdt_suspend, 252 .resume = twl4030_wdt_resume, 253 .driver = { 254 .owner = THIS_MODULE, 255 .name = "twl4030_wdt", 256 }, 257 }; 258 259 module_platform_driver(twl4030_wdt_driver); 260 261 MODULE_AUTHOR("Nokia Corporation"); 262 MODULE_LICENSE("GPL"); 263 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 264 MODULE_ALIAS("platform:twl4030_wdt"); 265 266