diff options
author | Jeetesh Burman <jburman@nvidia.com> | 2013-12-17 17:32:59 +0530 |
---|---|---|
committer | Matthew Pedro <mapedro@nvidia.com> | 2014-05-05 08:14:09 -0700 |
commit | 8e435e6e4d7155eeffc1fe60ac98b7dab83ed4ec (patch) | |
tree | 755c75a6031ee094df1e6c1b2baa16f96162d88c | |
parent | 3f41b064189a7ee9d2d04b12ad3d15c416366f5d (diff) |
tegra_wdt: support default watchdog api
Reworked driver in order to properly support default watchdog api
such as triggering by writing a character and disable by sending
a magic character. Renamed ENABLE_ON_PROBE to ENABLE_HEARTBEAT
which triggers the watchdog using the interrupt service routine.
Original patch:
http://git.toradex.com/gitweb/linux-toradex.git/commitdiff/5da592b805718b4f33897d642f577b1e6511bc2b
Bug 1402031
Change-Id: I33ef556dac6d6717cadc00f8937437b925dc2ca3
Signed-off-by: Stefan Agner <stefan.agner@toradex.com>
Signed-off-by: Jeetesh Burman <jburman@nvidia.com>
Reviewed-on: http://git-master/r/346477
Reviewed-by: Varun Wadekar <vwadekar@nvidia.com>
Reviewed-by: Matthew Pedro <mapedro@nvidia.com>
-rw-r--r-- | drivers/watchdog/Kconfig | 15 | ||||
-rw-r--r-- | drivers/watchdog/tegra_wdt.c | 201 |
2 files changed, 150 insertions, 66 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 9a94f7e093dd..8d41441b05ed 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -217,17 +217,20 @@ config TEGRA_WATCHDOG depends on ARCH_TEGRA help Say Y here to include support for the watchdog timer - embedded in NVIDIA Tegra SoCs. + embedded in NVIDIA Tegra SoCs. The default timeout + is set to 60 seconds. To compile this driver as a module, choose M here: the module will be called tegra_wdt. -config TEGRA_WATCHDOG_ENABLE_ON_PROBE - tristate "Turn on Tegra watchdog at system start up" - depends on ARCH_TEGRA && TEGRA_WATCHDOG +config TEGRA_WATCHDOG_ENABLE_HEARTBEAT + bool "Use the first Tegra watchdog as kernel heartbeats" + depends on TEGRA_WATCHDOG + default n help - Say Y here to enable the tegra watchdog at driver - probe time, rather than when the device is opened. + Say Y here to enable the first tegra watchdog at driver + probe time. The kernel will reload the watchdog by + itself in an interrupt handler. config EP93XX_WATCHDOG tristate "EP93xx Watchdog" diff --git a/drivers/watchdog/tegra_wdt.c b/drivers/watchdog/tegra_wdt.c index 1bcd726ebbd6..3e58a984d9c2 100644 --- a/drivers/watchdog/tegra_wdt.c +++ b/drivers/watchdog/tegra_wdt.c @@ -3,7 +3,8 @@ * * watchdog driver for NVIDIA tegra internal watchdog * - * Copyright (c) 2012, NVIDIA Corporation. + * Copyright (c) 2012-2014, NVIDIA Corporation. All rights reserved. + * Copyright (c) 2013, Toradex AG * * based on drivers/watchdog/softdog.c and drivers/watchdog/omap_wdt.c * @@ -42,13 +43,33 @@ /* minimum and maximum watchdog trigger periods, in seconds */ #define MIN_WDT_PERIOD 5 #define MAX_WDT_PERIOD 1000 + /* Assign Timer 7 to Timer 10 for WDT0 to WDT3, respectively */ #define TMR_SRC_START 7 +/* + * For spinlock lockup detection to work, the heartbeat should be 2*lockup + * for cases where the spinlock disabled irqs. + * Must be between MIN_WDT_PERIOD and MAX_WDT_PERIOD + */ +#define WDT_DEFAULT_TIME 60 + +static int heartbeat = WDT_DEFAULT_TIME; +static bool nowayout = WATCHDOG_NOWAYOUT; + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat period in seconds (default=" + __MODULE_STRING(WDT_DEFAULT_TIME)); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#endif enum tegra_wdt_status { WDT_DISABLED = 1 << 0, WDT_ENABLED = 1 << 1, - WDT_ENABLED_AT_PROBE = 1 << 2, + WDT_KERNEL_HEARTBEAT = 1 << 2, }; struct tegra_wdt { @@ -61,18 +82,15 @@ struct tegra_wdt { void __iomem *wdt_source; void __iomem *wdt_timer; void __iomem *int_base; - int irq; int tmrsrc; int timeout; int status; + int way_out_ok; + struct tasklet_struct tasklet; + int irq; + int irq_counter; }; -/* - * For spinlock lockup detection to work, the heartbeat should be 2*lockup - * for cases where the spinlock disabled irqs. - */ -static int heartbeat = 120; /* must be greater than MIN_WDT_PERIOD and lower than MAX_WDT_PERIOD */ - #if defined(CONFIG_ARCH_TEGRA_2x_SOC) #define TIMER_PTV 0x0 @@ -107,6 +125,16 @@ static void tegra_wdt_disable(struct tegra_wdt *wdt) static inline void tegra_wdt_ping(struct tegra_wdt *wdt) { + /* Reset timer */ + tegra_wdt_enable(wdt); + + /* Reenable IRQ in case an interrupt already happend */ + if (wdt->irq_counter) { + writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); + wdt->irq_counter = 0; + enable_irq(wdt->irq); + } + return; } @@ -114,9 +142,30 @@ static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) { struct tegra_wdt *wdt = dev_id; - writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); + wdt->irq_counter++; + + if (wdt->status & WDT_KERNEL_HEARTBEAT) { + tegra_wdt_ping(wdt); + } else { + /* If not in heartbeat mode, disable IRQs to avoid IRQ storm + * We don't acknowledge the interrupt here since the user + * did not reload the timer in time. Upon next interrupt + * the system will reset... + */ + tasklet_schedule(&wdt->tasklet); + disable_irq_nosync(irq); + } + return IRQ_HANDLED; } + +static void tegra_wdt_do_tasklet(unsigned long data) +{ + struct tegra_wdt *wdt = (struct tegra_wdt *)data; + dev_info(wdt->miscdev.parent, "Watchdog interrupt received, system " + "will reset soon if no ping arrives\n"); +} + #elif defined(CONFIG_ARCH_TEGRA_3x_SOC) #define TIMER_PTV 0 @@ -132,6 +181,7 @@ static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) #define WDT_CFG_PMC2CAR_RST_EN (1 << 15) #define WDT_STATUS (4) #define WDT_INTR_STAT (1 << 1) + #define WDT_STATUS_EXPIR_COUNTER (3 << 12) #define WDT_CMD (8) #define WDT_CMD_START_COUNTER (1 << 0) #define WDT_CMD_DISABLE_COUNTER (1 << 1) @@ -169,19 +219,21 @@ static void tegra_wdt_enable(struct tegra_wdt *wdt) val |= (TIMER_EN | TIMER_PERIODIC); writel(val, wdt->wdt_timer + TIMER_PTV); - /* Interrupt handler is not required for user space - * WDT accesses, since the caller is responsible to ping the - * WDT to reset the counter before expiration, through ioctls. + writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); + + /* Interrupt handler is not required for user space since + * a warning in a fourth of time don't make sense. Then + * the interrupt line is also shared, so it can't be disabled + * if one watchdog is about to expire... (interrupt storm) * SYS_RST_EN doesnt work as there is no external reset * from Tegra. */ - val = wdt->tmrsrc | WDT_CFG_PERIOD | /*WDT_CFG_INT_EN |*/ + val = wdt->tmrsrc | WDT_CFG_PERIOD | /* WDT_CFG_INT_EN | */ /*WDT_CFG_SYS_RST_EN |*/ WDT_CFG_PMC2CAR_RST_EN; #ifdef CONFIG_TEGRA_FIQ_DEBUGGER val |= WDT_CFG_FIQ_INT_EN; #endif writel(val, wdt->wdt_source + WDT_CFG); - writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); } static void tegra_wdt_disable(struct tegra_wdt *wdt) @@ -192,17 +244,29 @@ static void tegra_wdt_disable(struct tegra_wdt *wdt) writel(0, wdt->wdt_timer + TIMER_PTV); } +static void tegra_wdt_interrupt_instance(struct tegra_wdt *wdt) +{ + WARN_ON_ONCE(!(wdt->status & WDT_KERNEL_HEARTBEAT)); + + if (wdt->status & WDT_KERNEL_HEARTBEAT) { + tegra_wdt_ping(wdt); + } +} + static irqreturn_t tegra_wdt_interrupt(int irq, void *dev_id) { + struct tegra_wdt *wdt = NULL; unsigned i, status; for (i = 0; i < MAX_NR_CPU_WDT; i++) { - if (tegra_wdt[i] == NULL) + wdt = tegra_wdt[i]; + if (wdt == NULL) continue; - status = readl(tegra_wdt[i]->wdt_source + WDT_STATUS); - if ((tegra_wdt[i]->status & WDT_ENABLED) && - (status & WDT_INTR_STAT)) - tegra_wdt_ping(tegra_wdt[i]); + status = readl(wdt->wdt_source + WDT_STATUS); + if ((wdt->status & WDT_ENABLED) && + (status & WDT_INTR_STAT)) { + tegra_wdt_interrupt_instance(wdt); + } } return IRQ_HANDLED; @@ -228,6 +292,9 @@ static int tegra_wdt_open(struct inode *inode, struct file *file) if (test_and_set_bit(1, &wdt->users)) return -EBUSY; + /* Reset no way out, we need a new magic again */ + wdt->way_out_ok = 0; + wdt->status |= WDT_ENABLED; wdt->timeout = heartbeat; tegra_wdt_enable(wdt); @@ -239,13 +306,19 @@ static int tegra_wdt_release(struct inode *inode, struct file *file) { struct tegra_wdt *wdt = file->private_data; - if (wdt->status == WDT_ENABLED) { -#ifndef CONFIG_WATCHDOG_NOWAYOUT - tegra_wdt_disable(wdt); - wdt->status = WDT_DISABLED; -#endif + if (wdt->status & WDT_ENABLED && !nowayout) { + if (wdt->way_out_ok) { + tegra_wdt_disable(wdt); + wdt->status = WDT_DISABLED; + } else { + dev_info(wdt->miscdev.parent, "No Magic Close " + "received, watchdog not disabled!\n"); + } + } else if (nowayout) { + dev_info(wdt->miscdev.parent, "No way out is " + "enabled, watchdog not disabled!\n"); } - wdt->users = 0; + clear_bit(1, &wdt->users); return 0; } @@ -288,11 +361,10 @@ static long tegra_wdt_ioctl(struct file *file, unsigned int cmd, return put_user(wdt->timeout, (int __user *)arg); case WDIOC_SETOPTIONS: -#ifndef CONFIG_WATCHDOG_NOWAYOUT if (get_user(option, (int __user *)arg)) return -EFAULT; spin_lock(&lock); - if (option & WDIOS_DISABLECARD) { + if (option & WDIOS_DISABLECARD && !nowayout) { wdt->status &= ~WDT_ENABLED; wdt->status |= WDT_DISABLED; tegra_wdt_disable(wdt); @@ -306,9 +378,6 @@ static long tegra_wdt_ioctl(struct file *file, unsigned int cmd, } spin_unlock(&lock); return 0; -#else - return -EINVAL; -#endif } return -ENOTTY; } @@ -316,6 +385,19 @@ static long tegra_wdt_ioctl(struct file *file, unsigned int cmd, static ssize_t tegra_wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) { + struct tegra_wdt *wdt = file->private_data; + + /* check if way-out char was written as last data */ + char c; + if (len) { + tegra_wdt_ping(wdt); + + if (get_user(c, data + len - 1)) + return -EFAULT; + else + wdt->way_out_ok = (('V' == c) ? 1 : 0); + } + return len; } @@ -333,8 +415,14 @@ static int tegra_wdt_probe(struct platform_device *pdev) struct resource *res_src, *res_wdt, *res_irq; struct resource *res_int_base = NULL; struct tegra_wdt *wdt; - u32 src; int ret = 0; +#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_HEARTBEAT +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + int is_heartbeat_wdt = 1; +#else + int is_heartbeat_wdt = !(pdev->id); +#endif +#endif if (pdev->id < -1 && pdev->id > 3) { dev_err(&pdev->dev, "only IDs 3:0 supported\n"); @@ -369,7 +457,6 @@ static int tegra_wdt_probe(struct platform_device *pdev) return -ENOMEM; } - wdt->irq = -1; wdt->miscdev.parent = &pdev->dev; if (pdev->id == -1) { wdt->miscdev.minor = WATCHDOG_MINOR; @@ -410,12 +497,17 @@ static int tegra_wdt_probe(struct platform_device *pdev) goto fail; } - src = readl(wdt->wdt_source); - if (src & BIT(12)) +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + /* Watchdog of Tegra 3 are not at the reset controller regs */ + if (readl(wdt->wdt_source) & BIT(12)) dev_info(&pdev->dev, "last reset due to watchdog timeout\n"); +#endif tegra_wdt_disable(wdt); writel(TIMER_PCR_INTR, wdt->wdt_timer + TIMER_PCR); +#ifndef CONFIG_ARCH_TEGRA_2x_SOC + writel(WDT_CMD_START_COUNTER, wdt->wdt_source + WDT_CMD); +#endif if (res_irq != NULL) { #ifdef CONFIG_TEGRA_FIQ_DEBUGGER @@ -436,6 +528,7 @@ static int tegra_wdt_probe(struct platform_device *pdev) goto fail; tegra_wdt_int_priority(wdt); #endif + wdt->irq = -1; ret = request_irq(res_irq->start, tegra_wdt_interrupt, IRQF_DISABLED, dev_name(&pdev->dev), wdt); if (ret) { @@ -464,24 +557,26 @@ static int tegra_wdt_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, wdt); +#ifdef CONFIG_ARCH_TEGRA_2x_SOC + tasklet_init(&wdt->tasklet, tegra_wdt_do_tasklet, (unsigned long)wdt); +#endif -#ifndef CONFIG_ARCH_TEGRA_2x_SOC -#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_ON_PROBE - /* Init and enable watchdog on WDT0 with timer 8 during probe */ - if (!(pdev->id)) { - u32 val = 0; - wdt->status = WDT_ENABLED | WDT_ENABLED_AT_PROBE; +#ifdef CONFIG_TEGRA_WATCHDOG_ENABLE_HEARTBEAT + /* Enable first watchdog during probe */ + if (is_heartbeat_wdt) { + wdt->status = WDT_ENABLED | WDT_KERNEL_HEARTBEAT; wdt->timeout = heartbeat; + set_bit(1, &wdt->users); tegra_wdt_enable(wdt); - val = readl(wdt->wdt_source + WDT_CFG); - val |= WDT_CFG_INT_EN; - writel(val, wdt->wdt_source + WDT_CFG); - pr_info("WDT heartbeat enabled on probe\n"); + pr_info("WDT kernel heartbeat enabled on probe\n"); } #endif + +#ifndef CONFIG_ARCH_TEGRA_2x_SOC tegra_wdt[pdev->id] = wdt; #endif - pr_info("%s done\n", __func__); + + dev_info(&pdev->dev, "%s done\n", __func__); return 0; fail: if (wdt->irq != -1) @@ -543,16 +638,6 @@ static int tegra_wdt_resume(struct platform_device *pdev) if (wdt->status & WDT_ENABLED) tegra_wdt_enable(wdt); -#ifndef CONFIG_ARCH_TEGRA_2x_SOC - /* Enable interrupt for WDT3 heartbeat watchdog */ - if (wdt->status & WDT_ENABLED_AT_PROBE) { - u32 val = 0; - val = readl(wdt->wdt_source + WDT_CFG); - val |= WDT_CFG_INT_EN; - writel(val, wdt->wdt_source + WDT_CFG); - pr_info("WDT heartbeat enabled on probe\n"); - } -#endif return 0; } #endif @@ -586,10 +671,6 @@ module_exit(tegra_wdt_exit); MODULE_AUTHOR("NVIDIA Corporation"); MODULE_DESCRIPTION("Tegra Watchdog Driver"); -module_param(heartbeat, int, 0); -MODULE_PARM_DESC(heartbeat, - "Watchdog heartbeat period in seconds"); - MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); MODULE_ALIAS("platform:tegra_wdt"); |