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