| This generic GPIO watchdog is used on Huawei E970 (bcm47xx) |
| |
| Signed-off-by: Mathias Adam <m.adam--openwrt@adamis.de> |
| |
| --- a/drivers/watchdog/Kconfig |
| +++ b/drivers/watchdog/Kconfig |
| @@ -1657,6 +1657,15 @@ config WDT_MTX1 |
| Hardware driver for the MTX-1 boards. This is a watchdog timer that |
| will reboot the machine after a 100 seconds timer expired. |
| |
| +config GPIO_WDT |
| + tristate "GPIO Hardware Watchdog" |
| + help |
| + Hardware driver for GPIO-controlled watchdogs. GPIO pin and |
| + toggle interval settings are platform-specific. The driver |
| + will stop toggling the GPIO (i.e. machine reboots) after a |
| + 100 second timer expired and no process has written to |
| + /dev/watchdog during that time. |
| + |
| config PNX833X_WDT |
| tristate "PNX833x Hardware Watchdog" |
| depends on SOC_PNX8335 |
| --- a/drivers/watchdog/Makefile |
| +++ b/drivers/watchdog/Makefile |
| @@ -158,6 +158,7 @@ obj-$(CONFIG_RC32434_WDT) += rc32434_wdt |
| obj-$(CONFIG_INDYDOG) += indydog.o |
| obj-$(CONFIG_JZ4740_WDT) += jz4740_wdt.o |
| obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o |
| +obj-$(CONFIG_GPIO_WDT) += old_gpio_wdt.o |
| obj-$(CONFIG_PNX833X_WDT) += pnx833x_wdt.o |
| obj-$(CONFIG_SIBYTE_WDOG) += sb_wdog.o |
| obj-$(CONFIG_AR7_WDT) += ar7_wdt.o |
| --- /dev/null |
| +++ b/drivers/watchdog/old_gpio_wdt.c |
| @@ -0,0 +1,301 @@ |
| +/* |
| + * Driver for GPIO-controlled Hardware Watchdogs. |
| + * |
| + * Copyright (C) 2013 Mathias Adam <m.adam--linux@adamis.de> |
| + * |
| + * Replaces mtx1_wdt (driver for the MTX-1 Watchdog): |
| + * |
| + * (C) Copyright 2005 4G Systems <info@4g-systems.biz>, |
| + * All Rights Reserved. |
| + * http://www.4g-systems.biz |
| + * |
| + * (C) Copyright 2007 OpenWrt.org, Florian Fainelli <florian@openwrt.org> |
| + * |
| + * 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 <info@4g-systems.biz> |
| + * |
| + * 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 <m.adam--linux@adamis.de> |
| + * 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 <linux/module.h> |
| +#include <linux/moduleparam.h> |
| +#include <linux/types.h> |
| +#include <linux/errno.h> |
| +#include <linux/miscdevice.h> |
| +#include <linux/fs.h> |
| +#include <linux/init.h> |
| +#include <linux/ioport.h> |
| +#include <linux/timer.h> |
| +#include <linux/completion.h> |
| +#include <linux/jiffies.h> |
| +#include <linux/watchdog.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/io.h> |
| +#include <linux/uaccess.h> |
| +#include <linux/gpio.h> |
| +#include <linux/old_gpio_wdt.h> |
| + |
| +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(struct timer_list *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); |
| + timer_setup(&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"); |
| --- /dev/null |
| +++ b/include/linux/old_gpio_wdt.h |
| @@ -0,0 +1,21 @@ |
| +/* |
| + * Definitions for the GPIO watchdog driver |
| + * |
| + * Copyright (C) 2013 Mathias Adam <m.adam--linux@adamis.de> |
| + * |
| + * This program is free software; you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 2 as |
| + * published by the Free Software Foundation. |
| + * |
| + */ |
| + |
| +#ifndef _GPIO_WDT_H_ |
| +#define _GPIO_WDT_H_ |
| + |
| +struct gpio_wdt_platform_data { |
| + int gpio; /* GPIO line number */ |
| + int interval; /* watchdog reset interval in system ticks */ |
| + int first_interval; /* first wd reset interval in system ticks */ |
| +}; |
| + |
| +#endif /* _GPIO_WDT_H_ */ |