1/* 2 * Broadcom BCM63xx SoC watchdog driver 3 * 4 * Copyright (C) 2007, Miguel Gaio <miguel.gaio@efixo.com> 5 * Copyright (C) 2008, Florian Fainelli <florian@openwrt.org> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 10 * 2 of the License, or (at your option) any later version. 11 */ 12 13#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 14 15#include <linux/bitops.h> 16#include <linux/errno.h> 17#include <linux/fs.h> 18#include <linux/io.h> 19#include <linux/kernel.h> 20#include <linux/miscdevice.h> 21#include <linux/module.h> 22#include <linux/moduleparam.h> 23#include <linux/types.h> 24#include <linux/uaccess.h> 25#include <linux/watchdog.h> 26#include <linux/timer.h> 27#include <linux/jiffies.h> 28#include <linux/interrupt.h> 29#include <linux/ptrace.h> 30#include <linux/resource.h> 31#include <linux/platform_device.h> 32 33#include <bcm63xx_cpu.h> 34#include <bcm63xx_io.h> 35#include <bcm63xx_regs.h> 36#include <bcm63xx_timer.h> 37 38#define PFX KBUILD_MODNAME 39 40#define WDT_HZ 50000000 /* Fclk */ 41#define WDT_DEFAULT_TIME 30 /* seconds */ 42#define WDT_MAX_TIME 256 /* seconds */ 43 44static struct { 45 void __iomem *regs; 46 struct timer_list timer; 47 unsigned long inuse; 48 atomic_t ticks; 49} bcm63xx_wdt_device; 50 51static int expect_close; 52 53static int wdt_time = WDT_DEFAULT_TIME; 54static bool nowayout = WATCHDOG_NOWAYOUT; 55module_param(nowayout, bool, 0); 56MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 57 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 58 59/* HW functions */ 60static void bcm63xx_wdt_hw_start(void) 61{ 62 bcm_writel(0xfffffffe, bcm63xx_wdt_device.regs + WDT_DEFVAL_REG); 63 bcm_writel(WDT_START_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); 64 bcm_writel(WDT_START_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); 65} 66 67static void bcm63xx_wdt_hw_stop(void) 68{ 69 bcm_writel(WDT_STOP_1, bcm63xx_wdt_device.regs + WDT_CTL_REG); 70 bcm_writel(WDT_STOP_2, bcm63xx_wdt_device.regs + WDT_CTL_REG); 71} 72 73static void bcm63xx_wdt_isr(void *data) 74{ 75 struct pt_regs *regs = get_irq_regs(); 76 77 die(PFX " fire", regs); 78} 79 80static void bcm63xx_timer_tick(unsigned long unused) 81{ 82 if (!atomic_dec_and_test(&bcm63xx_wdt_device.ticks)) { 83 bcm63xx_wdt_hw_start(); 84 mod_timer(&bcm63xx_wdt_device.timer, jiffies + HZ); 85 } else 86 pr_crit("watchdog will restart system\n"); 87} 88 89static void bcm63xx_wdt_pet(void) 90{ 91 atomic_set(&bcm63xx_wdt_device.ticks, wdt_time); 92} 93 94static void bcm63xx_wdt_start(void) 95{ 96 bcm63xx_wdt_pet(); 97 bcm63xx_timer_tick(0); 98} 99 100static void bcm63xx_wdt_pause(void) 101{ 102 del_timer_sync(&bcm63xx_wdt_device.timer); 103 bcm63xx_wdt_hw_stop(); 104} 105 106static int bcm63xx_wdt_settimeout(int new_time) 107{ 108 if ((new_time <= 0) || (new_time > WDT_MAX_TIME)) 109 return -EINVAL; 110 111 wdt_time = new_time; 112 113 return 0; 114} 115 116static int bcm63xx_wdt_open(struct inode *inode, struct file *file) 117{ 118 if (test_and_set_bit(0, &bcm63xx_wdt_device.inuse)) 119 return -EBUSY; 120 121 bcm63xx_wdt_start(); 122 return nonseekable_open(inode, file); 123} 124 125static int bcm63xx_wdt_release(struct inode *inode, struct file *file) 126{ 127 if (expect_close == 42) 128 bcm63xx_wdt_pause(); 129 else { 130 pr_crit("Unexpected close, not stopping watchdog!\n"); 131 bcm63xx_wdt_start(); 132 } 133 clear_bit(0, &bcm63xx_wdt_device.inuse); 134 expect_close = 0; 135 return 0; 136} 137 138static ssize_t bcm63xx_wdt_write(struct file *file, const char *data, 139 size_t len, loff_t *ppos) 140{ 141 if (len) { 142 if (!nowayout) { 143 size_t i; 144 145 /* In case it was set long ago */ 146 expect_close = 0; 147 148 for (i = 0; i != len; i++) { 149 char c; 150 if (get_user(c, data + i)) 151 return -EFAULT; 152 if (c == 'V') 153 expect_close = 42; 154 } 155 } 156 bcm63xx_wdt_pet(); 157 } 158 return len; 159} 160 161static struct watchdog_info bcm63xx_wdt_info = { 162 .identity = PFX, 163 .options = WDIOF_SETTIMEOUT | 164 WDIOF_KEEPALIVEPING | 165 WDIOF_MAGICCLOSE, 166}; 167 168 169static long bcm63xx_wdt_ioctl(struct file *file, unsigned int cmd, 170 unsigned long arg) 171{ 172 void __user *argp = (void __user *)arg; 173 int __user *p = argp; 174 int new_value, retval = -EINVAL; 175 176 switch (cmd) { 177 case WDIOC_GETSUPPORT: 178 return copy_to_user(argp, &bcm63xx_wdt_info, 179 sizeof(bcm63xx_wdt_info)) ? -EFAULT : 0; 180 181 case WDIOC_GETSTATUS: 182 case WDIOC_GETBOOTSTATUS: 183 return put_user(0, p); 184 185 case WDIOC_SETOPTIONS: 186 if (get_user(new_value, p)) 187 return -EFAULT; 188 189 if (new_value & WDIOS_DISABLECARD) { 190 bcm63xx_wdt_pause(); 191 retval = 0; 192 } 193 if (new_value & WDIOS_ENABLECARD) { 194 bcm63xx_wdt_start(); 195 retval = 0; 196 } 197 198 return retval; 199 200 case WDIOC_KEEPALIVE: 201 bcm63xx_wdt_pet(); 202 return 0; 203 204 case WDIOC_SETTIMEOUT: 205 if (get_user(new_value, p)) 206 return -EFAULT; 207 208 if (bcm63xx_wdt_settimeout(new_value)) 209 return -EINVAL; 210 211 bcm63xx_wdt_pet(); 212 213 case WDIOC_GETTIMEOUT: 214 return put_user(wdt_time, p); 215 216 default: 217 return -ENOTTY; 218 219 } 220} 221 222static const struct file_operations bcm63xx_wdt_fops = { 223 .owner = THIS_MODULE, 224 .llseek = no_llseek, 225 .write = bcm63xx_wdt_write, 226 .unlocked_ioctl = bcm63xx_wdt_ioctl, 227 .open = bcm63xx_wdt_open, 228 .release = bcm63xx_wdt_release, 229}; 230 231static struct miscdevice bcm63xx_wdt_miscdev = { 232 .minor = WATCHDOG_MINOR, 233 .name = "watchdog", 234 .fops = &bcm63xx_wdt_fops, 235}; 236 237 238static int bcm63xx_wdt_probe(struct platform_device *pdev) 239{ 240 int ret; 241 struct resource *r; 242 243 setup_timer(&bcm63xx_wdt_device.timer, bcm63xx_timer_tick, 0L); 244 245 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 246 if (!r) { 247 dev_err(&pdev->dev, "failed to get resources\n"); 248 return -ENODEV; 249 } 250 251 bcm63xx_wdt_device.regs = devm_ioremap_nocache(&pdev->dev, r->start, 252 resource_size(r)); 253 if (!bcm63xx_wdt_device.regs) { 254 dev_err(&pdev->dev, "failed to remap I/O resources\n"); 255 return -ENXIO; 256 } 257 258 ret = bcm63xx_timer_register(TIMER_WDT_ID, bcm63xx_wdt_isr, NULL); 259 if (ret < 0) { 260 dev_err(&pdev->dev, "failed to register wdt timer isr\n"); 261 return ret; 262 } 263 264 if (bcm63xx_wdt_settimeout(wdt_time)) { 265 bcm63xx_wdt_settimeout(WDT_DEFAULT_TIME); 266 dev_info(&pdev->dev, 267 ": wdt_time value must be 1 <= wdt_time <= 256, using %d\n", 268 wdt_time); 269 } 270 271 ret = misc_register(&bcm63xx_wdt_miscdev); 272 if (ret < 0) { 273 dev_err(&pdev->dev, "failed to register watchdog device\n"); 274 goto unregister_timer; 275 } 276 277 dev_info(&pdev->dev, " started, timer margin: %d sec\n", 278 WDT_DEFAULT_TIME); 279 280 return 0; 281 282unregister_timer: 283 bcm63xx_timer_unregister(TIMER_WDT_ID); 284 return ret; 285} 286 287static int bcm63xx_wdt_remove(struct platform_device *pdev) 288{ 289 if (!nowayout) 290 bcm63xx_wdt_pause(); 291 292 misc_deregister(&bcm63xx_wdt_miscdev); 293 bcm63xx_timer_unregister(TIMER_WDT_ID); 294 return 0; 295} 296 297static void bcm63xx_wdt_shutdown(struct platform_device *pdev) 298{ 299 bcm63xx_wdt_pause(); 300} 301 302static struct platform_driver bcm63xx_wdt_driver = { 303 .probe = bcm63xx_wdt_probe, 304 .remove = bcm63xx_wdt_remove, 305 .shutdown = bcm63xx_wdt_shutdown, 306 .driver = { 307 .name = "bcm63xx-wdt", 308 } 309}; 310 311module_platform_driver(bcm63xx_wdt_driver); 312 313MODULE_AUTHOR("Miguel Gaio <miguel.gaio@efixo.com>"); 314MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); 315MODULE_DESCRIPTION("Driver for the Broadcom BCM63xx SoC watchdog"); 316MODULE_LICENSE("GPL"); 317MODULE_ALIAS("platform:bcm63xx-wdt"); 318