1006948baSMark Brown /* 2006948baSMark Brown * Watchdog driver for the wm8350 3006948baSMark Brown * 4006948baSMark Brown * Copyright (C) 2007, 2008 Wolfson Microelectronics <linux@wolfsonmicro.com> 5006948baSMark Brown * 6006948baSMark Brown * This program is free software; you can redistribute it and/or 7006948baSMark Brown * modify it under the terms of the GNU General Public License 8006948baSMark Brown * as published by the Free Software Foundation 9006948baSMark Brown */ 10006948baSMark Brown 11006948baSMark Brown #include <linux/module.h> 12006948baSMark Brown #include <linux/moduleparam.h> 13006948baSMark Brown #include <linux/types.h> 14006948baSMark Brown #include <linux/kernel.h> 15006948baSMark Brown #include <linux/fs.h> 16006948baSMark Brown #include <linux/miscdevice.h> 17006948baSMark Brown #include <linux/platform_device.h> 18006948baSMark Brown #include <linux/watchdog.h> 19006948baSMark Brown #include <linux/uaccess.h> 20006948baSMark Brown #include <linux/mfd/wm8350/core.h> 21006948baSMark Brown 22006948baSMark Brown static int nowayout = WATCHDOG_NOWAYOUT; 23006948baSMark Brown module_param(nowayout, int, 0); 24006948baSMark Brown MODULE_PARM_DESC(nowayout, 25006948baSMark Brown "Watchdog cannot be stopped once started (default=" 26006948baSMark Brown __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 27006948baSMark Brown 28006948baSMark Brown static unsigned long wm8350_wdt_users; 29006948baSMark Brown static struct miscdevice wm8350_wdt_miscdev; 30006948baSMark Brown static int wm8350_wdt_expect_close; 31006948baSMark Brown static DEFINE_MUTEX(wdt_mutex); 32006948baSMark Brown 33006948baSMark Brown static struct { 34006948baSMark Brown int time; /* Seconds */ 35006948baSMark Brown u16 val; /* To be set in WM8350_SYSTEM_CONTROL_2 */ 36006948baSMark Brown } wm8350_wdt_cfgs[] = { 37006948baSMark Brown { 1, 0x02 }, 38006948baSMark Brown { 2, 0x04 }, 39006948baSMark Brown { 4, 0x05 }, 40006948baSMark Brown }; 41006948baSMark Brown 42006948baSMark Brown static struct wm8350 *get_wm8350(void) 43006948baSMark Brown { 44006948baSMark Brown return dev_get_drvdata(wm8350_wdt_miscdev.parent); 45006948baSMark Brown } 46006948baSMark Brown 47006948baSMark Brown static int wm8350_wdt_set_timeout(struct wm8350 *wm8350, u16 value) 48006948baSMark Brown { 49006948baSMark Brown int ret; 50006948baSMark Brown u16 reg; 51006948baSMark Brown 52006948baSMark Brown mutex_lock(&wdt_mutex); 53006948baSMark Brown wm8350_reg_unlock(wm8350); 54006948baSMark Brown 55006948baSMark Brown reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2); 56006948baSMark Brown reg &= ~WM8350_WDOG_TO_MASK; 57006948baSMark Brown reg |= value; 58006948baSMark Brown ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg); 59006948baSMark Brown 60006948baSMark Brown wm8350_reg_lock(wm8350); 61006948baSMark Brown mutex_unlock(&wdt_mutex); 62006948baSMark Brown 63006948baSMark Brown return ret; 64006948baSMark Brown } 65006948baSMark Brown 66006948baSMark Brown static int wm8350_wdt_start(struct wm8350 *wm8350) 67006948baSMark Brown { 68006948baSMark Brown int ret; 69006948baSMark Brown u16 reg; 70006948baSMark Brown 71006948baSMark Brown mutex_lock(&wdt_mutex); 72006948baSMark Brown wm8350_reg_unlock(wm8350); 73006948baSMark Brown 74006948baSMark Brown reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2); 75006948baSMark Brown reg &= ~WM8350_WDOG_MODE_MASK; 76006948baSMark Brown reg |= 0x20; 77006948baSMark Brown ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg); 78006948baSMark Brown 79006948baSMark Brown wm8350_reg_lock(wm8350); 80006948baSMark Brown mutex_unlock(&wdt_mutex); 81006948baSMark Brown 82006948baSMark Brown return ret; 83006948baSMark Brown } 84006948baSMark Brown 85006948baSMark Brown static int wm8350_wdt_stop(struct wm8350 *wm8350) 86006948baSMark Brown { 87006948baSMark Brown int ret; 88006948baSMark Brown u16 reg; 89006948baSMark Brown 90006948baSMark Brown mutex_lock(&wdt_mutex); 91006948baSMark Brown wm8350_reg_unlock(wm8350); 92006948baSMark Brown 93006948baSMark Brown reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2); 94006948baSMark Brown reg &= ~WM8350_WDOG_MODE_MASK; 95006948baSMark Brown ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg); 96006948baSMark Brown 97006948baSMark Brown wm8350_reg_lock(wm8350); 98006948baSMark Brown mutex_unlock(&wdt_mutex); 99006948baSMark Brown 100006948baSMark Brown return ret; 101006948baSMark Brown } 102006948baSMark Brown 103006948baSMark Brown static int wm8350_wdt_kick(struct wm8350 *wm8350) 104006948baSMark Brown { 105006948baSMark Brown int ret; 106006948baSMark Brown u16 reg; 107006948baSMark Brown 108006948baSMark Brown mutex_lock(&wdt_mutex); 109006948baSMark Brown 110006948baSMark Brown reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2); 111006948baSMark Brown ret = wm8350_reg_write(wm8350, WM8350_SYSTEM_CONTROL_2, reg); 112006948baSMark Brown 113006948baSMark Brown mutex_unlock(&wdt_mutex); 114006948baSMark Brown 115006948baSMark Brown return ret; 116006948baSMark Brown } 117006948baSMark Brown 118006948baSMark Brown static int wm8350_wdt_open(struct inode *inode, struct file *file) 119006948baSMark Brown { 120006948baSMark Brown struct wm8350 *wm8350 = get_wm8350(); 121006948baSMark Brown int ret; 122006948baSMark Brown 123006948baSMark Brown if (!wm8350) 124006948baSMark Brown return -ENODEV; 125006948baSMark Brown 126006948baSMark Brown if (test_and_set_bit(0, &wm8350_wdt_users)) 127006948baSMark Brown return -EBUSY; 128006948baSMark Brown 129006948baSMark Brown ret = wm8350_wdt_start(wm8350); 130006948baSMark Brown if (ret != 0) 131006948baSMark Brown return ret; 132006948baSMark Brown 133006948baSMark Brown return nonseekable_open(inode, file); 134006948baSMark Brown } 135006948baSMark Brown 136006948baSMark Brown static int wm8350_wdt_release(struct inode *inode, struct file *file) 137006948baSMark Brown { 138006948baSMark Brown struct wm8350 *wm8350 = get_wm8350(); 139006948baSMark Brown 140006948baSMark Brown if (wm8350_wdt_expect_close) 141006948baSMark Brown wm8350_wdt_stop(wm8350); 142006948baSMark Brown else { 143006948baSMark Brown dev_warn(wm8350->dev, "Watchdog device closed uncleanly\n"); 144006948baSMark Brown wm8350_wdt_kick(wm8350); 145006948baSMark Brown } 146006948baSMark Brown 147006948baSMark Brown clear_bit(0, &wm8350_wdt_users); 148006948baSMark Brown 149006948baSMark Brown return 0; 150006948baSMark Brown } 151006948baSMark Brown 152006948baSMark Brown static ssize_t wm8350_wdt_write(struct file *file, 153006948baSMark Brown const char __user *data, size_t count, 154006948baSMark Brown loff_t *ppos) 155006948baSMark Brown { 156006948baSMark Brown struct wm8350 *wm8350 = get_wm8350(); 157006948baSMark Brown size_t i; 158006948baSMark Brown 159006948baSMark Brown if (count) { 160006948baSMark Brown wm8350_wdt_kick(wm8350); 161006948baSMark Brown 162006948baSMark Brown if (!nowayout) { 163006948baSMark Brown /* In case it was set long ago */ 164006948baSMark Brown wm8350_wdt_expect_close = 0; 165006948baSMark Brown 166006948baSMark Brown /* scan to see whether or not we got the magic 167006948baSMark Brown character */ 168006948baSMark Brown for (i = 0; i != count; i++) { 169006948baSMark Brown char c; 170006948baSMark Brown if (get_user(c, data + i)) 171006948baSMark Brown return -EFAULT; 172006948baSMark Brown if (c == 'V') 173006948baSMark Brown wm8350_wdt_expect_close = 42; 174006948baSMark Brown } 175006948baSMark Brown } 176006948baSMark Brown } 177006948baSMark Brown return count; 178006948baSMark Brown } 179006948baSMark Brown 180006948baSMark Brown static struct watchdog_info ident = { 181006948baSMark Brown .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 182006948baSMark Brown .identity = "WM8350 Watchdog", 183006948baSMark Brown }; 184006948baSMark Brown 185006948baSMark Brown static long wm8350_wdt_ioctl(struct file *file, unsigned int cmd, 186006948baSMark Brown unsigned long arg) 187006948baSMark Brown { 188006948baSMark Brown struct wm8350 *wm8350 = get_wm8350(); 189006948baSMark Brown int ret = -ENOTTY, time, i; 190006948baSMark Brown void __user *argp = (void __user *)arg; 191006948baSMark Brown int __user *p = argp; 192006948baSMark Brown u16 reg; 193006948baSMark Brown 194006948baSMark Brown switch (cmd) { 195006948baSMark Brown case WDIOC_GETSUPPORT: 196006948baSMark Brown ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; 197006948baSMark Brown break; 198006948baSMark Brown 199006948baSMark Brown case WDIOC_GETSTATUS: 200006948baSMark Brown case WDIOC_GETBOOTSTATUS: 201006948baSMark Brown ret = put_user(0, p); 202006948baSMark Brown break; 203006948baSMark Brown 204006948baSMark Brown case WDIOC_SETOPTIONS: 205006948baSMark Brown { 206006948baSMark Brown int options; 207006948baSMark Brown 208006948baSMark Brown if (get_user(options, p)) 209006948baSMark Brown return -EFAULT; 210006948baSMark Brown 211006948baSMark Brown ret = -EINVAL; 212006948baSMark Brown 213006948baSMark Brown /* Setting both simultaneously means at least one must fail */ 214006948baSMark Brown if (options == WDIOS_DISABLECARD) 215006948baSMark Brown ret = wm8350_wdt_start(wm8350); 216006948baSMark Brown 217006948baSMark Brown if (options == WDIOS_ENABLECARD) 218006948baSMark Brown ret = wm8350_wdt_stop(wm8350); 219006948baSMark Brown break; 220006948baSMark Brown } 221006948baSMark Brown 222006948baSMark Brown case WDIOC_KEEPALIVE: 223006948baSMark Brown ret = wm8350_wdt_kick(wm8350); 224006948baSMark Brown break; 225006948baSMark Brown 226006948baSMark Brown case WDIOC_SETTIMEOUT: 227006948baSMark Brown ret = get_user(time, p); 228006948baSMark Brown if (ret) 229006948baSMark Brown break; 230006948baSMark Brown 231006948baSMark Brown if (time == 0) { 232006948baSMark Brown if (nowayout) 233006948baSMark Brown ret = -EINVAL; 234006948baSMark Brown else 235006948baSMark Brown wm8350_wdt_stop(wm8350); 236006948baSMark Brown break; 237006948baSMark Brown } 238006948baSMark Brown 239006948baSMark Brown for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++) 240006948baSMark Brown if (wm8350_wdt_cfgs[i].time == time) 241006948baSMark Brown break; 242006948baSMark Brown if (i == ARRAY_SIZE(wm8350_wdt_cfgs)) 243006948baSMark Brown ret = -EINVAL; 244006948baSMark Brown else 245006948baSMark Brown ret = wm8350_wdt_set_timeout(wm8350, 246006948baSMark Brown wm8350_wdt_cfgs[i].val); 247006948baSMark Brown break; 248006948baSMark Brown 249006948baSMark Brown case WDIOC_GETTIMEOUT: 250006948baSMark Brown reg = wm8350_reg_read(wm8350, WM8350_SYSTEM_CONTROL_2); 251006948baSMark Brown reg &= WM8350_WDOG_TO_MASK; 252006948baSMark Brown for (i = 0; i < ARRAY_SIZE(wm8350_wdt_cfgs); i++) 253006948baSMark Brown if (wm8350_wdt_cfgs[i].val == reg) 254006948baSMark Brown break; 255006948baSMark Brown if (i == ARRAY_SIZE(wm8350_wdt_cfgs)) { 256006948baSMark Brown dev_warn(wm8350->dev, 257006948baSMark Brown "Unknown watchdog configuration: %x\n", reg); 258006948baSMark Brown ret = -EINVAL; 259006948baSMark Brown } else 260006948baSMark Brown ret = put_user(wm8350_wdt_cfgs[i].time, p); 261006948baSMark Brown 262006948baSMark Brown } 263006948baSMark Brown 264006948baSMark Brown return ret; 265006948baSMark Brown } 266006948baSMark Brown 267006948baSMark Brown static const struct file_operations wm8350_wdt_fops = { 268006948baSMark Brown .owner = THIS_MODULE, 269006948baSMark Brown .llseek = no_llseek, 270006948baSMark Brown .write = wm8350_wdt_write, 271006948baSMark Brown .unlocked_ioctl = wm8350_wdt_ioctl, 272006948baSMark Brown .open = wm8350_wdt_open, 273006948baSMark Brown .release = wm8350_wdt_release, 274006948baSMark Brown }; 275006948baSMark Brown 276006948baSMark Brown static struct miscdevice wm8350_wdt_miscdev = { 277006948baSMark Brown .minor = WATCHDOG_MINOR, 278006948baSMark Brown .name = "watchdog", 279006948baSMark Brown .fops = &wm8350_wdt_fops, 280006948baSMark Brown }; 281006948baSMark Brown 282b1cf3e99SMark Brown static int __devinit wm8350_wdt_probe(struct platform_device *pdev) 283006948baSMark Brown { 284006948baSMark Brown struct wm8350 *wm8350 = platform_get_drvdata(pdev); 285006948baSMark Brown 286006948baSMark Brown if (!wm8350) { 287006948baSMark Brown dev_err(wm8350->dev, "No driver data supplied\n"); 288006948baSMark Brown return -ENODEV; 289006948baSMark Brown } 290006948baSMark Brown 291006948baSMark Brown /* Default to 4s timeout */ 292006948baSMark Brown wm8350_wdt_set_timeout(wm8350, 0x05); 293006948baSMark Brown 294006948baSMark Brown wm8350_wdt_miscdev.parent = &pdev->dev; 295006948baSMark Brown 296006948baSMark Brown return misc_register(&wm8350_wdt_miscdev); 297006948baSMark Brown } 298006948baSMark Brown 299b1cf3e99SMark Brown static int __devexit wm8350_wdt_remove(struct platform_device *pdev) 300006948baSMark Brown { 301006948baSMark Brown misc_deregister(&wm8350_wdt_miscdev); 302006948baSMark Brown 303006948baSMark Brown return 0; 304006948baSMark Brown } 305006948baSMark Brown 306006948baSMark Brown static struct platform_driver wm8350_wdt_driver = { 307006948baSMark Brown .probe = wm8350_wdt_probe, 308b1cf3e99SMark Brown .remove = __devexit_p(wm8350_wdt_remove), 309006948baSMark Brown .driver = { 310006948baSMark Brown .name = "wm8350-wdt", 311006948baSMark Brown }, 312006948baSMark Brown }; 313006948baSMark Brown 314006948baSMark Brown static int __init wm8350_wdt_init(void) 315006948baSMark Brown { 316006948baSMark Brown return platform_driver_register(&wm8350_wdt_driver); 317006948baSMark Brown } 318006948baSMark Brown module_init(wm8350_wdt_init); 319006948baSMark Brown 320006948baSMark Brown static void __exit wm8350_wdt_exit(void) 321006948baSMark Brown { 322006948baSMark Brown platform_driver_unregister(&wm8350_wdt_driver); 323006948baSMark Brown } 324006948baSMark Brown module_exit(wm8350_wdt_exit); 325006948baSMark Brown 326006948baSMark Brown MODULE_AUTHOR("Mark Brown"); 327006948baSMark Brown MODULE_DESCRIPTION("WM8350 Watchdog"); 328006948baSMark Brown MODULE_LICENSE("GPL"); 329006948baSMark Brown MODULE_ALIAS("platform:wm8350-wdt"); 330