/* * Driver for GPIO-controlled Hardware Watchdogs. * * Copyright (C) 2013 Mathias Adam * * Replaces mtx1_wdt (driver for the MTX-1 Watchdog): * * (C) Copyright 2005 4G Systems , * All Rights Reserved. * http://www.4g-systems.biz * * (C) Copyright 2007 OpenWrt.org, Florian Fainelli * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Neither Michael Stickel nor 4G Systems admit liability nor provide * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * * (c) Copyright 2005 4G Systems * * Release 0.01. * Author: Michael Stickel michael.stickel@4g-systems.biz * * Release 0.02. * Author: Florian Fainelli florian@openwrt.org * use the Linux watchdog/timer APIs * * Release 0.03. * Author: Mathias Adam * make it a generic gpio watchdog driver * * The Watchdog is configured to reset the MTX-1 * if it is not triggered for 100 seconds. * It should not be triggered more often than 1.6 seconds. * * A timer triggers the watchdog every 5 seconds, until * it is opened for the first time. After the first open * it MUST be triggered every 2..95 seconds. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int ticks = 100 * HZ; static struct { struct completion stop; spinlock_t lock; int running; struct timer_list timer; int queue; int default_ticks; unsigned long inuse; unsigned gpio; unsigned int gstate; int interval; int first_interval; } gpio_wdt_device; static void gpio_wdt_trigger(unsigned long unused) { spin_lock(&gpio_wdt_device.lock); if (gpio_wdt_device.running && ticks > 0) ticks -= gpio_wdt_device.interval; /* toggle wdt gpio */ gpio_wdt_device.gstate = !gpio_wdt_device.gstate; gpio_set_value(gpio_wdt_device.gpio, gpio_wdt_device.gstate); if (gpio_wdt_device.queue && ticks > 0) mod_timer(&gpio_wdt_device.timer, jiffies + gpio_wdt_device.interval); else complete(&gpio_wdt_device.stop); spin_unlock(&gpio_wdt_device.lock); } static void gpio_wdt_reset(void) { ticks = gpio_wdt_device.default_ticks; } static void gpio_wdt_start(void) { unsigned long flags; spin_lock_irqsave(&gpio_wdt_device.lock, flags); if (!gpio_wdt_device.queue) { gpio_wdt_device.queue = 1; gpio_wdt_device.gstate = 1; gpio_set_value(gpio_wdt_device.gpio, 1); mod_timer(&gpio_wdt_device.timer, jiffies + gpio_wdt_device.first_interval); } gpio_wdt_device.running++; spin_unlock_irqrestore(&gpio_wdt_device.lock, flags); } static int gpio_wdt_stop(void) { unsigned long flags; spin_lock_irqsave(&gpio_wdt_device.lock, flags); if (gpio_wdt_device.queue) { gpio_wdt_device.queue = 0; gpio_wdt_device.gstate = 0; gpio_set_value(gpio_wdt_device.gpio, 0); } ticks = gpio_wdt_device.default_ticks; spin_unlock_irqrestore(&gpio_wdt_device.lock, flags); return 0; } /* Filesystem functions */ static int gpio_wdt_open(struct inode *inode, struct file *file) { if (test_and_set_bit(0, &gpio_wdt_device.inuse)) return -EBUSY; return nonseekable_open(inode, file); } static int gpio_wdt_release(struct inode *inode, struct file *file) { clear_bit(0, &gpio_wdt_device.inuse); return 0; } static long gpio_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = (int __user *)argp; unsigned int value; static const struct watchdog_info ident = { .options = WDIOF_CARDRESET, .identity = "GPIO WDT", }; switch (cmd) { case WDIOC_GETSUPPORT: if (copy_to_user(argp, &ident, sizeof(ident))) return -EFAULT; break; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: put_user(0, p); break; case WDIOC_SETOPTIONS: if (get_user(value, p)) return -EFAULT; if (value & WDIOS_ENABLECARD) gpio_wdt_start(); else if (value & WDIOS_DISABLECARD) gpio_wdt_stop(); else return -EINVAL; return 0; case WDIOC_KEEPALIVE: gpio_wdt_reset(); break; default: return -ENOTTY; } return 0; } static ssize_t gpio_wdt_write(struct file *file, const char *buf, size_t count, loff_t *ppos) { if (!count) return -EIO; gpio_wdt_reset(); return count; } static const struct file_operations gpio_wdt_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .unlocked_ioctl = gpio_wdt_ioctl, .open = gpio_wdt_open, .write = gpio_wdt_write, .release = gpio_wdt_release, }; static struct miscdevice gpio_wdt_misc = { .minor = WATCHDOG_MINOR, .name = "watchdog", .fops = &gpio_wdt_fops, }; static int gpio_wdt_probe(struct platform_device *pdev) { int ret; struct gpio_wdt_platform_data *gpio_wdt_data = pdev->dev.platform_data; gpio_wdt_device.gpio = gpio_wdt_data->gpio; gpio_wdt_device.interval = gpio_wdt_data->interval; gpio_wdt_device.first_interval = gpio_wdt_data->first_interval; if (gpio_wdt_device.first_interval <= 0) { gpio_wdt_device.first_interval = gpio_wdt_device.interval; } ret = gpio_request(gpio_wdt_device.gpio, "gpio-wdt"); if (ret < 0) { dev_err(&pdev->dev, "failed to request gpio"); return ret; } spin_lock_init(&gpio_wdt_device.lock); init_completion(&gpio_wdt_device.stop); gpio_wdt_device.queue = 0; clear_bit(0, &gpio_wdt_device.inuse); setup_timer(&gpio_wdt_device.timer, gpio_wdt_trigger, 0L); gpio_wdt_device.default_ticks = ticks; gpio_wdt_start(); dev_info(&pdev->dev, "GPIO Hardware Watchdog driver (gpio=%i interval=%i/%i)\n", gpio_wdt_data->gpio, gpio_wdt_data->first_interval, gpio_wdt_data->interval); return 0; } static int gpio_wdt_remove(struct platform_device *pdev) { /* FIXME: do we need to lock this test ? */ if (gpio_wdt_device.queue) { gpio_wdt_device.queue = 0; wait_for_completion(&gpio_wdt_device.stop); } gpio_free(gpio_wdt_device.gpio); misc_deregister(&gpio_wdt_misc); return 0; } static struct platform_driver gpio_wdt_driver = { .probe = gpio_wdt_probe, .remove = gpio_wdt_remove, .driver.name = "gpio-wdt", .driver.owner = THIS_MODULE, }; static int __init gpio_wdt_init(void) { return platform_driver_register(&gpio_wdt_driver); } arch_initcall(gpio_wdt_init); /* * We do wdt initialization in two steps: arch_initcall probes the wdt * very early to start pinging the watchdog (misc devices are not yet * available), and later module_init() just registers the misc device. */ static int gpio_wdt_init_late(void) { int ret; ret = misc_register(&gpio_wdt_misc); if (ret < 0) { pr_err("GPIO_WDT: failed to register misc device\n"); return ret; } return 0; } #ifndef MODULE module_init(gpio_wdt_init_late); #endif static void __exit gpio_wdt_exit(void) { platform_driver_unregister(&gpio_wdt_driver); } module_exit(gpio_wdt_exit); MODULE_AUTHOR("Michael Stickel, Florian Fainelli, Mathias Adam"); MODULE_DESCRIPTION("Driver for GPIO hardware watchdogs"); MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS("platform:gpio-wdt");