1/* 2 * Watchdog driver for Freescale STMP37XX/STMP378X 3 * 4 * Author: Vitaly Wool <vital@embeddedalley.com> 5 * 6 * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. 7 * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. 8 */ 9#include <linux/init.h> 10#include <linux/kernel.h> 11#include <linux/fs.h> 12#include <linux/miscdevice.h> 13#include <linux/watchdog.h> 14#include <linux/platform_device.h> 15#include <linux/spinlock.h> 16#include <linux/uaccess.h> 17 18#include <mach/platform.h> 19#include <mach/regs-rtc.h> 20 21#define DEFAULT_HEARTBEAT 19 22#define MAX_HEARTBEAT (0x10000000 >> 6) 23 24/* missing bitmask in headers */ 25#define BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER 0x80000000 26 27#define WDT_IN_USE 0 28#define WDT_OK_TO_CLOSE 1 29 30#define WDOG_COUNTER_RATE 1000 /* 1 kHz clock */ 31 32static DEFINE_SPINLOCK(stmp3xxx_wdt_io_lock); 33static unsigned long wdt_status; 34static const int nowayout = WATCHDOG_NOWAYOUT; 35static int heartbeat = DEFAULT_HEARTBEAT; 36static unsigned long boot_status; 37 38static void wdt_enable(u32 value) 39{ 40 spin_lock(&stmp3xxx_wdt_io_lock); 41 __raw_writel(value, REGS_RTC_BASE + HW_RTC_WATCHDOG); 42 stmp3xxx_setl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL); 43 stmp3xxx_setl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, 44 REGS_RTC_BASE + HW_RTC_PERSISTENT1); 45 spin_unlock(&stmp3xxx_wdt_io_lock); 46} 47 48static void wdt_disable(void) 49{ 50 spin_lock(&stmp3xxx_wdt_io_lock); 51 stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, 52 REGS_RTC_BASE + HW_RTC_PERSISTENT1); 53 stmp3xxx_clearl(BM_RTC_CTRL_WATCHDOGEN, REGS_RTC_BASE + HW_RTC_CTRL); 54 spin_unlock(&stmp3xxx_wdt_io_lock); 55} 56 57static void wdt_ping(void) 58{ 59 wdt_enable(heartbeat * WDOG_COUNTER_RATE); 60} 61 62static int stmp3xxx_wdt_open(struct inode *inode, struct file *file) 63{ 64 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 65 return -EBUSY; 66 67 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 68 wdt_ping(); 69 70 return nonseekable_open(inode, file); 71} 72 73static ssize_t stmp3xxx_wdt_write(struct file *file, const char __user *data, 74 size_t len, loff_t *ppos) 75{ 76 if (len) { 77 if (!nowayout) { 78 size_t i; 79 80 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 81 82 for (i = 0; i != len; i++) { 83 char c; 84 85 if (get_user(c, data + i)) 86 return -EFAULT; 87 if (c == 'V') 88 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 89 } 90 } 91 wdt_ping(); 92 } 93 94 return len; 95} 96 97static const struct watchdog_info ident = { 98 .options = WDIOF_CARDRESET | 99 WDIOF_MAGICCLOSE | 100 WDIOF_SETTIMEOUT | 101 WDIOF_KEEPALIVEPING, 102 .identity = "STMP3XXX Watchdog", 103}; 104 105static long stmp3xxx_wdt_ioctl(struct file *file, unsigned int cmd, 106 unsigned long arg) 107{ 108 void __user *argp = (void __user *)arg; 109 int __user *p = argp; 110 int new_heartbeat, opts; 111 int ret = -ENOTTY; 112 113 switch (cmd) { 114 case WDIOC_GETSUPPORT: 115 ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; 116 break; 117 118 case WDIOC_GETSTATUS: 119 ret = put_user(0, p); 120 break; 121 122 case WDIOC_GETBOOTSTATUS: 123 ret = put_user(boot_status, p); 124 break; 125 126 case WDIOC_SETOPTIONS: 127 if (get_user(opts, p)) { 128 ret = -EFAULT; 129 break; 130 } 131 if (opts & WDIOS_DISABLECARD) 132 wdt_disable(); 133 else if (opts & WDIOS_ENABLECARD) 134 wdt_ping(); 135 else { 136 pr_debug("%s: unknown option 0x%x\n", __func__, opts); 137 ret = -EINVAL; 138 break; 139 } 140 ret = 0; 141 break; 142 143 case WDIOC_KEEPALIVE: 144 wdt_ping(); 145 ret = 0; 146 break; 147 148 case WDIOC_SETTIMEOUT: 149 if (get_user(new_heartbeat, p)) { 150 ret = -EFAULT; 151 break; 152 } 153 if (new_heartbeat <= 0 || new_heartbeat > MAX_HEARTBEAT) { 154 ret = -EINVAL; 155 break; 156 } 157 158 heartbeat = new_heartbeat; 159 wdt_ping(); 160 /* Fall through */ 161 162 case WDIOC_GETTIMEOUT: 163 ret = put_user(heartbeat, p); 164 break; 165 } 166 return ret; 167} 168 169static int stmp3xxx_wdt_release(struct inode *inode, struct file *file) 170{ 171 int ret = 0; 172 173 if (!nowayout) { 174 if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { 175 wdt_ping(); 176 pr_debug("%s: Device closed unexpectdly\n", __func__); 177 ret = -EINVAL; 178 } else { 179 wdt_disable(); 180 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 181 } 182 } 183 clear_bit(WDT_IN_USE, &wdt_status); 184 185 return ret; 186} 187 188static const struct file_operations stmp3xxx_wdt_fops = { 189 .owner = THIS_MODULE, 190 .llseek = no_llseek, 191 .write = stmp3xxx_wdt_write, 192 .unlocked_ioctl = stmp3xxx_wdt_ioctl, 193 .open = stmp3xxx_wdt_open, 194 .release = stmp3xxx_wdt_release, 195}; 196 197static struct miscdevice stmp3xxx_wdt_miscdev = { 198 .minor = WATCHDOG_MINOR, 199 .name = "watchdog", 200 .fops = &stmp3xxx_wdt_fops, 201}; 202 203static int __devinit stmp3xxx_wdt_probe(struct platform_device *pdev) 204{ 205 int ret = 0; 206 207 if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 208 heartbeat = DEFAULT_HEARTBEAT; 209 210 boot_status = __raw_readl(REGS_RTC_BASE + HW_RTC_PERSISTENT1) & 211 BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER; 212 boot_status = !!boot_status; 213 stmp3xxx_clearl(BV_RTC_PERSISTENT1_GENERAL__RTC_FORCE_UPDATER, 214 REGS_RTC_BASE + HW_RTC_PERSISTENT1); 215 wdt_disable(); /* disable for now */ 216 217 ret = misc_register(&stmp3xxx_wdt_miscdev); 218 if (ret < 0) { 219 dev_err(&pdev->dev, "cannot register misc device\n"); 220 return ret; 221 } 222 223 printk(KERN_INFO "stmp3xxx watchdog: initialized, heartbeat %d sec\n", 224 heartbeat); 225 226 return ret; 227} 228 229static int __devexit stmp3xxx_wdt_remove(struct platform_device *pdev) 230{ 231 misc_deregister(&stmp3xxx_wdt_miscdev); 232 return 0; 233} 234 235#ifdef CONFIG_PM 236static int wdt_suspended; 237static u32 wdt_saved_time; 238 239static int stmp3xxx_wdt_suspend(struct platform_device *pdev, 240 pm_message_t state) 241{ 242 if (__raw_readl(REGS_RTC_BASE + HW_RTC_CTRL) & 243 BM_RTC_CTRL_WATCHDOGEN) { 244 wdt_suspended = 1; 245 wdt_saved_time = __raw_readl(REGS_RTC_BASE + HW_RTC_WATCHDOG); 246 wdt_disable(); 247 } 248 return 0; 249} 250 251static int stmp3xxx_wdt_resume(struct platform_device *pdev) 252{ 253 if (wdt_suspended) { 254 wdt_enable(wdt_saved_time); 255 wdt_suspended = 0; 256 } 257 return 0; 258} 259#else 260#define stmp3xxx_wdt_suspend NULL 261#define stmp3xxx_wdt_resume NULL 262#endif 263 264static struct platform_driver platform_wdt_driver = { 265 .driver = { 266 .name = "stmp3xxx_wdt", 267 }, 268 .probe = stmp3xxx_wdt_probe, 269 .remove = __devexit_p(stmp3xxx_wdt_remove), 270 .suspend = stmp3xxx_wdt_suspend, 271 .resume = stmp3xxx_wdt_resume, 272}; 273 274static int __init stmp3xxx_wdt_init(void) 275{ 276 return platform_driver_register(&platform_wdt_driver); 277} 278 279static void __exit stmp3xxx_wdt_exit(void) 280{ 281 return platform_driver_unregister(&platform_wdt_driver); 282} 283 284module_init(stmp3xxx_wdt_init); 285module_exit(stmp3xxx_wdt_exit); 286 287MODULE_DESCRIPTION("STMP3XXX Watchdog Driver"); 288MODULE_LICENSE("GPL"); 289 290module_param(heartbeat, int, 0); 291MODULE_PARM_DESC(heartbeat, 292 "Watchdog heartbeat period in seconds from 1 to " 293 __MODULE_STRING(MAX_HEARTBEAT) ", default " 294 __MODULE_STRING(DEFAULT_HEARTBEAT)); 295 296MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 297