diff options
Diffstat (limited to 'drivers/watchdog/ep93xx_wdt.c')
-rw-r--r-- | drivers/watchdog/ep93xx_wdt.c | 182 |
1 files changed, 182 insertions, 0 deletions
diff --git a/drivers/watchdog/ep93xx_wdt.c b/drivers/watchdog/ep93xx_wdt.c new file mode 100644 index 00000000..77050037 --- /dev/null +++ b/drivers/watchdog/ep93xx_wdt.c @@ -0,0 +1,182 @@ +/* + * Watchdog driver for Cirrus Logic EP93xx family of devices. + * + * Copyright (c) 2004 Ray Lehtiniemi + * Copyright (c) 2006 Tower Technologies + * Based on ep93xx driver, bits from alim7101_wdt.c + * + * Authors: Ray Lehtiniemi <rayl@mail.com>, + * Alessandro Zummo <a.zummo@towertech.it> + * + * Copyright (c) 2012 H Hartley Sweeten <hsweeten@visionengravers.com> + * Convert to a platform device and use the watchdog framework API + * + * 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. + * + * This watchdog fires after 250msec, which is a too short interval + * for us to rely on the user space daemon alone. So we ping the + * wdt each ~200msec and eventually stop doing it if the user space + * daemon dies. + * + * TODO: + * + * - Test last reset from watchdog status + * - Add a few missing ioctls + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/timer.h> +#include <linux/io.h> + +#define WDT_VERSION "0.4" + +/* default timeout (secs) */ +#define WDT_TIMEOUT 30 + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +static unsigned int timeout = WDT_TIMEOUT; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (1<=timeout<=3600, default=" + __MODULE_STRING(WDT_TIMEOUT) ")"); + +static void __iomem *mmio_base; +static struct timer_list timer; +static unsigned long next_heartbeat; + +#define EP93XX_WATCHDOG 0x00 +#define EP93XX_WDSTATUS 0x04 + +/* reset the wdt every ~200ms - the heartbeat of the device is 0.250 seconds*/ +#define WDT_INTERVAL (HZ/5) + +static void ep93xx_wdt_timer_ping(unsigned long data) +{ + if (time_before(jiffies, next_heartbeat)) + writel(0x5555, mmio_base + EP93XX_WATCHDOG); + + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); +} + +static int ep93xx_wdt_start(struct watchdog_device *wdd) +{ + next_heartbeat = jiffies + (timeout * HZ); + + writel(0xaaaa, mmio_base + EP93XX_WATCHDOG); + mod_timer(&timer, jiffies + WDT_INTERVAL); + + return 0; +} + +static int ep93xx_wdt_stop(struct watchdog_device *wdd) +{ + del_timer_sync(&timer); + writel(0xaa55, mmio_base + EP93XX_WATCHDOG); + + return 0; +} + +static int ep93xx_wdt_keepalive(struct watchdog_device *wdd) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); + + return 0; +} + +static const struct watchdog_info ep93xx_wdt_ident = { + .options = WDIOF_CARDRESET | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .identity = "EP93xx Watchdog", +}; + +static struct watchdog_ops ep93xx_wdt_ops = { + .owner = THIS_MODULE, + .start = ep93xx_wdt_start, + .stop = ep93xx_wdt_stop, + .ping = ep93xx_wdt_keepalive, +}; + +static struct watchdog_device ep93xx_wdt_wdd = { + .info = &ep93xx_wdt_ident, + .ops = &ep93xx_wdt_ops, +}; + +static int __devinit ep93xx_wdt_probe(struct platform_device *pdev) +{ + struct resource *res; + unsigned long val; + int err; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENXIO; + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), pdev->name)) + return -EBUSY; + + mmio_base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!mmio_base) + return -ENXIO; + + if (timeout < 1 || timeout > 3600) { + timeout = WDT_TIMEOUT; + dev_warn(&pdev->dev, + "timeout value must be 1<=x<=3600, using %d\n", + timeout); + } + + val = readl(mmio_base + EP93XX_WATCHDOG); + ep93xx_wdt_wdd.bootstatus = (val & 0x01) ? WDIOF_CARDRESET : 0; + ep93xx_wdt_wdd.timeout = timeout; + + watchdog_set_nowayout(&ep93xx_wdt_wdd, nowayout); + + setup_timer(&timer, ep93xx_wdt_timer_ping, 1); + + err = watchdog_register_device(&ep93xx_wdt_wdd); + if (err) + return err; + + dev_info(&pdev->dev, + "EP93XX watchdog, driver version " WDT_VERSION "%s\n", + (val & 0x08) ? " (nCS1 disable detected)" : ""); + + return 0; +} + +static int __devexit ep93xx_wdt_remove(struct platform_device *pdev) +{ + watchdog_unregister_device(&ep93xx_wdt_wdd); + return 0; +} + +static struct platform_driver ep93xx_wdt_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "ep93xx-wdt", + }, + .probe = ep93xx_wdt_probe, + .remove = __devexit_p(ep93xx_wdt_remove), +}; + +module_platform_driver(ep93xx_wdt_driver); + +MODULE_AUTHOR("Ray Lehtiniemi <rayl@mail.com>," + "Alessandro Zummo <a.zummo@towertech.it>," + "H Hartley Sweeten <hsweeten@visionengravers.com>"); +MODULE_DESCRIPTION("EP93xx Watchdog"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(WDT_VERSION); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |