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 <linux/slab.h> 34 #include <mach/hardware.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 /* stop counter, initiate counter reset */ 101 __raw_writel(RESET_COUNT, WDTIM_CTRL(wdt_base)); 102 /*wait for reset to complete. 100% guarantee event */ 103 while (__raw_readl(WDTIM_COUNTER(wdt_base))) 104 cpu_relax(); 105 /* internal and external reset, stop after that */ 106 __raw_writel(M_RES2 | STOP_COUNT0 | RESET_COUNT0, 107 WDTIM_MCTRL(wdt_base)); 108 /* configure match output */ 109 __raw_writel(MATCH_OUTPUT_HIGH, WDTIM_EMR(wdt_base)); 110 /* clear interrupt, just in case */ 111 __raw_writel(MATCH_INT, WDTIM_INT(wdt_base)); 112 /* the longest pulse period 65541/(13*10^6) seconds ~ 5 ms. */ 113 __raw_writel(0xFFFF, WDTIM_PULSE(wdt_base)); 114 __raw_writel(heartbeat * WDOG_COUNTER_RATE, WDTIM_MATCH0(wdt_base)); 115 /*enable counter, stop when debugger active */ 116 __raw_writel(COUNT_ENAB | DEBUG_EN, WDTIM_CTRL(wdt_base)); 117 118 spin_unlock(&io_lock); 119 } 120 121 static void wdt_disable(void) 122 { 123 spin_lock(&io_lock); 124 125 __raw_writel(0, WDTIM_CTRL(wdt_base)); /*stop counter */ 126 127 spin_unlock(&io_lock); 128 } 129 130 static int pnx4008_wdt_open(struct inode *inode, struct file *file) 131 { 132 int ret; 133 134 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 135 return -EBUSY; 136 137 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 138 139 ret = clk_enable(wdt_clk); 140 if (ret) { 141 clear_bit(WDT_IN_USE, &wdt_status); 142 return ret; 143 } 144 145 wdt_enable(); 146 147 return nonseekable_open(inode, file); 148 } 149 150 static ssize_t pnx4008_wdt_write(struct file *file, const char *data, 151 size_t len, loff_t *ppos) 152 { 153 if (len) { 154 if (!nowayout) { 155 size_t i; 156 157 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 158 159 for (i = 0; i != len; i++) { 160 char c; 161 162 if (get_user(c, data + i)) 163 return -EFAULT; 164 if (c == 'V') 165 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 166 } 167 } 168 wdt_enable(); 169 } 170 171 return len; 172 } 173 174 static const struct watchdog_info ident = { 175 .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | 176 WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 177 .identity = "PNX4008 Watchdog", 178 }; 179 180 static long pnx4008_wdt_ioctl(struct file *file, unsigned int cmd, 181 unsigned long arg) 182 { 183 int ret = -ENOTTY; 184 int time; 185 186 switch (cmd) { 187 case WDIOC_GETSUPPORT: 188 ret = copy_to_user((struct watchdog_info *)arg, &ident, 189 sizeof(ident)) ? -EFAULT : 0; 190 break; 191 192 case WDIOC_GETSTATUS: 193 ret = put_user(0, (int *)arg); 194 break; 195 196 case WDIOC_GETBOOTSTATUS: 197 ret = put_user(boot_status, (int *)arg); 198 break; 199 200 case WDIOC_KEEPALIVE: 201 wdt_enable(); 202 ret = 0; 203 break; 204 205 case WDIOC_SETTIMEOUT: 206 ret = get_user(time, (int *)arg); 207 if (ret) 208 break; 209 210 if (time <= 0 || time > MAX_HEARTBEAT) { 211 ret = -EINVAL; 212 break; 213 } 214 215 heartbeat = time; 216 wdt_enable(); 217 /* Fall through */ 218 219 case WDIOC_GETTIMEOUT: 220 ret = put_user(heartbeat, (int *)arg); 221 break; 222 } 223 return ret; 224 } 225 226 static int pnx4008_wdt_release(struct inode *inode, struct file *file) 227 { 228 if (!test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 229 printk(KERN_WARNING "WATCHDOG: Device closed unexpectedly\n"); 230 231 wdt_disable(); 232 clk_disable(wdt_clk); 233 clear_bit(WDT_IN_USE, &wdt_status); 234 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 235 236 return 0; 237 } 238 239 static const struct file_operations pnx4008_wdt_fops = { 240 .owner = THIS_MODULE, 241 .llseek = no_llseek, 242 .write = pnx4008_wdt_write, 243 .unlocked_ioctl = pnx4008_wdt_ioctl, 244 .open = pnx4008_wdt_open, 245 .release = pnx4008_wdt_release, 246 }; 247 248 static struct miscdevice pnx4008_wdt_miscdev = { 249 .minor = WATCHDOG_MINOR, 250 .name = "watchdog", 251 .fops = &pnx4008_wdt_fops, 252 }; 253 254 static int __devinit pnx4008_wdt_probe(struct platform_device *pdev) 255 { 256 int ret = 0, size; 257 258 if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 259 heartbeat = DEFAULT_HEARTBEAT; 260 261 printk(KERN_INFO MODULE_NAME 262 "PNX4008 Watchdog Timer: heartbeat %d sec\n", heartbeat); 263 264 wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 265 if (wdt_mem == NULL) { 266 printk(KERN_INFO MODULE_NAME 267 "failed to get memory region resource\n"); 268 return -ENOENT; 269 } 270 271 size = resource_size(wdt_mem); 272 273 if (!request_mem_region(wdt_mem->start, size, pdev->name)) { 274 printk(KERN_INFO MODULE_NAME "failed to get memory region\n"); 275 return -ENOENT; 276 } 277 wdt_base = (void __iomem *)IO_ADDRESS(wdt_mem->start); 278 279 wdt_clk = clk_get(&pdev->dev, NULL); 280 if (IS_ERR(wdt_clk)) { 281 ret = PTR_ERR(wdt_clk); 282 release_mem_region(wdt_mem->start, size); 283 wdt_mem = NULL; 284 goto out; 285 } 286 287 ret = clk_enable(wdt_clk); 288 if (ret) { 289 release_mem_region(wdt_mem->start, size); 290 wdt_mem = NULL; 291 clk_put(wdt_clk); 292 goto out; 293 } 294 295 ret = misc_register(&pnx4008_wdt_miscdev); 296 if (ret < 0) { 297 printk(KERN_ERR MODULE_NAME "cannot register misc device\n"); 298 release_mem_region(wdt_mem->start, size); 299 wdt_mem = NULL; 300 clk_disable(wdt_clk); 301 clk_put(wdt_clk); 302 } else { 303 boot_status = (__raw_readl(WDTIM_RES(wdt_base)) & WDOG_RESET) ? 304 WDIOF_CARDRESET : 0; 305 wdt_disable(); /*disable for now */ 306 clk_disable(wdt_clk); 307 set_bit(WDT_DEVICE_INITED, &wdt_status); 308 } 309 310 out: 311 return ret; 312 } 313 314 static int __devexit pnx4008_wdt_remove(struct platform_device *pdev) 315 { 316 misc_deregister(&pnx4008_wdt_miscdev); 317 318 clk_disable(wdt_clk); 319 clk_put(wdt_clk); 320 321 if (wdt_mem) { 322 release_mem_region(wdt_mem->start, resource_size(wdt_mem)); 323 wdt_mem = NULL; 324 } 325 return 0; 326 } 327 328 static struct platform_driver platform_wdt_driver = { 329 .driver = { 330 .name = "pnx4008-watchdog", 331 .owner = THIS_MODULE, 332 }, 333 .probe = pnx4008_wdt_probe, 334 .remove = __devexit_p(pnx4008_wdt_remove), 335 }; 336 337 module_platform_driver(platform_wdt_driver); 338 339 MODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>"); 340 MODULE_DESCRIPTION("PNX4008 Watchdog Driver"); 341 342 module_param(heartbeat, int, 0); 343 MODULE_PARM_DESC(heartbeat, 344 "Watchdog heartbeat period in seconds from 1 to " 345 __MODULE_STRING(MAX_HEARTBEAT) ", default " 346 __MODULE_STRING(DEFAULT_HEARTBEAT)); 347 348 module_param(nowayout, int, 0); 349 MODULE_PARM_DESC(nowayout, 350 "Set to 1 to keep watchdog running after device release"); 351 352 MODULE_LICENSE("GPL"); 353 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 354 MODULE_ALIAS("platform:pnx4008-watchdog"); 355