/* * Watchdog driver for Technologic Systems TS-72xx based SBCs * (TS-7200, TS-7250 and TS-7260). These boards have external * glue logic CPLD chip, which includes programmable watchdog * timer. * * Copyright (c) 2009 Mika Westerberg <mika.westerberg@iki.fi> * * This driver is based on ep93xx_wdt and wm831x_wdt drivers. * * This file is licensed under the terms of the GNU General Public * License version 2. This program is licensed "as is" without any * warranty of any kind, whether express or implied. */ #include <linux/fs.h> #include <linux/io.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/miscdevice.h> #include <linux/mutex.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/watchdog.h> #include <linux/uaccess.h> #define TS72XX_WDT_FEED_VAL 0x05 #define TS72XX_WDT_DEFAULT_TIMEOUT 8 static int timeout = TS72XX_WDT_DEFAULT_TIMEOUT; module_param(timeout, int, 0); MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. " "(1 <= timeout <= 8, default=" __MODULE_STRING(TS72XX_WDT_DEFAULT_TIMEOUT) ")"); static int nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); /** * struct ts72xx_wdt - watchdog control structure * @lock: lock that protects this structure * @regval: watchdog timeout value suitable for control register * @flags: flags controlling watchdog device state * @control_reg: watchdog control register * @feed_reg: watchdog feed register * @pdev: back pointer to platform dev */ struct ts72xx_wdt { struct mutex lock; int regval; #define TS72XX_WDT_BUSY_FLAG 1 #define TS72XX_WDT_EXPECT_CLOSE_FLAG 2 int flags; void __iomem *control_reg; void __iomem *feed_reg; struct platform_device *pdev; }; struct platform_device *ts72xx_wdt_pdev; /* * TS-72xx Watchdog supports following timeouts (value written * to control register): * value description * ------------------------- * 0x00 watchdog disabled * 0x01 250ms * 0x02 500ms * 0x03 1s * 0x04 reserved * 0x05 2s * 0x06 4s * 0x07 8s * * Timeouts below 1s are not very usable so we don't * allow them at all. * * We provide two functions that convert between these: * timeout_to_regval() and regval_to_timeout(). */ static const struct { int timeout; int regval; } ts72xx_wdt_map[] = { { 1, 3 }, { 2, 5 }, { 4, 6 }, { 8, 7 }, }; /** * timeout_to_regval() - converts given timeout to control register value * @new_timeout: timeout in seconds to be converted * * Function converts given @new_timeout into valid value that can * be programmed into watchdog control register. When conversion is * not possible, function returns %-EINVAL. */ static int timeout_to_regval(int new_timeout) { int i; /* first limit it to 1 - 8 seconds */ new_timeout = clamp_val(new_timeout, 1, 8); for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { if (ts72xx_wdt_map[i].timeout >= new_timeout) return ts72xx_wdt_map[i].regval; } return -EINVAL; } /** * regval_to_timeout() - converts control register value to timeout * @regval: control register value to be converted * * Function converts given @regval to timeout in seconds (1, 2, 4 or 8). * If @regval cannot be converted, function returns %-EINVAL. */ static int regval_to_timeout(int regval) { int i; for (i = 0; i < ARRAY_SIZE(ts72xx_wdt_map); i++) { if (ts72xx_wdt_map[i].regval == regval) return ts72xx_wdt_map[i].timeout; } return -EINVAL; } /** * ts72xx_wdt_kick() - kick the watchdog * @wdt: watchdog to be kicked * * Called with @wdt->lock held. */ static inline void ts72xx_wdt_kick(struct ts72xx_wdt *wdt) { __raw_writeb(TS72XX_WDT_FEED_VAL, wdt->feed_reg); } /** * ts72xx_wdt_start() - starts the watchdog timer * @wdt: watchdog to be started * * This function programs timeout to watchdog timer * and starts it. * * Called with @wdt->lock held. */ static void ts72xx_wdt_start(struct ts72xx_wdt *wdt) { /* * To program the wdt, it first must be "fed" and * only after that (within 30 usecs) the configuration * can be changed. */ ts72xx_wdt_kick(wdt); __raw_writeb((u8)wdt->regval, wdt->control_reg); } /** * ts72xx_wdt_stop() - stops the watchdog timer * @wdt: watchdog to be stopped * * Called with @wdt->lock held. */ static void ts72xx_wdt_stop(struct ts72xx_wdt *wdt) { ts72xx_wdt_kick(wdt); __raw_writeb(0, wdt->control_reg); } static int ts72xx_wdt_open(struct inode *inode, struct file *file) { struct ts72xx_wdt *wdt = platform_get_drvdata(ts72xx_wdt_pdev); int regval; /* * Try to convert default timeout to valid register * value first. */ regval = timeout_to_regval(timeout); if (regval < 0) { dev_err(&wdt->pdev->dev, "failed to convert timeout (%d) to register value\n", timeout); return -EINVAL; } if (mutex_lock_interruptible(&wdt->lock)) return -ERESTARTSYS; if ((wdt->flags & TS72XX_WDT_BUSY_FLAG) != 0) { mutex_unlock(&wdt->lock); return -EBUSY; } wdt->flags = TS72XX_WDT_BUSY_FLAG; wdt->regval = regval; file->private_data = wdt; ts72xx_wdt_start(wdt); mutex_unlock(&wdt->lock); return nonseekable_open(inode, file); } static int ts72xx_wdt_release(struct inode *inode, struct file *file) { struct ts72xx_wdt *wdt = file->private_data; if (mutex_lock_interruptible(&wdt->lock)) return -ERESTARTSYS; if ((wdt->flags & TS72XX_WDT_EXPECT_CLOSE_FLAG) != 0) { ts72xx_wdt_stop(wdt); } else { dev_warn(&wdt->pdev->dev, "TS-72XX WDT device closed unexpectly. " "Watchdog timer will not stop!\n"); /* * Kick it one more time, to give userland some time * to recover (for example, respawning the kicker * daemon). */ ts72xx_wdt_kick(wdt); } wdt->flags = 0; mutex_unlock(&wdt->lock); return 0; } static ssize_t ts72xx_wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { struct ts72xx_wdt *wdt = file->private_data; if (!len) return 0; if (mutex_lock_interruptible(&wdt->lock)) return -ERESTARTSYS; ts72xx_wdt_kick(wdt); /* * Support for magic character closing. User process * writes 'V' into the device, just before it is closed. * This means that we know that the wdt timer can be * stopped after user closes the device. */ if (!nowayout) { int i; for (i = 0; i < len; i++) { char c; /* In case it was set long ago */ wdt->flags &= ~TS72XX_WDT_EXPECT_CLOSE_FLAG; if (get_user(c, data + i)) { mutex_unlock(&wdt->lock); return -EFAULT; } if (c == 'V') { wdt->flags |= TS72XX_WDT_EXPECT_CLOSE_FLAG; break; } } } mutex_unlock(&wdt->lock); return len; } static const struct watchdog_info winfo = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, .firmware_version = 1, .identity = "TS-72XX WDT", }; static long ts72xx_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct ts72xx_wdt *wdt = file->private_data; void __user *argp = (void __user *)arg; int __user *p = (int __user *)argp; int error = 0; if (mutex_lock_interruptible(&wdt->lock)) return -ERESTARTSYS; switch (cmd) { case WDIOC_GETSUPPORT: error = copy_to_user(argp, &winfo, sizeof(winfo)); break; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, p); case WDIOC_KEEPALIVE: ts72xx_wdt_kick(wdt); break; case WDIOC_SETOPTIONS: { int options; if (get_user(options, p)) { error = -EFAULT; break; } error = -EINVAL; if ((options & WDIOS_DISABLECARD) != 0) { ts72xx_wdt_stop(wdt); error = 0; } if ((options & WDIOS_ENABLECARD) != 0) { ts72xx_wdt_start(wdt); error = 0; } break; } case WDIOC_SETTIMEOUT: { int new_timeout; if (get_user(new_timeout, p)) { error = -EFAULT; } else { int regval; regval = timeout_to_regval(new_timeout); if (regval < 0) { error = -EINVAL; } else { ts72xx_wdt_stop(wdt); wdt->regval = regval; ts72xx_wdt_start(wdt); } } if (error) break; /*FALLTHROUGH*/ } case WDIOC_GETTIMEOUT: if (put_user(regval_to_timeout(wdt->regval), p)) error = -EFAULT; break; default: error = -ENOTTY; break; } mutex_unlock(&wdt->lock); return error; } static const struct file_operations ts72xx_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .open = ts72xx_wdt_open, .release = ts72xx_wdt_release, .write = ts72xx_wdt_write, .unlocked_ioctl = ts72xx_wdt_ioctl, }; static struct miscdevice ts72xx_wdt_miscdev = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &ts72xx_wdt_fops, }; static __devinit int ts72xx_wdt_probe(struct platform_device *pdev) { struct ts72xx_wdt *wdt; struct resource *r1, *r2; int error = 0; wdt = kzalloc(sizeof(struct ts72xx_wdt), GFP_KERNEL); if (!wdt) { dev_err(&pdev->dev, "failed to allocate memory\n"); return -ENOMEM; } r1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!r1) { dev_err(&pdev->dev, "failed to get memory resource\n"); error = -ENODEV; goto fail; } r1 = request_mem_region(r1->start, resource_size(r1), pdev->name); if (!r1) { dev_err(&pdev->dev, "cannot request memory region\n"); error = -EBUSY; goto fail; } wdt->control_reg = ioremap(r1->start, resource_size(r1)); if (!wdt->control_reg) { dev_err(&pdev->dev, "failed to map memory\n"); error = -ENODEV; goto fail_free_control; } r2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!r2) { dev_err(&pdev->dev, "failed to get memory resource\n"); error = -ENODEV; goto fail_unmap_control; } r2 = request_mem_region(r2->start, resource_size(r2), pdev->name); if (!r2) { dev_err(&pdev->dev, "cannot request memory region\n"); error = -EBUSY; goto fail_unmap_control; } wdt->feed_reg = ioremap(r2->start, resource_size(r2)); if (!wdt->feed_reg) { dev_err(&pdev->dev, "failed to map memory\n"); error = -ENODEV; goto fail_free_feed; } platform_set_drvdata(pdev, wdt); ts72xx_wdt_pdev = pdev; wdt->pdev = pdev; mutex_init(&wdt->lock); /* make sure that the watchdog is disabled */ ts72xx_wdt_stop(wdt); error = misc_register(&ts72xx_wdt_miscdev); if (error) { dev_err(&pdev->dev, "failed to register miscdev\n"); goto fail_unmap_feed; } dev_info(&pdev->dev, "TS-72xx Watchdog driver\n"); return 0; fail_unmap_feed: platform_set_drvdata(pdev, NULL); iounmap(wdt->feed_reg); fail_free_feed: release_mem_region(r2->start, resource_size(r2)); fail_unmap_control: iounmap(wdt->control_reg); fail_free_control: release_mem_region(r1->start, resource_size(r1)); fail: kfree(wdt); return error; } static __devexit int ts72xx_wdt_remove(struct platform_device *pdev) { struct ts72xx_wdt *wdt = platform_get_drvdata(pdev); struct resource *res; int error; error = misc_deregister(&ts72xx_wdt_miscdev); platform_set_drvdata(pdev, NULL); iounmap(wdt->feed_reg); res = platform_get_resource(pdev, IORESOURCE_MEM, 1); release_mem_region(res->start, resource_size(res)); iounmap(wdt->control_reg); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); release_mem_region(res->start, resource_size(res)); kfree(wdt); return error; } static struct platform_driver ts72xx_wdt_driver = { .probe = ts72xx_wdt_probe, .remove = __devexit_p(ts72xx_wdt_remove), .driver = { .name = "ts72xx-wdt", .owner = THIS_MODULE, }, }; static __init int ts72xx_wdt_init(void) { return platform_driver_register(&ts72xx_wdt_driver); } module_init(ts72xx_wdt_init); static __exit void ts72xx_wdt_exit(void) { platform_driver_unregister(&ts72xx_wdt_driver); } module_exit(ts72xx_wdt_exit); MODULE_AUTHOR("Mika Westerberg <mika.westerberg@iki.fi>"); MODULE_DESCRIPTION("TS-72xx SBC Watchdog"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:ts72xx-wdt");