[WATCHDOG] support for max63xx watchdog timer chips

This driver adds support for the max63{69,70,71,72,73,74} family of
watchdog timer chips.

It has been tested on an Arcom Zeus (max6369).

Signed-off-by: Marc Zyngier <maz@misterjones.org>
Signed-off-by: Wim Van Sebroeck <wim@iguana.be>

authored by Marc Zyngier and committed by Wim Van Sebroeck 66aaa7a5 12926dc4

+403
+5
drivers/watchdog/Kconfig
··· 55 To compile this driver as a module, choose M here: the 56 module will be called softdog. 57 58 config WM831X_WATCHDOG 59 tristate "WM831x watchdog" 60 depends on MFD_WM831X
··· 55 To compile this driver as a module, choose M here: the 56 module will be called softdog. 57 58 + config MAX63XX_WATCHDOG 59 + tristate "Max63xx watchdog" 60 + help 61 + Support for memory mapped max63{69,70,71,72,73,74} watchdog timer. 62 + 63 config WM831X_WATCHDOG 64 tristate "WM831x watchdog" 65 depends on MFD_WM831X
+1
drivers/watchdog/Makefile
··· 143 # Architecture Independant 144 obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o 145 obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o 146 obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
··· 143 # Architecture Independant 144 obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o 145 obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o 146 + obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o 147 obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o
+397
drivers/watchdog/max63xx_wdt.c
···
··· 1 + /* 2 + * drivers/char/watchdog/max63xx_wdt.c 3 + * 4 + * Driver for max63{69,70,71,72,73,74} watchdog timers 5 + * 6 + * Copyright (C) 2009 Marc Zyngier <maz@misterjones.org> 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 + * This driver assumes the watchdog pins are memory mapped (as it is 13 + * the case for the Arcom Zeus). Should it be connected over GPIOs or 14 + * another interface, some abstraction will have to be introduced. 15 + */ 16 + 17 + #include <linux/module.h> 18 + #include <linux/moduleparam.h> 19 + #include <linux/types.h> 20 + #include <linux/kernel.h> 21 + #include <linux/fs.h> 22 + #include <linux/miscdevice.h> 23 + #include <linux/watchdog.h> 24 + #include <linux/init.h> 25 + #include <linux/bitops.h> 26 + #include <linux/platform_device.h> 27 + #include <linux/spinlock.h> 28 + #include <linux/uaccess.h> 29 + #include <linux/io.h> 30 + #include <linux/device.h> 31 + 32 + #define DEFAULT_HEARTBEAT 60 33 + #define MAX_HEARTBEAT 60 34 + 35 + static int heartbeat = DEFAULT_HEARTBEAT; 36 + static int nowayout = WATCHDOG_NOWAYOUT; 37 + 38 + /* 39 + * Memory mapping: a single byte, 3 first lower bits to select bit 3 40 + * to ping the watchdog. 41 + */ 42 + #define MAX6369_WDSET (7 << 0) 43 + #define MAX6369_WDI (1 << 3) 44 + 45 + static DEFINE_SPINLOCK(io_lock); 46 + 47 + static unsigned long wdt_status; 48 + #define WDT_IN_USE 0 49 + #define WDT_RUNNING 1 50 + #define WDT_OK_TO_CLOSE 2 51 + 52 + static int nodelay; 53 + static struct resource *wdt_mem; 54 + static void __iomem *wdt_base; 55 + static struct platform_device *max63xx_pdev; 56 + 57 + /* 58 + * The timeout values used are actually the absolute minimum the chip 59 + * offers. Typical values on my board are slightly over twice as long 60 + * (10s setting ends up with a 25s timeout), and can be up to 3 times 61 + * the nominal setting (according to the datasheet). So please take 62 + * these values with a grain of salt. Same goes for the initial delay 63 + * "feature". Only max6373/74 have a few settings without this initial 64 + * delay (selected with the "nodelay" parameter). 65 + * 66 + * I also decided to remove from the tables any timeout smaller than a 67 + * second, as it looked completly overkill... 68 + */ 69 + 70 + /* Timeouts in second */ 71 + struct max63xx_timeout { 72 + u8 wdset; 73 + u8 tdelay; 74 + u8 twd; 75 + }; 76 + 77 + static struct max63xx_timeout max6369_table[] = { 78 + { 5, 1, 1 }, 79 + { 6, 10, 10 }, 80 + { 7, 60, 60 }, 81 + { }, 82 + }; 83 + 84 + static struct max63xx_timeout max6371_table[] = { 85 + { 6, 60, 3 }, 86 + { 7, 60, 60 }, 87 + { }, 88 + }; 89 + 90 + static struct max63xx_timeout max6373_table[] = { 91 + { 2, 60, 1 }, 92 + { 5, 0, 1 }, 93 + { 1, 3, 3 }, 94 + { 7, 60, 10 }, 95 + { 6, 0, 10 }, 96 + { }, 97 + }; 98 + 99 + static struct max63xx_timeout *current_timeout; 100 + 101 + static struct max63xx_timeout * 102 + max63xx_select_timeout(struct max63xx_timeout *table, int value) 103 + { 104 + while (table->twd) { 105 + if (value <= table->twd) { 106 + if (nodelay && table->tdelay == 0) 107 + return table; 108 + 109 + if (!nodelay) 110 + return table; 111 + } 112 + 113 + table++; 114 + } 115 + 116 + return NULL; 117 + } 118 + 119 + static void max63xx_wdt_ping(void) 120 + { 121 + u8 val; 122 + 123 + spin_lock(&io_lock); 124 + 125 + val = __raw_readb(wdt_base); 126 + 127 + __raw_writeb(val | MAX6369_WDI, wdt_base); 128 + __raw_writeb(val & ~MAX6369_WDI, wdt_base); 129 + 130 + spin_unlock(&io_lock); 131 + } 132 + 133 + static void max63xx_wdt_enable(struct max63xx_timeout *entry) 134 + { 135 + u8 val; 136 + 137 + if (test_and_set_bit(WDT_RUNNING, &wdt_status)) 138 + return; 139 + 140 + spin_lock(&io_lock); 141 + 142 + val = __raw_readb(wdt_base); 143 + val &= ~MAX6369_WDSET; 144 + val |= entry->wdset; 145 + __raw_writeb(val, wdt_base); 146 + 147 + spin_unlock(&io_lock); 148 + 149 + /* check for a edge triggered startup */ 150 + if (entry->tdelay == 0) 151 + max63xx_wdt_ping(); 152 + } 153 + 154 + static void max63xx_wdt_disable(void) 155 + { 156 + spin_lock(&io_lock); 157 + 158 + __raw_writeb(3, wdt_base); 159 + 160 + spin_unlock(&io_lock); 161 + 162 + clear_bit(WDT_RUNNING, &wdt_status); 163 + } 164 + 165 + static int max63xx_wdt_open(struct inode *inode, struct file *file) 166 + { 167 + if (test_and_set_bit(WDT_IN_USE, &wdt_status)) 168 + return -EBUSY; 169 + 170 + max63xx_wdt_enable(current_timeout); 171 + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 172 + 173 + return nonseekable_open(inode, file); 174 + } 175 + 176 + static ssize_t max63xx_wdt_write(struct file *file, const char *data, 177 + size_t len, loff_t *ppos) 178 + { 179 + if (len) { 180 + if (!nowayout) { 181 + size_t i; 182 + 183 + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 184 + for (i = 0; i != len; i++) { 185 + char c; 186 + 187 + if (get_user(c, data + i)) 188 + return -EFAULT; 189 + 190 + if (c == 'V') 191 + set_bit(WDT_OK_TO_CLOSE, &wdt_status); 192 + } 193 + } 194 + 195 + max63xx_wdt_ping(); 196 + } 197 + 198 + return len; 199 + } 200 + 201 + static const struct watchdog_info ident = { 202 + .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, 203 + .identity = "max63xx Watchdog", 204 + }; 205 + 206 + static long max63xx_wdt_ioctl(struct file *file, unsigned int cmd, 207 + unsigned long arg) 208 + { 209 + int ret = -ENOTTY; 210 + 211 + switch (cmd) { 212 + case WDIOC_GETSUPPORT: 213 + ret = copy_to_user((struct watchdog_info *)arg, &ident, 214 + sizeof(ident)) ? -EFAULT : 0; 215 + break; 216 + 217 + case WDIOC_GETSTATUS: 218 + case WDIOC_GETBOOTSTATUS: 219 + ret = put_user(0, (int *)arg); 220 + break; 221 + 222 + case WDIOC_KEEPALIVE: 223 + max63xx_wdt_ping(); 224 + ret = 0; 225 + break; 226 + 227 + case WDIOC_GETTIMEOUT: 228 + ret = put_user(heartbeat, (int *)arg); 229 + break; 230 + } 231 + return ret; 232 + } 233 + 234 + static int max63xx_wdt_release(struct inode *inode, struct file *file) 235 + { 236 + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) 237 + max63xx_wdt_disable(); 238 + else 239 + dev_crit(&max63xx_pdev->dev, 240 + "device closed unexpectedly - timer will not stop\n"); 241 + 242 + clear_bit(WDT_IN_USE, &wdt_status); 243 + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); 244 + 245 + return 0; 246 + } 247 + 248 + static const struct file_operations max63xx_wdt_fops = { 249 + .owner = THIS_MODULE, 250 + .llseek = no_llseek, 251 + .write = max63xx_wdt_write, 252 + .unlocked_ioctl = max63xx_wdt_ioctl, 253 + .open = max63xx_wdt_open, 254 + .release = max63xx_wdt_release, 255 + }; 256 + 257 + static struct miscdevice max63xx_wdt_miscdev = { 258 + .minor = WATCHDOG_MINOR, 259 + .name = "watchdog", 260 + .fops = &max63xx_wdt_fops, 261 + }; 262 + 263 + static int __devinit max63xx_wdt_probe(struct platform_device *pdev) 264 + { 265 + int ret = 0; 266 + int size; 267 + struct resource *res; 268 + struct device *dev = &pdev->dev; 269 + struct max63xx_timeout *table; 270 + 271 + table = (struct max63xx_timeout *)pdev->id_entry->driver_data; 272 + 273 + if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 274 + heartbeat = DEFAULT_HEARTBEAT; 275 + 276 + dev_info(dev, "requesting %ds heartbeat\n", heartbeat); 277 + current_timeout = max63xx_select_timeout(table, heartbeat); 278 + 279 + if (!current_timeout) { 280 + dev_err(dev, "unable to satisfy heartbeat request\n"); 281 + return -EINVAL; 282 + } 283 + 284 + dev_info(dev, "using %ds heartbeat with %ds initial delay\n", 285 + current_timeout->twd, current_timeout->tdelay); 286 + 287 + heartbeat = current_timeout->twd; 288 + 289 + max63xx_pdev = pdev; 290 + 291 + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 292 + if (res == NULL) { 293 + dev_err(dev, "failed to get memory region resource\n"); 294 + return -ENOENT; 295 + } 296 + 297 + size = resource_size(res); 298 + wdt_mem = request_mem_region(res->start, size, pdev->name); 299 + 300 + if (wdt_mem == NULL) { 301 + dev_err(dev, "failed to get memory region\n"); 302 + return -ENOENT; 303 + } 304 + 305 + wdt_base = ioremap(res->start, size); 306 + if (!wdt_base) { 307 + dev_err(dev, "failed to map memory region\n"); 308 + ret = -ENOMEM; 309 + goto out_request; 310 + } 311 + 312 + ret = misc_register(&max63xx_wdt_miscdev); 313 + if (ret < 0) { 314 + dev_err(dev, "cannot register misc device\n"); 315 + goto out_unmap; 316 + } 317 + 318 + return 0; 319 + 320 + out_unmap: 321 + iounmap(wdt_base); 322 + out_request: 323 + release_resource(wdt_mem); 324 + kfree(wdt_mem); 325 + 326 + return ret; 327 + } 328 + 329 + static int __devexit max63xx_wdt_remove(struct platform_device *pdev) 330 + { 331 + misc_deregister(&max63xx_wdt_miscdev); 332 + if (wdt_mem) { 333 + release_resource(wdt_mem); 334 + kfree(wdt_mem); 335 + wdt_mem = NULL; 336 + } 337 + 338 + if (wdt_base) 339 + iounmap(wdt_base); 340 + 341 + return 0; 342 + } 343 + 344 + static struct platform_device_id max63xx_id_table[] = { 345 + { "max6369_wdt", (kernel_ulong_t)max6369_table, }, 346 + { "max6370_wdt", (kernel_ulong_t)max6369_table, }, 347 + { "max6371_wdt", (kernel_ulong_t)max6371_table, }, 348 + { "max6372_wdt", (kernel_ulong_t)max6371_table, }, 349 + { "max6373_wdt", (kernel_ulong_t)max6373_table, }, 350 + { "max6374_wdt", (kernel_ulong_t)max6373_table, }, 351 + { }, 352 + }; 353 + MODULE_DEVICE_TABLE(platform, max63xx_id_table); 354 + 355 + static struct platform_driver max63xx_wdt_driver = { 356 + .probe = max63xx_wdt_probe, 357 + .remove = __devexit_p(max63xx_wdt_remove), 358 + .id_table = max63xx_id_table, 359 + .driver = { 360 + .name = "max63xx_wdt", 361 + .owner = THIS_MODULE, 362 + }, 363 + }; 364 + 365 + static int __init max63xx_wdt_init(void) 366 + { 367 + return platform_driver_register(&max63xx_wdt_driver); 368 + } 369 + 370 + static void __exit max63xx_wdt_exit(void) 371 + { 372 + platform_driver_unregister(&max63xx_wdt_driver); 373 + } 374 + 375 + module_init(max63xx_wdt_init); 376 + module_exit(max63xx_wdt_exit); 377 + 378 + MODULE_AUTHOR("Marc Zyngier <maz@misterjones.org>"); 379 + MODULE_DESCRIPTION("max63xx Watchdog Driver"); 380 + 381 + module_param(heartbeat, int, 0); 382 + MODULE_PARM_DESC(heartbeat, 383 + "Watchdog heartbeat period in seconds from 1 to " 384 + __MODULE_STRING(MAX_HEARTBEAT) ", default " 385 + __MODULE_STRING(DEFAULT_HEARTBEAT)); 386 + 387 + module_param(nowayout, int, 0); 388 + MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 389 + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 390 + 391 + module_param(nodelay, int, 0); 392 + MODULE_PARM_DESC(nodelay, 393 + "Force selection of a timeout setting without initial delay " 394 + "(max6373/74 only, default=0)"); 395 + 396 + MODULE_LICENSE("GPL"); 397 + MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);