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