1 /* 2 * txx9wdt: A Hardware Watchdog Driver for TXx9 SoCs 3 * 4 * Copyright (C) 2007 Atsushi Nemoto <anemo@mba.ocn.ne.jp> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 2 as 8 * published by the Free Software Foundation. 9 */ 10 #include <linux/module.h> 11 #include <linux/moduleparam.h> 12 #include <linux/types.h> 13 #include <linux/miscdevice.h> 14 #include <linux/watchdog.h> 15 #include <linux/fs.h> 16 #include <linux/reboot.h> 17 #include <linux/init.h> 18 #include <linux/uaccess.h> 19 #include <linux/platform_device.h> 20 #include <linux/clk.h> 21 #include <linux/err.h> 22 #include <linux/io.h> 23 #include <asm/txx9tmr.h> 24 25 #define TIMER_MARGIN 60 /* Default is 60 seconds */ 26 27 static int timeout = TIMER_MARGIN; /* in seconds */ 28 module_param(timeout, int, 0); 29 MODULE_PARM_DESC(timeout, 30 "Watchdog timeout in seconds. " 31 "(0<timeout<((2^" __MODULE_STRING(TXX9_TIMER_BITS) ")/(IMCLK/256)), " 32 "default=" __MODULE_STRING(TIMER_MARGIN) ")"); 33 34 static int nowayout = WATCHDOG_NOWAYOUT; 35 module_param(nowayout, int, 0); 36 MODULE_PARM_DESC(nowayout, 37 "Watchdog cannot be stopped once started " 38 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 39 40 #define WD_TIMER_CCD 7 /* 1/256 */ 41 #define WD_TIMER_CLK (clk_get_rate(txx9_imclk) / (2 << WD_TIMER_CCD)) 42 #define WD_MAX_TIMEOUT ((0xffffffff >> (32 - TXX9_TIMER_BITS)) / WD_TIMER_CLK) 43 44 static unsigned long txx9wdt_alive; 45 static int expect_close; 46 static struct txx9_tmr_reg __iomem *txx9wdt_reg; 47 static struct clk *txx9_imclk; 48 49 static void txx9wdt_ping(void) 50 { 51 __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr); 52 } 53 54 static void txx9wdt_start(void) 55 { 56 __raw_writel(WD_TIMER_CLK * timeout, &txx9wdt_reg->cpra); 57 __raw_writel(WD_TIMER_CCD, &txx9wdt_reg->ccdr); 58 __raw_writel(0, &txx9wdt_reg->tisr); /* clear pending interrupt */ 59 __raw_writel(TXx9_TMTCR_TCE | TXx9_TMTCR_CCDE | TXx9_TMTCR_TMODE_WDOG, 60 &txx9wdt_reg->tcr); 61 __raw_writel(TXx9_TMWTMR_TWIE | TXx9_TMWTMR_TWC, &txx9wdt_reg->wtmr); 62 } 63 64 static void txx9wdt_stop(void) 65 { 66 __raw_writel(TXx9_TMWTMR_WDIS, &txx9wdt_reg->wtmr); 67 __raw_writel(__raw_readl(&txx9wdt_reg->tcr) & ~TXx9_TMTCR_TCE, 68 &txx9wdt_reg->tcr); 69 } 70 71 static int txx9wdt_open(struct inode *inode, struct file *file) 72 { 73 if (test_and_set_bit(0, &txx9wdt_alive)) 74 return -EBUSY; 75 76 if (__raw_readl(&txx9wdt_reg->tcr) & TXx9_TMTCR_TCE) { 77 clear_bit(0, &txx9wdt_alive); 78 return -EBUSY; 79 } 80 81 if (nowayout) 82 __module_get(THIS_MODULE); 83 84 txx9wdt_start(); 85 return nonseekable_open(inode, file); 86 } 87 88 static int txx9wdt_release(struct inode *inode, struct file *file) 89 { 90 if (expect_close) 91 txx9wdt_stop(); 92 else { 93 printk(KERN_CRIT "txx9wdt: " 94 "Unexpected close, not stopping watchdog!\n"); 95 txx9wdt_ping(); 96 } 97 clear_bit(0, &txx9wdt_alive); 98 expect_close = 0; 99 return 0; 100 } 101 102 static ssize_t txx9wdt_write(struct file *file, const char __user *data, 103 size_t len, loff_t *ppos) 104 { 105 if (len) { 106 if (!nowayout) { 107 size_t i; 108 109 expect_close = 0; 110 for (i = 0; i != len; i++) { 111 char c; 112 if (get_user(c, data + i)) 113 return -EFAULT; 114 if (c == 'V') 115 expect_close = 1; 116 } 117 } 118 txx9wdt_ping(); 119 } 120 return len; 121 } 122 123 static int txx9wdt_ioctl(struct inode *inode, struct file *file, 124 unsigned int cmd, unsigned long arg) 125 { 126 void __user *argp = (void __user *)arg; 127 int __user *p = argp; 128 int new_timeout; 129 static struct watchdog_info ident = { 130 .options = WDIOF_SETTIMEOUT | 131 WDIOF_KEEPALIVEPING | 132 WDIOF_MAGICCLOSE, 133 .firmware_version = 0, 134 .identity = "Hardware Watchdog for TXx9", 135 }; 136 137 switch (cmd) { 138 default: 139 return -ENOTTY; 140 case WDIOC_GETSUPPORT: 141 return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; 142 case WDIOC_GETSTATUS: 143 case WDIOC_GETBOOTSTATUS: 144 return put_user(0, p); 145 case WDIOC_KEEPALIVE: 146 txx9wdt_ping(); 147 return 0; 148 case WDIOC_SETTIMEOUT: 149 if (get_user(new_timeout, p)) 150 return -EFAULT; 151 if (new_timeout < 1 || new_timeout > WD_MAX_TIMEOUT) 152 return -EINVAL; 153 timeout = new_timeout; 154 txx9wdt_stop(); 155 txx9wdt_start(); 156 /* Fall */ 157 case WDIOC_GETTIMEOUT: 158 return put_user(timeout, p); 159 } 160 } 161 162 static int txx9wdt_notify_sys(struct notifier_block *this, unsigned long code, 163 void *unused) 164 { 165 if (code == SYS_DOWN || code == SYS_HALT) 166 txx9wdt_stop(); 167 return NOTIFY_DONE; 168 } 169 170 static const struct file_operations txx9wdt_fops = { 171 .owner = THIS_MODULE, 172 .llseek = no_llseek, 173 .write = txx9wdt_write, 174 .ioctl = txx9wdt_ioctl, 175 .open = txx9wdt_open, 176 .release = txx9wdt_release, 177 }; 178 179 static struct miscdevice txx9wdt_miscdev = { 180 .minor = WATCHDOG_MINOR, 181 .name = "watchdog", 182 .fops = &txx9wdt_fops, 183 }; 184 185 static struct notifier_block txx9wdt_notifier = { 186 .notifier_call = txx9wdt_notify_sys 187 }; 188 189 static int __init txx9wdt_probe(struct platform_device *dev) 190 { 191 struct resource *res; 192 int ret; 193 194 txx9_imclk = clk_get(NULL, "imbus_clk"); 195 if (IS_ERR(txx9_imclk)) { 196 ret = PTR_ERR(txx9_imclk); 197 txx9_imclk = NULL; 198 goto exit; 199 } 200 ret = clk_enable(txx9_imclk); 201 if (ret) { 202 clk_put(txx9_imclk); 203 txx9_imclk = NULL; 204 goto exit; 205 } 206 207 res = platform_get_resource(dev, IORESOURCE_MEM, 0); 208 if (!res) 209 goto exit_busy; 210 if (!devm_request_mem_region(&dev->dev, 211 res->start, res->end - res->start + 1, 212 "txx9wdt")) 213 goto exit_busy; 214 txx9wdt_reg = devm_ioremap(&dev->dev, 215 res->start, res->end - res->start + 1); 216 if (!txx9wdt_reg) 217 goto exit_busy; 218 219 ret = register_reboot_notifier(&txx9wdt_notifier); 220 if (ret) 221 goto exit; 222 223 ret = misc_register(&txx9wdt_miscdev); 224 if (ret) { 225 unregister_reboot_notifier(&txx9wdt_notifier); 226 goto exit; 227 } 228 229 printk(KERN_INFO "Hardware Watchdog Timer for TXx9: " 230 "timeout=%d sec (max %ld) (nowayout= %d)\n", 231 timeout, WD_MAX_TIMEOUT, nowayout); 232 233 return 0; 234 exit_busy: 235 ret = -EBUSY; 236 exit: 237 if (txx9_imclk) { 238 clk_disable(txx9_imclk); 239 clk_put(txx9_imclk); 240 } 241 return ret; 242 } 243 244 static int __exit txx9wdt_remove(struct platform_device *dev) 245 { 246 misc_deregister(&txx9wdt_miscdev); 247 unregister_reboot_notifier(&txx9wdt_notifier); 248 clk_disable(txx9_imclk); 249 clk_put(txx9_imclk); 250 return 0; 251 } 252 253 static struct platform_driver txx9wdt_driver = { 254 .remove = __exit_p(txx9wdt_remove), 255 .driver = { 256 .name = "txx9wdt", 257 .owner = THIS_MODULE, 258 }, 259 }; 260 261 static int __init watchdog_init(void) 262 { 263 return platform_driver_probe(&txx9wdt_driver, txx9wdt_probe); 264 } 265 266 static void __exit watchdog_exit(void) 267 { 268 platform_driver_unregister(&txx9wdt_driver); 269 } 270 271 module_init(watchdog_init); 272 module_exit(watchdog_exit); 273 274 MODULE_DESCRIPTION("TXx9 Watchdog Driver"); 275 MODULE_LICENSE("GPL"); 276 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 277 MODULE_ALIAS("platform:txx9wdt"); 278