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