From 34844524c88edbb0fd75339fd655f6b69b221b0a Mon Sep 17 00:00:00 2001 From: popcornmix Date: Wed, 1 May 2013 19:54:32 +0100 Subject: [PATCH 003/196] bcm2708 watchdog driver Signed-off-by: popcornmix --- drivers/watchdog/Kconfig | 6 + drivers/watchdog/Makefile | 1 + drivers/watchdog/bcm2708_wdog.c | 385 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+) create mode 100644 drivers/watchdog/bcm2708_wdog.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index e89fc31..60ca320 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -391,6 +391,12 @@ config RETU_WATCHDOG To compile this driver as a module, choose M here: the module will be called retu_wdt. +config BCM2708_WDT + tristate "BCM2708 Watchdog" + depends on ARCH_BCM2708 + help + Enables BCM2708 watchdog support. + # AVR32 Architecture config AT32AP700X_WDT diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index a300b94..0e357711 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_TS72XX_WATCHDOG) += ts72xx_wdt.o obj-$(CONFIG_IMX2_WDT) += imx2_wdt.o obj-$(CONFIG_UX500_WATCHDOG) += ux500_wdt.o obj-$(CONFIG_RETU_WATCHDOG) += retu_wdt.o +obj-$(CONFIG_BCM2708_WDT) += bcm2708_wdog.o # AVR32 Architecture obj-$(CONFIG_AT32AP700X_WDT) += at32ap700x_wdt.o diff --git a/drivers/watchdog/bcm2708_wdog.c b/drivers/watchdog/bcm2708_wdog.c new file mode 100644 index 0000000..dd33c35 --- /dev/null +++ b/drivers/watchdog/bcm2708_wdog.c @@ -0,0 +1,385 @@ +/* + * Broadcom BCM2708 watchdog driver. + * + * (c) Copyright 2010 Broadcom Europe Ltd + * + * 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. + * + * BCM2708 watchdog driver. Loosely based on wdt driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SECS_TO_WDOG_TICKS(x) ((x) << 16) +#define WDOG_TICKS_TO_SECS(x) ((x) >> 16) + +static unsigned long wdog_is_open; +static uint32_t wdog_ticks; /* Ticks to load into wdog timer */ +static char expect_close; + +/* + * Module parameters + */ + +#define WD_TIMO 10 /* Default heartbeat = 60 seconds */ +static int heartbeat = WD_TIMO; /* Heartbeat in seconds */ + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, + "Watchdog heartbeat in seconds. (0 < heartbeat < 65536, default=" + __MODULE_STRING(WD_TIMO) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static DEFINE_SPINLOCK(wdog_lock); + +/** + * Start the watchdog driver. + */ + +static int wdog_start(unsigned long timeout) +{ + uint32_t cur; + unsigned long flags; + spin_lock_irqsave(&wdog_lock, flags); + + /* enable the watchdog */ + iowrite32(PM_PASSWORD | (timeout & PM_WDOG_TIME_SET), + __io_address(PM_WDOG)); + cur = ioread32(__io_address(PM_RSTC)); + iowrite32(PM_PASSWORD | (cur & PM_RSTC_WRCFG_CLR) | + PM_RSTC_WRCFG_FULL_RESET, __io_address(PM_RSTC)); + + spin_unlock_irqrestore(&wdog_lock, flags); + return 0; +} + +/** + * Stop the watchdog driver. + */ + +static int wdog_stop(void) +{ + iowrite32(PM_PASSWORD | PM_RSTC_RESET, __io_address(PM_RSTC)); + printk(KERN_INFO "watchdog stopped\n"); + return 0; +} + +/** + * Reload counter one with the watchdog heartbeat. We don't bother + * reloading the cascade counter. + */ + +static void wdog_ping(void) +{ + wdog_start(wdog_ticks); +} + +/** + * @t: the new heartbeat value that needs to be set. + * + * Set a new heartbeat value for the watchdog device. If the heartbeat + * value is incorrect we keep the old value and return -EINVAL. If + * successful we return 0. + */ + +static int wdog_set_heartbeat(int t) +{ + if (t < 1 || t > WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET)) + return -EINVAL; + + heartbeat = t; + wdog_ticks = SECS_TO_WDOG_TICKS(t); + return 0; +} + +/** + * @file: file handle to the watchdog + * @buf: buffer to write (unused as data does not matter here + * @count: count of bytes + * @ppos: pointer to the position to write. No seeks allowed + * + * A write to a watchdog device is defined as a keepalive signal. + * + * if 'nowayout' is set then normally a close() is ignored. But + * if you write 'V' first then the close() will stop the timer. + */ + +static ssize_t wdog_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + wdog_ping(); + } + return count; +} + +static int wdog_get_status(void) +{ + unsigned long flags; + int status = 0; + spin_lock_irqsave(&wdog_lock, flags); + /* FIXME: readback reset reason */ + spin_unlock_irqrestore(&wdog_lock, flags); + return status; +} + +static uint32_t wdog_get_remaining(void) +{ + uint32_t ret = ioread32(__io_address(PM_WDOG)); + return ret & PM_WDOG_TIME_SET; +} + +/** + * @file: file handle to the device + * @cmd: watchdog command + * @arg: argument pointer + * + * The watchdog API defines a common set of functions for all watchdogs + * according to their available features. We only actually usefully support + * querying capabilities and current status. + */ + +static long wdog_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat; + int status; + int options; + uint32_t remaining; + + struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "BCM2708", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + status = wdog_get_status(); + return put_user(status, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + wdog_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + if (wdog_set_heartbeat(new_heartbeat)) + return -EINVAL; + wdog_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + case WDIOC_GETTIMELEFT: + remaining = WDOG_TICKS_TO_SECS(wdog_get_remaining()); + return put_user(remaining, p); + case WDIOC_SETOPTIONS: + if (get_user(options, p)) + return -EFAULT; + if (options & WDIOS_DISABLECARD) + wdog_stop(); + if (options & WDIOS_ENABLECARD) + wdog_start(wdog_ticks); + return 0; + default: + return -ENOTTY; + } +} + +/** + * @inode: inode of device + * @file: file handle to device + * + * The watchdog device has been opened. The watchdog device is single + * open and on opening we load the counters. + */ + +static int wdog_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &wdog_is_open)) + return -EBUSY; + /* + * Activate + */ + wdog_start(wdog_ticks); + return nonseekable_open(inode, file); +} + +/** + * @inode: inode to board + * @file: file handle to board + * + * The watchdog has a configurable API. There is a religious dispute + * between people who want their watchdog to be able to shut down and + * those who want to be sure if the watchdog manager dies the machine + * reboots. In the former case we disable the counters, in the latter + * case you have to open it again very soon. + */ + +static int wdog_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + wdog_stop(); + } else { + printk(KERN_CRIT + "wdt: WDT device closed unexpectedly. WDT will not stop!\n"); + wdog_ping(); + } + clear_bit(0, &wdog_is_open); + expect_close = 0; + return 0; +} + +/** + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. Turn the watchdog + * off so that it does not fire during the next reboot. + */ + +static int wdog_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + wdog_stop(); + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + + +static const struct file_operations wdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdog_write, + .unlocked_ioctl = wdog_ioctl, + .open = wdog_open, + .release = wdog_release, +}; + +static struct miscdevice wdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdog_fops, +}; + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdog_notifier = { + .notifier_call = wdog_notify_sys, +}; + +/** + * cleanup_module: + * + * Unload the watchdog. You cannot do this with any file handles open. + * If your watchdog is set to continue ticking on close and you unload + * it, well it keeps ticking. We won't get the interrupt but the board + * will not touch PC memory so all is fine. You just have to load a new + * module in 60 seconds or reboot. + */ + +static void __exit wdog_exit(void) +{ + misc_deregister(&wdog_miscdev); + unregister_reboot_notifier(&wdog_notifier); +} + +static int __init wdog_init(void) +{ + int ret; + + /* Check that the heartbeat value is within it's range; + if not reset to the default */ + if (wdog_set_heartbeat(heartbeat)) { + wdog_set_heartbeat(WD_TIMO); + printk(KERN_INFO "bcm2708_wdog: heartbeat value must be " + "0 < heartbeat < %d, using %d\n", + WDOG_TICKS_TO_SECS(PM_WDOG_TIME_SET), + WD_TIMO); + } + + ret = register_reboot_notifier(&wdog_notifier); + if (ret) { + printk(KERN_ERR + "wdt: cannot register reboot notifier (err=%d)\n", ret); + goto out_reboot; + } + + ret = misc_register(&wdog_miscdev); + if (ret) { + printk(KERN_ERR + "wdt: cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto out_misc; + } + + printk(KERN_INFO "bcm2708 watchdog, heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + return 0; + +out_misc: + unregister_reboot_notifier(&wdog_notifier); +out_reboot: + return ret; +} + +module_init(wdog_init); +module_exit(wdog_exit); + +MODULE_AUTHOR("Luke Diamand"); +MODULE_DESCRIPTION("Driver for BCM2708 watchdog"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); +MODULE_LICENSE("GPL"); + -- 1.9.1