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/arch/hardware.h> 33 #include <asm/uaccess.h> 34 #include <asm/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 148 pnx4008_wdt_write(struct file *file, const char *data, size_t len, 149 loff_t * ppos) 150 { 151 if (len) { 152 if (!nowayout) { 153 size_t i; 154 155 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 156 157 for (i = 0; i != len; i++) { 158 char c; 159 160 if (get_user(c, data + i)) 161 return -EFAULT; 162 if (c == 'V') 163 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 164 } 165 } 166 wdt_enable(); 167 } 168 169 return len; 170 } 171 172 static struct watchdog_info ident = { 173 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 174 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 175 .identity = "PNX4008 Watchdog", 176 }; 177 178 static int 179 pnx4008_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, 180 unsigned long arg) 181 { 182 int ret = -ENOTTY; 183 int time; 184 185 switch (cmd) { 186 case WDIOC_GETSUPPORT: 187 ret = copy_to_user((struct watchdog_info *)arg, &ident, 188 sizeof(ident)) ? -EFAULT : 0; 189 break; 190 191 case WDIOC_GETSTATUS: 192 ret = put_user(0, (int *)arg); 193 break; 194 195 case WDIOC_GETBOOTSTATUS: 196 ret = put_user(boot_status, (int *)arg); 197 break; 198 199 case WDIOC_SETTIMEOUT: 200 ret = get_user(time, (int *)arg); 201 if (ret) 202 break; 203 204 if (time <= 0 || time > MAX_HEARTBEAT) { 205 ret = -EINVAL; 206 break; 207 } 208 209 heartbeat = time; 210 wdt_enable(); 211 /* Fall through */ 212 213 case WDIOC_GETTIMEOUT: 214 ret = put_user(heartbeat, (int *)arg); 215 break; 216 217 case WDIOC_KEEPALIVE: 218 wdt_enable(); 219 ret = 0; 220 break; 221 } 222 return ret; 223 } 224 225 static int pnx4008_wdt_release(struct inode *inode, struct file *file) 226 { 227 if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 228 printk(KERN_WARNING "WATCHDOG: Device closed unexpectdly\n"); 229 230 wdt_disable(); 231 clear_bit(WDT_IN_USE, &wdt_status); 232 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 233 234 return 0; 235 } 236 237 static const struct file_operations pnx4008_wdt_fops = { 238 .owner = THIS_MODULE, 239 .llseek = no_llseek, 240 .write = pnx4008_wdt_write, 241 .ioctl = pnx4008_wdt_ioctl, 242 .open = pnx4008_wdt_open, 243 .release = pnx4008_wdt_release, 244 }; 245 246 static struct miscdevice pnx4008_wdt_miscdev = { 247 .minor = WATCHDOG_MINOR, 248 .name = "watchdog", 249 .fops = &pnx4008_wdt_fops, 250 }; 251 252 static int pnx4008_wdt_probe(struct platform_device *pdev) 253 { 254 int ret = 0, size; 255 struct resource *res; 256 257 if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 258 heartbeat = DEFAULT_HEARTBEAT; 259 260 printk(KERN_INFO MODULE_NAME 261 "PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat); 262 263 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 264 if (res == NULL) { 265 printk(KERN_INFO MODULE_NAME 266 "failed to get memory region resouce\n"); 267 return -ENOENT; 268 } 269 270 size = res->end - res->start + 1; 271 wdt_mem = request_mem_region(res->start, size, pdev->name); 272 273 if (wdt_mem == NULL) { 274 printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); 275 return -ENOENT; 276 } 277 wdt_base = (void __iomem *)IO_ADDRESS(res->start); 278 279 wdt_clk = clk_get(&pdev->dev, "wdt_ck"); 280 if (IS_ERR(wdt_clk)) { 281 ret = PTR_ERR(wdt_clk); 282 release_resource(wdt_mem); 283 kfree(wdt_mem); 284 goto out; 285 } else 286 clk_set_rate(wdt_clk, 1); 287 288 ret = misc_register(&pnx4008_wdt_miscdev); 289 if (ret < 0) { 290 printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); 291 release_resource(wdt_mem); 292 kfree(wdt_mem); 293 clk_set_rate(wdt_clk, 0); 294 } else { 295 boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? 296 WDIOF_CARDRESET : 0; 297 wdt_disable(); /*disable for now */ 298 set_bit(WDT_DEVICE_INITED, &wdt_status); 299 } 300 301 out: 302 return ret; 303 } 304 305 static int pnx4008_wdt_remove(struct platform_device *pdev) 306 { 307 misc_deregister(&pnx4008_wdt_miscdev); 308 if (wdt_clk) { 309 clk_set_rate(wdt_clk, 0); 310 clk_put(wdt_clk); 311 wdt_clk = NULL; 312 } 313 if (wdt_mem) { 314 release_resource(wdt_mem); 315 kfree(wdt_mem); 316 wdt_mem = NULL; 317 } 318 return 0; 319 } 320 321 static struct platform_driver platform_wdt_driver = { 322 .driver = { 323 .name = "watchdog", 324 .owner = THIS_MODULE, 325 }, 326 .probe = pnx4008_wdt_probe, 327 .remove = pnx4008_wdt_remove, 328 }; 329 330 static int __init pnx4008_wdt_init(void) 331 { 332 return platform_driver_register(&platform_wdt_driver); 333 } 334 335 static void __exit pnx4008_wdt_exit(void) 336 { 337 platform_driver_unregister(&platform_wdt_driver); 338 } 339 340 module_init(pnx4008_wdt_init); 341 module_exit(pnx4008_wdt_exit); 342 343 MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); 344 MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); 345 346 module_param(heartbeat, int, 0); 347 MODULE_PARM_DESC(heartbeat, 348 "Watchdog heartbeat period in seconds from 1 to " 349 __MODULE_STRING(MAX_HEARTBEAT) ", default " 350 __MODULE_STRING(DEFAULT_HEARTBEAT)); 351 352 module_param(nowayout, int, 0); 353 MODULE_PARM_DESC(nowayout, 354 "Set to 1 to keep watchdog running after device release"); 355 356 MODULE_LICENSE("GPL"); 357 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 358 MODULE_ALIAS("platform:watchdog"); 359