1 /* 2 * drivers/char/watchdog/pnx4008_wdt.c 3 * 4 * Watchdog driver for PNX4008 board 5 * 6 * Authors: Dmitry Chigirev <source@mvista.com>, 7 * Vitaly Wool <vitalywool@gmail.com> 8 * Based on sa1100 driver, 9 * Copyright (C) 2000 Oleg Drokin <green@crimea.edu> 10 * 11 * 2005-2006 (c) MontaVista Software, Inc. This file is licensed under 12 * the terms of the GNU General Public License version 2. This program 13 * is licensed "as is" without any warranty of any kind, whether express 14 * or implied. 15 */ 16 17 #include <linux/module.h> 18 #include <linux/moduleparam.h> 19 #include <linux/types.h> 20 #include <linux/kernel.h> 21 #include <linux/fs.h> 22 #include <linux/miscdevice.h> 23 #include <linux/watchdog.h> 24 #include <linux/init.h> 25 #include <linux/bitops.h> 26 #include <linux/ioport.h> 27 #include <linux/device.h> 28 #include <linux/platform_device.h> 29 #include <linux/clk.h> 30 #include <linux/spinlock.h> 31 #include <linux/uaccess.h> 32 #include <linux/io.h> 33 #include <mach/hardware.h> 34 35 #define MODULE_NAME "PNX4008-WDT: " 36 37 /* WatchDog Timer - Chapter 23 Page 207 */ 38 39 #define DEFAULT_HEARTBEAT 19 40 #define MAX_HEARTBEAT 60 41 42 /* Watchdog timer register set definition */ 43 #define WDTIM_INT(p) ((p) + 0x0) 44 #define WDTIM_CTRL(p) ((p) + 0x4) 45 #define WDTIM_COUNTER(p) ((p) + 0x8) 46 #define WDTIM_MCTRL(p) ((p) + 0xC) 47 #define WDTIM_MATCH0(p) ((p) + 0x10) 48 #define WDTIM_EMR(p) ((p) + 0x14) 49 #define WDTIM_PULSE(p) ((p) + 0x18) 50 #define WDTIM_RES(p) ((p) + 0x1C) 51 52 /* WDTIM_INT bit definitions */ 53 #define MATCH_INT 1 54 55 /* WDTIM_CTRL bit definitions */ 56 #define COUNT_ENAB 1 57 #define RESET_COUNT (1<<1) 58 #define DEBUG_EN (1<<2) 59 60 /* WDTIM_MCTRL bit definitions */ 61 #define MR0_INT 1 62 #undef RESET_COUNT0 63 #define RESET_COUNT0 (1<<2) 64 #define STOP_COUNT0 (1<<2) 65 #define M_RES1 (1<<3) 66 #define M_RES2 (1<<4) 67 #define RESFRC1 (1<<5) 68 #define RESFRC2 (1<<6) 69 70 /* WDTIM_EMR bit definitions */ 71 #define EXT_MATCH0 1 72 #define MATCH_OUTPUT_HIGH (2<<4) /*a MATCH_CTRL setting */ 73 74 /* WDTIM_RES bit definitions */ 75 #define WDOG_RESET 1 /* read only */ 76 77 #define WDOG_COUNTER_RATE 13000000 /*the counter clock is 13 MHz fixed */ 78 79 static int nowayout = WATCHDOG_NOWAYOUT; 80 static int heartbeat = DEFAULT_HEARTBEAT; 81 82 static DEFINE_SPINLOCK(io_lock); 83 static unsigned long wdt_status; 84 #define WDT_IN_USE 0 85 #define WDT_OK_TO_CLOSE 1 86 #define WDT_REGION_INITED 2 87 #define WDT_DEVICE_INITED 3 88 89 static unsigned long boot_status; 90 91 static struct resource *wdt_mem; 92 static void __iomem *wdt_base; 93 struct clk *wdt_clk; 94 95 static void wdt_enable(void) 96 { 97 spin_lock(&io_lock); 98 99 if (wdt_clk) 100 clk_set_rate(wdt_clk, 1); 101 102 /* stop counter, initiate counter reset */ 103 __raw_writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); 104 /*wait for reset to complete. 100% guarantee event */ 105 while (__raw_readl(WDTIM_COUNTER(wdt_base))) 106 cpu_relax(); 107 /* internal and external reset, stop after that */ 108 __raw_writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, 109 WDTIM_MCTRL(wdt_base)); 110 /* configure match output */ 111 __raw_writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); 112 /* clear interrupt, just in case */ 113 __raw_writel(MATCH_INT, WDTIM_INT(wdt_base)); 114 /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ 115 __raw_writel(0xFFFF, WDTIM_PULSE(wdt_base)); 116 __raw_writel(heartbeat * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); 117 /*enable counter, stop when debugger active */ 118 __raw_writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); 119 120 spin_unlock(&io_lock); 121 } 122 123 static void wdt_disable(void) 124 { 125 spin_lock(&io_lock); 126 127 __raw_writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ 128 if (wdt_clk) 129 clk_set_rate(wdt_clk, 0); 130 131 spin_unlock(&io_lock); 132 } 133 134 static int pnx4008_wdt_open(struct inode *inode, struct file *file) 135 { 136 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 137 return -EBUSY; 138 139 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 140 141 wdt_enable(); 142 143 return nonseekable_open(inode, file); 144 } 145 146 static ssize_t pnx4008_wdt_write(struct file *file, const char *data, 147 size_t len, loff_t *ppos) 148 { 149 if (len) { 150 if (!nowayout) { 151 size_t i; 152 153 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 154 155 for (i = 0; i != len; i++) { 156 char c; 157 158 if (get_user(c, data + i)) 159 return -EFAULT; 160 if (c == 'V') 161 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 162 } 163 } 164 wdt_enable(); 165 } 166 167 return len; 168 } 169 170 static const struct watchdog_info ident = { 171 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 172 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 173 .identity = "PNX4008 Watchdog", 174 }; 175 176 static long pnx4008_wdt_ioctl(struct file *file, unsigned int cmd, 177 unsigned long arg) 178 { 179 int ret = -ENOTTY; 180 int time; 181 182 switch (cmd) { 183 case WDIOC_GETSUPPORT: 184 ret = copy_to_user((struct watchdog_info *)arg, &ident, 185 sizeof(ident)) ? -EFAULT : 0; 186 break; 187 188 case WDIOC_GETSTATUS: 189 ret = put_user(0, (int *)arg); 190 break; 191 192 case WDIOC_GETBOOTSTATUS: 193 ret = put_user(boot_status, (int *)arg); 194 break; 195 196 case WDIOC_KEEPALIVE: 197 wdt_enable(); 198 ret = 0; 199 break; 200 201 case WDIOC_SETTIMEOUT: 202 ret = get_user(time, (int *)arg); 203 if (ret) 204 break; 205 206 if (time <= 0 || time > MAX_HEARTBEAT) { 207 ret = -EINVAL; 208 break; 209 } 210 211 heartbeat = time; 212 wdt_enable(); 213 /* Fall through */ 214 215 case WDIOC_GETTIMEOUT: 216 ret = put_user(heartbeat, (int *)arg); 217 break; 218 } 219 return ret; 220 } 221 222 static int pnx4008_wdt_release(struct inode *inode, struct file *file) 223 { 224 if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 225 printk(KERN_WARNING "WATCHDOG: Device closed unexpectdly\n"); 226 227 wdt_disable(); 228 clear_bit(WDT_IN_USE, &wdt_status); 229 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 230 231 return 0; 232 } 233 234 static const struct file_operations pnx4008_wdt_fops = { 235 .owner = THIS_MODULE, 236 .llseek = no_llseek, 237 .write = pnx4008_wdt_write, 238 .unlocked_ioctl = pnx4008_wdt_ioctl, 239 .open = pnx4008_wdt_open, 240 .release = pnx4008_wdt_release, 241 }; 242 243 static struct miscdevice pnx4008_wdt_miscdev = { 244 .minor = WATCHDOG_MINOR, 245 .name = "watchdog", 246 .fops = &pnx4008_wdt_fops, 247 }; 248 249 static int pnx4008_wdt_probe(struct platform_device *pdev) 250 { 251 int ret = 0, size; 252 struct resource *res; 253 254 if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 255 heartbeat = DEFAULT_HEARTBEAT; 256 257 printk(KERN_INFO MODULE_NAME 258 "PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat); 259 260 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 261 if (res == NULL) { 262 printk(KERN_INFO MODULE_NAME 263 "failed to get memory region resouce\n"); 264 return -ENOENT; 265 } 266 267 size = res->end - res->start + 1; 268 wdt_mem = request_mem_region(res->start, size, pdev->name); 269 270 if (wdt_mem == NULL) { 271 printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); 272 return -ENOENT; 273 } 274 wdt_base = (void __iomem *)IO_ADDRESS(res->start); 275 276 wdt_clk = clk_get(&pdev->dev, "wdt_ck"); 277 if (IS_ERR(wdt_clk)) { 278 ret = PTR_ERR(wdt_clk); 279 release_resource(wdt_mem); 280 kfree(wdt_mem); 281 goto out; 282 } else 283 clk_set_rate(wdt_clk, 1); 284 285 ret = misc_register(&pnx4008_wdt_miscdev); 286 if (ret < 0) { 287 printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); 288 release_resource(wdt_mem); 289 kfree(wdt_mem); 290 clk_set_rate(wdt_clk, 0); 291 } else { 292 boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? 293 WDIOF_CARDRESET : 0; 294 wdt_disable(); /*disable for now */ 295 set_bit(WDT_DEVICE_INITED, &wdt_status); 296 } 297 298 out: 299 return ret; 300 } 301 302 static int pnx4008_wdt_remove(struct platform_device *pdev) 303 { 304 misc_deregister(&pnx4008_wdt_miscdev); 305 if (wdt_clk) { 306 clk_set_rate(wdt_clk, 0); 307 clk_put(wdt_clk); 308 wdt_clk = NULL; 309 } 310 if (wdt_mem) { 311 release_resource(wdt_mem); 312 kfree(wdt_mem); 313 wdt_mem = NULL; 314 } 315 return 0; 316 } 317 318 static struct platform_driver platform_wdt_driver = { 319 .driver = { 320 .name = "watchdog", 321 .owner = THIS_MODULE, 322 }, 323 .probe = pnx4008_wdt_probe, 324 .remove = pnx4008_wdt_remove, 325 }; 326 327 static int __init pnx4008_wdt_init(void) 328 { 329 return platform_driver_register(&platform_wdt_driver); 330 } 331 332 static void __exit pnx4008_wdt_exit(void) 333 { 334 platform_driver_unregister(&platform_wdt_driver); 335 } 336 337 module_init(pnx4008_wdt_init); 338 module_exit(pnx4008_wdt_exit); 339 340 MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); 341 MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); 342 343 module_param(heartbeat, int, 0); 344 MODULE_PARM_DESC(heartbeat, 345 "Watchdog heartbeat period in seconds from 1 to " 346 __MODULE_STRING(MAX_HEARTBEAT) ", default " 347 __MODULE_STRING(DEFAULT_HEARTBEAT)); 348 349 module_param(nowayout, int, 0); 350 MODULE_PARM_DESC(nowayout, 351 "Set to 1 to keep watchdog running after device release"); 352 353 MODULE_LICENSE("GPL"); 354 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 355 MODULE_ALIAS("platform:watchdog"); 356