1 /* 2 * drivers/watchdog/orion_wdt.c 3 * 4 * Watchdog driver for Orion/Kirkwood processors 5 * 6 * Author: Sylver Bruneau <sylver.bruneau@googlemail.com> 7 * 8 * This file is licensed under the terms of the GNU General Public 9 * License version 2. This program is licensed "as is" without any 10 * warranty of any kind, whether express or implied. 11 */ 12 13 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 14 15 #include <linux/module.h> 16 #include <linux/moduleparam.h> 17 #include <linux/types.h> 18 #include <linux/kernel.h> 19 #include <linux/fs.h> 20 #include <linux/miscdevice.h> 21 #include <linux/platform_device.h> 22 #include <linux/watchdog.h> 23 #include <linux/init.h> 24 #include <linux/uaccess.h> 25 #include <linux/io.h> 26 #include <linux/spinlock.h> 27 #include <mach/bridge-regs.h> 28 #include <plat/orion_wdt.h> 29 30 /* 31 * Watchdog timer block registers. 32 */ 33 #define TIMER_CTRL 0x0000 34 #define WDT_EN 0x0010 35 #define WDT_VAL 0x0024 36 37 #define WDT_MAX_CYCLE_COUNT 0xffffffff 38 #define WDT_IN_USE 0 39 #define WDT_OK_TO_CLOSE 1 40 41 static bool nowayout = WATCHDOG_NOWAYOUT; 42 static int heartbeat = -1; /* module parameter (seconds) */ 43 static unsigned int wdt_max_duration; /* (seconds) */ 44 static unsigned int wdt_tclk; 45 static void __iomem *wdt_reg; 46 static unsigned long wdt_status; 47 static DEFINE_SPINLOCK(wdt_lock); 48 49 static void orion_wdt_ping(void) 50 { 51 spin_lock(&wdt_lock); 52 53 /* Reload watchdog duration */ 54 writel(wdt_tclk * heartbeat, wdt_reg + WDT_VAL); 55 56 spin_unlock(&wdt_lock); 57 } 58 59 static void orion_wdt_enable(void) 60 { 61 u32 reg; 62 63 spin_lock(&wdt_lock); 64 65 /* Set watchdog duration */ 66 writel(wdt_tclk * heartbeat, wdt_reg + WDT_VAL); 67 68 /* Clear watchdog timer interrupt */ 69 reg = readl(BRIDGE_CAUSE); 70 reg &= ~WDT_INT_REQ; 71 writel(reg, BRIDGE_CAUSE); 72 73 /* Enable watchdog timer */ 74 reg = readl(wdt_reg + TIMER_CTRL); 75 reg |= WDT_EN; 76 writel(reg, wdt_reg + TIMER_CTRL); 77 78 /* Enable reset on watchdog */ 79 reg = readl(RSTOUTn_MASK); 80 reg |= WDT_RESET_OUT_EN; 81 writel(reg, RSTOUTn_MASK); 82 83 spin_unlock(&wdt_lock); 84 } 85 86 static void orion_wdt_disable(void) 87 { 88 u32 reg; 89 90 spin_lock(&wdt_lock); 91 92 /* Disable reset on watchdog */ 93 reg = readl(RSTOUTn_MASK); 94 reg &= ~WDT_RESET_OUT_EN; 95 writel(reg, RSTOUTn_MASK); 96 97 /* Disable watchdog timer */ 98 reg = readl(wdt_reg + TIMER_CTRL); 99 reg &= ~WDT_EN; 100 writel(reg, wdt_reg + TIMER_CTRL); 101 102 spin_unlock(&wdt_lock); 103 } 104 105 static int orion_wdt_get_timeleft(int *time_left) 106 { 107 spin_lock(&wdt_lock); 108 *time_left = readl(wdt_reg + WDT_VAL) / wdt_tclk; 109 spin_unlock(&wdt_lock); 110 return 0; 111 } 112 113 static int orion_wdt_open(struct inode *inode, struct file *file) 114 { 115 if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 116 return -EBUSY; 117 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 118 orion_wdt_enable(); 119 return nonseekable_open(inode, file); 120 } 121 122 static ssize_t orion_wdt_write(struct file *file, const char *data, 123 size_t len, loff_t *ppos) 124 { 125 if (len) { 126 if (!nowayout) { 127 size_t i; 128 129 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 130 for (i = 0; i != len; i++) { 131 char c; 132 133 if (get_user(c, data + i)) 134 return -EFAULT; 135 if (c == 'V') 136 set_bit(WDT_OK_TO_CLOSE, &wdt_status); 137 } 138 } 139 orion_wdt_ping(); 140 } 141 return len; 142 } 143 144 static int orion_wdt_settimeout(int new_time) 145 { 146 if ((new_time <= 0) || (new_time > wdt_max_duration)) 147 return -EINVAL; 148 149 /* Set new watchdog time to be used when 150 * orion_wdt_enable() or orion_wdt_ping() is called. */ 151 heartbeat = new_time; 152 return 0; 153 } 154 155 static const struct watchdog_info ident = { 156 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | 157 WDIOF_KEEPALIVEPING, 158 .identity = "Orion Watchdog", 159 }; 160 161 static long orion_wdt_ioctl(struct file *file, unsigned int cmd, 162 unsigned long arg) 163 { 164 int ret = -ENOTTY; 165 int time; 166 167 switch (cmd) { 168 case WDIOC_GETSUPPORT: 169 ret = copy_to_user((struct watchdog_info *)arg, &ident, 170 sizeof(ident)) ? -EFAULT : 0; 171 break; 172 173 case WDIOC_GETSTATUS: 174 case WDIOC_GETBOOTSTATUS: 175 ret = put_user(0, (int *)arg); 176 break; 177 178 case WDIOC_KEEPALIVE: 179 orion_wdt_ping(); 180 ret = 0; 181 break; 182 183 case WDIOC_SETTIMEOUT: 184 ret = get_user(time, (int *)arg); 185 if (ret) 186 break; 187 188 if (orion_wdt_settimeout(time)) { 189 ret = -EINVAL; 190 break; 191 } 192 orion_wdt_ping(); 193 /* Fall through */ 194 195 case WDIOC_GETTIMEOUT: 196 ret = put_user(heartbeat, (int *)arg); 197 break; 198 199 case WDIOC_GETTIMELEFT: 200 if (orion_wdt_get_timeleft(&time)) { 201 ret = -EINVAL; 202 break; 203 } 204 ret = put_user(time, (int *)arg); 205 break; 206 } 207 return ret; 208 } 209 210 static int orion_wdt_release(struct inode *inode, struct file *file) 211 { 212 if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 213 orion_wdt_disable(); 214 else 215 pr_crit("Device closed unexpectedly - timer will not stop\n"); 216 clear_bit(WDT_IN_USE, &wdt_status); 217 clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 218 219 return 0; 220 } 221 222 223 static const struct file_operations orion_wdt_fops = { 224 .owner = THIS_MODULE, 225 .llseek = no_llseek, 226 .write = orion_wdt_write, 227 .unlocked_ioctl = orion_wdt_ioctl, 228 .open = orion_wdt_open, 229 .release = orion_wdt_release, 230 }; 231 232 static struct miscdevice orion_wdt_miscdev = { 233 .minor = WATCHDOG_MINOR, 234 .name = "watchdog", 235 .fops = &orion_wdt_fops, 236 }; 237 238 static int __devinit orion_wdt_probe(struct platform_device *pdev) 239 { 240 struct orion_wdt_platform_data *pdata = pdev->dev.platform_data; 241 struct resource *res; 242 int ret; 243 244 if (pdata) { 245 wdt_tclk = pdata->tclk; 246 } else { 247 pr_err("misses platform data\n"); 248 return -ENODEV; 249 } 250 251 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 252 253 wdt_reg = ioremap(res->start, resource_size(res)); 254 255 if (orion_wdt_miscdev.parent) 256 return -EBUSY; 257 orion_wdt_miscdev.parent = &pdev->dev; 258 259 wdt_max_duration = WDT_MAX_CYCLE_COUNT / wdt_tclk; 260 if (orion_wdt_settimeout(heartbeat)) 261 heartbeat = wdt_max_duration; 262 263 ret = misc_register(&orion_wdt_miscdev); 264 if (ret) 265 return ret; 266 267 pr_info("Initial timeout %d sec%s\n", 268 heartbeat, nowayout ? ", nowayout" : ""); 269 return 0; 270 } 271 272 static int __devexit orion_wdt_remove(struct platform_device *pdev) 273 { 274 int ret; 275 276 if (test_bit(WDT_IN_USE, &wdt_status)) { 277 orion_wdt_disable(); 278 clear_bit(WDT_IN_USE, &wdt_status); 279 } 280 281 ret = misc_deregister(&orion_wdt_miscdev); 282 if (!ret) 283 orion_wdt_miscdev.parent = NULL; 284 285 return ret; 286 } 287 288 static void orion_wdt_shutdown(struct platform_device *pdev) 289 { 290 if (test_bit(WDT_IN_USE, &wdt_status)) 291 orion_wdt_disable(); 292 } 293 294 static struct platform_driver orion_wdt_driver = { 295 .probe = orion_wdt_probe, 296 .remove = __devexit_p(orion_wdt_remove), 297 .shutdown = orion_wdt_shutdown, 298 .driver = { 299 .owner = THIS_MODULE, 300 .name = "orion_wdt", 301 }, 302 }; 303 304 module_platform_driver(orion_wdt_driver); 305 306 MODULE_AUTHOR("Sylver Bruneau <sylver.bruneau@googlemail.com>"); 307 MODULE_DESCRIPTION("Orion Processor Watchdog"); 308 309 module_param(heartbeat, int, 0); 310 MODULE_PARM_DESC(heartbeat, "Initial watchdog heartbeat in seconds"); 311 312 module_param(nowayout, bool, 0); 313 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 314 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 315 316 MODULE_LICENSE("GPL"); 317 MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 318