1 /* 2 * Copyright 2010-2011 Picochip Ltd., Jamie Iles 3 * http://www.picochip.com 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * as published by the Free Software Foundation; either version 8 * 2 of the License, or (at your option) any later version. 9 * 10 * This file implements a driver for the Synopsys DesignWare watchdog device 11 * in the many ARM subsystems. The watchdog has 16 different timeout periods 12 * and these are a function of the input clock frequency. 13 * 14 * The DesignWare watchdog cannot be stopped once it has been started so we 15 * use a software timer to implement a ping that will keep the watchdog alive. 16 * If we receive an expected close for the watchdog then we keep the timer 17 * running, otherwise the timer is stopped and the watchdog will expire. 18 */ 19 20 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 21 22 #include <linux/bitops.h> 23 #include <linux/clk.h> 24 #include <linux/device.h> 25 #include <linux/err.h> 26 #include <linux/fs.h> 27 #include <linux/io.h> 28 #include <linux/kernel.h> 29 #include <linux/miscdevice.h> 30 #include <linux/module.h> 31 #include <linux/moduleparam.h> 32 #include <linux/pm.h> 33 #include <linux/platform_device.h> 34 #include <linux/spinlock.h> 35 #include <linux/timer.h> 36 #include <linux/uaccess.h> 37 #include <linux/watchdog.h> 38 39 #define WDOG_CONTROL_REG_OFFSET 0x00 40 #define WDOG_CONTROL_REG_WDT_EN_MASK 0x01 41 #define WDOG_TIMEOUT_RANGE_REG_OFFSET 0x04 42 #define WDOG_CURRENT_COUNT_REG_OFFSET 0x08 43 #define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c 44 #define WDOG_COUNTER_RESTART_KICK_VALUE 0x76 45 46 /* The maximum TOP (timeout period) value that can be set in the watchdog. */ 47 #define DW_WDT_MAX_TOP 15 48 49 static bool nowayout = WATCHDOG_NOWAYOUT; 50 module_param(nowayout, bool, 0); 51 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 52 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 53 54 #define WDT_TIMEOUT (HZ / 2) 55 56 static struct { 57 spinlock_t lock; 58 void __iomem *regs; 59 struct clk *clk; 60 unsigned long in_use; 61 unsigned long next_heartbeat; 62 struct timer_list timer; 63 int expect_close; 64 } dw_wdt; 65 66 static inline int dw_wdt_is_enabled(void) 67 { 68 return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) & 69 WDOG_CONTROL_REG_WDT_EN_MASK; 70 } 71 72 static inline int dw_wdt_top_in_seconds(unsigned top) 73 { 74 /* 75 * There are 16 possible timeout values in 0..15 where the number of 76 * cycles is 2 ^ (16 + i) and the watchdog counts down. 77 */ 78 return (1 << (16 + top)) / clk_get_rate(dw_wdt.clk); 79 } 80 81 static int dw_wdt_get_top(void) 82 { 83 int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; 84 85 return dw_wdt_top_in_seconds(top); 86 } 87 88 static inline void dw_wdt_set_next_heartbeat(void) 89 { 90 dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ; 91 } 92 93 static int dw_wdt_set_top(unsigned top_s) 94 { 95 int i, top_val = DW_WDT_MAX_TOP; 96 97 /* 98 * Iterate over the timeout values until we find the closest match. We 99 * always look for >=. 100 */ 101 for (i = 0; i <= DW_WDT_MAX_TOP; ++i) 102 if (dw_wdt_top_in_seconds(i) >= top_s) { 103 top_val = i; 104 break; 105 } 106 107 /* Set the new value in the watchdog. */ 108 writel(top_val, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); 109 110 dw_wdt_set_next_heartbeat(); 111 112 return dw_wdt_top_in_seconds(top_val); 113 } 114 115 static void dw_wdt_keepalive(void) 116 { 117 writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + 118 WDOG_COUNTER_RESTART_REG_OFFSET); 119 } 120 121 static void dw_wdt_ping(unsigned long data) 122 { 123 if (time_before(jiffies, dw_wdt.next_heartbeat) || 124 (!nowayout && !dw_wdt.in_use)) { 125 dw_wdt_keepalive(); 126 mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); 127 } else 128 pr_crit("keepalive missed, machine will reset\n"); 129 } 130 131 static int dw_wdt_open(struct inode *inode, struct file *filp) 132 { 133 if (test_and_set_bit(0, &dw_wdt.in_use)) 134 return -EBUSY; 135 136 /* Make sure we don't get unloaded. */ 137 __module_get(THIS_MODULE); 138 139 spin_lock(&dw_wdt.lock); 140 if (!dw_wdt_is_enabled()) { 141 /* 142 * The watchdog is not currently enabled. Set the timeout to 143 * the maximum and then start it. 144 */ 145 dw_wdt_set_top(DW_WDT_MAX_TOP); 146 writel(WDOG_CONTROL_REG_WDT_EN_MASK, 147 dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); 148 } 149 150 dw_wdt_set_next_heartbeat(); 151 152 spin_unlock(&dw_wdt.lock); 153 154 return nonseekable_open(inode, filp); 155 } 156 157 ssize_t dw_wdt_write(struct file *filp, const char __user *buf, size_t len, 158 loff_t *offset) 159 { 160 if (!len) 161 return 0; 162 163 if (!nowayout) { 164 size_t i; 165 166 dw_wdt.expect_close = 0; 167 168 for (i = 0; i < len; ++i) { 169 char c; 170 171 if (get_user(c, buf + i)) 172 return -EFAULT; 173 174 if (c == 'V') { 175 dw_wdt.expect_close = 1; 176 break; 177 } 178 } 179 } 180 181 dw_wdt_set_next_heartbeat(); 182 mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); 183 184 return len; 185 } 186 187 static u32 dw_wdt_time_left(void) 188 { 189 return readl(dw_wdt.regs + WDOG_CURRENT_COUNT_REG_OFFSET) / 190 clk_get_rate(dw_wdt.clk); 191 } 192 193 static const struct watchdog_info dw_wdt_ident = { 194 .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | 195 WDIOF_MAGICCLOSE, 196 .identity = "Synopsys DesignWare Watchdog", 197 }; 198 199 static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) 200 { 201 unsigned long val; 202 int timeout; 203 204 switch (cmd) { 205 case WDIOC_GETSUPPORT: 206 return copy_to_user((struct watchdog_info *)arg, &dw_wdt_ident, 207 sizeof(dw_wdt_ident)) ? -EFAULT : 0; 208 209 case WDIOC_GETSTATUS: 210 case WDIOC_GETBOOTSTATUS: 211 return put_user(0, (int *)arg); 212 213 case WDIOC_KEEPALIVE: 214 dw_wdt_set_next_heartbeat(); 215 return 0; 216 217 case WDIOC_SETTIMEOUT: 218 if (get_user(val, (int __user *)arg)) 219 return -EFAULT; 220 timeout = dw_wdt_set_top(val); 221 return put_user(timeout , (int __user *)arg); 222 223 case WDIOC_GETTIMEOUT: 224 return put_user(dw_wdt_get_top(), (int __user *)arg); 225 226 case WDIOC_GETTIMELEFT: 227 /* Get the time left until expiry. */ 228 if (get_user(val, (int __user *)arg)) 229 return -EFAULT; 230 return put_user(dw_wdt_time_left(), (int __user *)arg); 231 232 default: 233 return -ENOTTY; 234 } 235 } 236 237 static int dw_wdt_release(struct inode *inode, struct file *filp) 238 { 239 clear_bit(0, &dw_wdt.in_use); 240 241 if (!dw_wdt.expect_close) { 242 del_timer(&dw_wdt.timer); 243 244 if (!nowayout) 245 pr_crit("unexpected close, system will reboot soon\n"); 246 else 247 pr_crit("watchdog cannot be disabled, system will reboot soon\n"); 248 } 249 250 dw_wdt.expect_close = 0; 251 252 return 0; 253 } 254 255 #ifdef CONFIG_PM 256 static int dw_wdt_suspend(struct device *dev) 257 { 258 clk_disable(dw_wdt.clk); 259 260 return 0; 261 } 262 263 static int dw_wdt_resume(struct device *dev) 264 { 265 int err = clk_enable(dw_wdt.clk); 266 267 if (err) 268 return err; 269 270 dw_wdt_keepalive(); 271 272 return 0; 273 } 274 275 static const struct dev_pm_ops dw_wdt_pm_ops = { 276 .suspend = dw_wdt_suspend, 277 .resume = dw_wdt_resume, 278 }; 279 #endif /* CONFIG_PM */ 280 281 static const struct file_operations wdt_fops = { 282 .owner = THIS_MODULE, 283 .llseek = no_llseek, 284 .open = dw_wdt_open, 285 .write = dw_wdt_write, 286 .unlocked_ioctl = dw_wdt_ioctl, 287 .release = dw_wdt_release 288 }; 289 290 static struct miscdevice dw_wdt_miscdev = { 291 .fops = &wdt_fops, 292 .name = "watchdog", 293 .minor = WATCHDOG_MINOR, 294 }; 295 296 static int dw_wdt_drv_probe(struct platform_device *pdev) 297 { 298 int ret; 299 struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); 300 301 if (!mem) 302 return -EINVAL; 303 304 dw_wdt.regs = devm_request_and_ioremap(&pdev->dev, mem); 305 if (!dw_wdt.regs) 306 return -ENOMEM; 307 308 dw_wdt.clk = clk_get(&pdev->dev, NULL); 309 if (IS_ERR(dw_wdt.clk)) 310 return PTR_ERR(dw_wdt.clk); 311 312 ret = clk_enable(dw_wdt.clk); 313 if (ret) 314 goto out_put_clk; 315 316 spin_lock_init(&dw_wdt.lock); 317 318 ret = misc_register(&dw_wdt_miscdev); 319 if (ret) 320 goto out_disable_clk; 321 322 dw_wdt_set_next_heartbeat(); 323 setup_timer(&dw_wdt.timer, dw_wdt_ping, 0); 324 mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); 325 326 return 0; 327 328 out_disable_clk: 329 clk_disable(dw_wdt.clk); 330 out_put_clk: 331 clk_put(dw_wdt.clk); 332 333 return ret; 334 } 335 336 static int dw_wdt_drv_remove(struct platform_device *pdev) 337 { 338 misc_deregister(&dw_wdt_miscdev); 339 340 clk_disable(dw_wdt.clk); 341 clk_put(dw_wdt.clk); 342 343 return 0; 344 } 345 346 static struct platform_driver dw_wdt_driver = { 347 .probe = dw_wdt_drv_probe, 348 .remove = dw_wdt_drv_remove, 349 .driver = { 350 .name = "dw_wdt", 351 .owner = THIS_MODULE, 352 #ifdef CONFIG_PM 353 .pm = &dw_wdt_pm_ops, 354 #endif /* CONFIG_PM */ 355 }, 356 }; 357 358 module_platform_driver(dw_wdt_driver); 359 360 MODULE_AUTHOR("Jamie Iles"); 361 MODULE_DESCRIPTION("Synopsys DesignWare Watchdog Driver"); 362 MODULE_LICENSE("GPL"); 363 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 364