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