1/* 2 * mv64x60_wdt.c - MV64X60 (Marvell Discovery) watchdog userspace interface 3 * 4 * Author: James Chapman <jchapman@katalix.com> 5 * 6 * Platform-specific setup code should configure the dog to generate 7 * interrupt or reset as required. This code only enables/disables 8 * and services the watchdog. 9 * 10 * Derived from mpc8xx_wdt.c, with the following copyright. 11 * 12 * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under 13 * the terms of the GNU General Public License version 2. This program 14 * is licensed "as is" without any warranty of any kind, whether express 15 * or implied. 16 */ 17 18#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 19 20#include <linux/fs.h> 21#include <linux/init.h> 22#include <linux/kernel.h> 23#include <linux/miscdevice.h> 24#include <linux/module.h> 25#include <linux/watchdog.h> 26#include <linux/platform_device.h> 27#include <linux/mv643xx.h> 28#include <linux/uaccess.h> 29#include <linux/io.h> 30 31#define MV64x60_WDT_WDC_OFFSET 0 32 33/* 34 * The watchdog configuration register contains a pair of 2-bit fields, 35 * 1. a reload field, bits 27-26, which triggers a reload of 36 * the countdown register, and 37 * 2. an enable field, bits 25-24, which toggles between 38 * enabling and disabling the watchdog timer. 39 * Bit 31 is a read-only field which indicates whether the 40 * watchdog timer is currently enabled. 41 * 42 * The low 24 bits contain the timer reload value. 43 */ 44#define MV64x60_WDC_ENABLE_SHIFT 24 45#define MV64x60_WDC_SERVICE_SHIFT 26 46#define MV64x60_WDC_ENABLED_SHIFT 31 47 48#define MV64x60_WDC_ENABLED_TRUE 1 49#define MV64x60_WDC_ENABLED_FALSE 0 50 51/* Flags bits */ 52#define MV64x60_WDOG_FLAG_OPENED 0 53 54static unsigned long wdt_flags; 55static int wdt_status; 56static void __iomem *mv64x60_wdt_regs; 57static int mv64x60_wdt_timeout; 58static int mv64x60_wdt_count; 59static unsigned int bus_clk; 60static char expect_close; 61static DEFINE_SPINLOCK(mv64x60_wdt_spinlock); 62 63static bool nowayout = WATCHDOG_NOWAYOUT; 64module_param(nowayout, bool, 0); 65MODULE_PARM_DESC(nowayout, 66 "Watchdog cannot be stopped once started (default=" 67 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 68 69static int mv64x60_wdt_toggle_wdc(int enabled_predicate, int field_shift) 70{ 71 u32 data; 72 u32 enabled; 73 int ret = 0; 74 75 spin_lock(&mv64x60_wdt_spinlock); 76 data = readl(mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); 77 enabled = (data >> MV64x60_WDC_ENABLED_SHIFT) & 1; 78 79 /* only toggle the requested field if enabled state matches predicate */ 80 if ((enabled ^ enabled_predicate) == 0) { 81 /* We write a 1, then a 2 -- to the appropriate field */ 82 data = (1 << field_shift) | mv64x60_wdt_count; 83 writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); 84 85 data = (2 << field_shift) | mv64x60_wdt_count; 86 writel(data, mv64x60_wdt_regs + MV64x60_WDT_WDC_OFFSET); 87 ret = 1; 88 } 89 spin_unlock(&mv64x60_wdt_spinlock); 90 91 return ret; 92} 93 94static void mv64x60_wdt_service(void) 95{ 96 mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, 97 MV64x60_WDC_SERVICE_SHIFT); 98} 99 100static void mv64x60_wdt_handler_enable(void) 101{ 102 if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_FALSE, 103 MV64x60_WDC_ENABLE_SHIFT)) { 104 mv64x60_wdt_service(); 105 pr_notice("watchdog activated\n"); 106 } 107} 108 109static void mv64x60_wdt_handler_disable(void) 110{ 111 if (mv64x60_wdt_toggle_wdc(MV64x60_WDC_ENABLED_TRUE, 112 MV64x60_WDC_ENABLE_SHIFT)) 113 pr_notice("watchdog deactivated\n"); 114} 115 116static void mv64x60_wdt_set_timeout(unsigned int timeout) 117{ 118 /* maximum bus cycle count is 0xFFFFFFFF */ 119 if (timeout > 0xFFFFFFFF / bus_clk) 120 timeout = 0xFFFFFFFF / bus_clk; 121 122 mv64x60_wdt_count = timeout * bus_clk >> 8; 123 mv64x60_wdt_timeout = timeout; 124} 125 126static int mv64x60_wdt_open(struct inode *inode, struct file *file) 127{ 128 if (test_and_set_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags)) 129 return -EBUSY; 130 131 if (nowayout) 132 __module_get(THIS_MODULE); 133 134 mv64x60_wdt_handler_enable(); 135 136 return nonseekable_open(inode, file); 137} 138 139static int mv64x60_wdt_release(struct inode *inode, struct file *file) 140{ 141 if (expect_close == 42) 142 mv64x60_wdt_handler_disable(); 143 else { 144 pr_crit("unexpected close, not stopping timer!\n"); 145 mv64x60_wdt_service(); 146 } 147 expect_close = 0; 148 149 clear_bit(MV64x60_WDOG_FLAG_OPENED, &wdt_flags); 150 151 return 0; 152} 153 154static ssize_t mv64x60_wdt_write(struct file *file, const char __user *data, 155 size_t len, loff_t *ppos) 156{ 157 if (len) { 158 if (!nowayout) { 159 size_t i; 160 161 expect_close = 0; 162 163 for (i = 0; i != len; i++) { 164 char c; 165 if (get_user(c, data + i)) 166 return -EFAULT; 167 if (c == 'V') 168 expect_close = 42; 169 } 170 } 171 mv64x60_wdt_service(); 172 } 173 174 return len; 175} 176 177static long mv64x60_wdt_ioctl(struct file *file, 178 unsigned int cmd, unsigned long arg) 179{ 180 int timeout; 181 int options; 182 void __user *argp = (void __user *)arg; 183 static const struct watchdog_info info = { 184 .options = WDIOF_SETTIMEOUT | 185 WDIOF_MAGICCLOSE | 186 WDIOF_KEEPALIVEPING, 187 .firmware_version = 0, 188 .identity = "MV64x60 watchdog", 189 }; 190 191 switch (cmd) { 192 case WDIOC_GETSUPPORT: 193 if (copy_to_user(argp, &info, sizeof(info))) 194 return -EFAULT; 195 break; 196 197 case WDIOC_GETSTATUS: 198 case WDIOC_GETBOOTSTATUS: 199 if (put_user(wdt_status, (int __user *)argp)) 200 return -EFAULT; 201 wdt_status &= ~WDIOF_KEEPALIVEPING; 202 break; 203 204 case WDIOC_GETTEMP: 205 return -EOPNOTSUPP; 206 207 case WDIOC_SETOPTIONS: 208 if (get_user(options, (int __user *)argp)) 209 return -EFAULT; 210 211 if (options & WDIOS_DISABLECARD) 212 mv64x60_wdt_handler_disable(); 213 214 if (options & WDIOS_ENABLECARD) 215 mv64x60_wdt_handler_enable(); 216 break; 217 218 case WDIOC_KEEPALIVE: 219 mv64x60_wdt_service(); 220 wdt_status |= WDIOF_KEEPALIVEPING; 221 break; 222 223 case WDIOC_SETTIMEOUT: 224 if (get_user(timeout, (int __user *)argp)) 225 return -EFAULT; 226 mv64x60_wdt_set_timeout(timeout); 227 /* Fall through */ 228 229 case WDIOC_GETTIMEOUT: 230 if (put_user(mv64x60_wdt_timeout, (int __user *)argp)) 231 return -EFAULT; 232 break; 233 234 default: 235 return -ENOTTY; 236 } 237 238 return 0; 239} 240 241static const struct file_operations mv64x60_wdt_fops = { 242 .owner = THIS_MODULE, 243 .llseek = no_llseek, 244 .write = mv64x60_wdt_write, 245 .unlocked_ioctl = mv64x60_wdt_ioctl, 246 .open = mv64x60_wdt_open, 247 .release = mv64x60_wdt_release, 248}; 249 250static struct miscdevice mv64x60_wdt_miscdev = { 251 .minor = WATCHDOG_MINOR, 252 .name = "watchdog", 253 .fops = &mv64x60_wdt_fops, 254}; 255 256static int mv64x60_wdt_probe(struct platform_device *dev) 257{ 258 struct mv64x60_wdt_pdata *pdata = dev_get_platdata(&dev->dev); 259 struct resource *r; 260 int timeout = 10; 261 262 bus_clk = 133; /* in MHz */ 263 if (pdata) { 264 timeout = pdata->timeout; 265 bus_clk = pdata->bus_clk; 266 } 267 268 /* Since bus_clk is truncated MHz, actual frequency could be 269 * up to 1MHz higher. Round up, since it's better to time out 270 * too late than too soon. 271 */ 272 bus_clk++; 273 bus_clk *= 1000000; /* convert to Hz */ 274 275 r = platform_get_resource(dev, IORESOURCE_MEM, 0); 276 if (!r) 277 return -ENODEV; 278 279 mv64x60_wdt_regs = devm_ioremap(&dev->dev, r->start, resource_size(r)); 280 if (mv64x60_wdt_regs == NULL) 281 return -ENOMEM; 282 283 mv64x60_wdt_set_timeout(timeout); 284 285 mv64x60_wdt_handler_disable(); /* in case timer was already running */ 286 287 return misc_register(&mv64x60_wdt_miscdev); 288} 289 290static int mv64x60_wdt_remove(struct platform_device *dev) 291{ 292 misc_deregister(&mv64x60_wdt_miscdev); 293 294 mv64x60_wdt_handler_disable(); 295 296 return 0; 297} 298 299static struct platform_driver mv64x60_wdt_driver = { 300 .probe = mv64x60_wdt_probe, 301 .remove = mv64x60_wdt_remove, 302 .driver = { 303 .name = MV64x60_WDT_NAME, 304 }, 305}; 306 307static int __init mv64x60_wdt_init(void) 308{ 309 pr_info("MV64x60 watchdog driver\n"); 310 311 return platform_driver_register(&mv64x60_wdt_driver); 312} 313 314static void __exit mv64x60_wdt_exit(void) 315{ 316 platform_driver_unregister(&mv64x60_wdt_driver); 317} 318 319module_init(mv64x60_wdt_init); 320module_exit(mv64x60_wdt_exit); 321 322MODULE_AUTHOR("James Chapman <jchapman@katalix.com>"); 323MODULE_DESCRIPTION("MV64x60 watchdog driver"); 324MODULE_LICENSE("GPL"); 325MODULE_ALIAS("platform:" MV64x60_WDT_NAME); 326