/* * Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. */ #include #include #include #include #include #include #include /* minimum and maximum watchdog trigger timeout, in seconds */ #define MIN_WDT_TIMEOUT 1 #define MAX_WDT_TIMEOUT 255 /* * Base of the WDT registers, from the timer base address. There are * actually 5 watchdogs that can be configured (by pairing with an available * timer), at bases 0x100 + (WDT ID) * 0x20, where WDT ID is 0 through 4. * This driver only configures the first watchdog (WDT ID 0). */ #define WDT_BASE 0x100 #define WDT_ID 0 /* * Register base of the timer that's selected for pairing with the watchdog. * This driver arbitrarily uses timer 5, which is currently unused by * other drivers (in particular, the Tegra clocksource driver). If this * needs to change, take care that the new timer is not used by the * clocksource driver. */ #define WDT_TIMER_BASE 0x60 #define WDT_TIMER_ID 5 /* WDT registers */ #define WDT_CFG 0x0 #define WDT_CFG_PERIOD_SHIFT 4 #define WDT_CFG_PERIOD_MASK 0xff #define WDT_CFG_INT_EN (1 << 12) #define WDT_CFG_PMC2CAR_RST_EN (1 << 15) #define WDT_STS 0x4 #define WDT_STS_COUNT_SHIFT 4 #define WDT_STS_COUNT_MASK 0xff #define WDT_STS_EXP_SHIFT 12 #define WDT_STS_EXP_MASK 0x3 #define WDT_CMD 0x8 #define WDT_CMD_START_COUNTER (1 << 0) #define WDT_CMD_DISABLE_COUNTER (1 << 1) #define WDT_UNLOCK (0xc) #define WDT_UNLOCK_PATTERN (0xc45a << 0) /* Timer registers */ #define TIMER_PTV 0x0 #define TIMER_EN (1 << 31) #define TIMER_PERIODIC (1 << 30) struct tegra_wdt { struct watchdog_device wdd; void __iomem *wdt_regs; void __iomem *tmr_regs; }; #define WDT_HEARTBEAT 120 static int heartbeat = WDT_HEARTBEAT; module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds. (default = " __MODULE_STRING(WDT_HEARTBEAT) ")"); static bool nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); static int tegra_wdt_start(struct watchdog_device *wdd) { struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); u32 val; /* * This thing has a fixed 1MHz clock. Normally, we would set the * period to 1 second by writing 1000000ul, but the watchdog system * reset actually occurs on the 4th expiration of this counter, * so we set the period to 1/4 of this amount. */ val = 1000000ul / 4; val |= (TIMER_EN | TIMER_PERIODIC); writel(val, wdt->tmr_regs + TIMER_PTV); /* * Set number of periods and start counter. * * 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. */ val = WDT_TIMER_ID | (wdd->timeout << WDT_CFG_PERIOD_SHIFT) | WDT_CFG_PMC2CAR_RST_EN; writel(val, wdt->wdt_regs + WDT_CFG); writel(WDT_CMD_START_COUNTER, wdt->wdt_regs + WDT_CMD); return 0; } static int tegra_wdt_stop(struct watchdog_device *wdd) { struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); writel(WDT_UNLOCK_PATTERN, wdt->wdt_regs + WDT_UNLOCK); writel(WDT_CMD_DISABLE_COUNTER, wdt->wdt_regs + WDT_CMD); writel(0, wdt->tmr_regs + TIMER_PTV); return 0; } static int tegra_wdt_ping(struct watchdog_device *wdd) { struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); writel(WDT_CMD_START_COUNTER, wdt->wdt_regs + WDT_CMD); return 0; } static int tegra_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { wdd->timeout = timeout; if (watchdog_active(wdd)) { tegra_wdt_stop(wdd); return tegra_wdt_start(wdd); } return 0; } static unsigned int tegra_wdt_get_timeleft(struct watchdog_device *wdd) { struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); u32 val; int count; int exp; val = readl(wdt->wdt_regs + WDT_STS); /* Current countdown (from timeout) */ count = (val >> WDT_STS_COUNT_SHIFT) & WDT_STS_COUNT_MASK; /* Number of expirations (we are waiting for the 4th expiration) */ exp = (val >> WDT_STS_EXP_SHIFT) & WDT_STS_EXP_MASK; /* * The entire thing is divided by 4 because we are ticking down 4 times * faster due to needing to wait for the 4th expiration. */ return (((3 - exp) * wdd->timeout) + count) / 4; } static const struct watchdog_info tegra_wdt_info = { .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, .firmware_version = 0, .identity = "Tegra Watchdog", }; static const struct watchdog_ops tegra_wdt_ops = { .owner = THIS_MODULE, .start = tegra_wdt_start, .stop = tegra_wdt_stop, .ping = tegra_wdt_ping, .set_timeout = tegra_wdt_set_timeout, .get_timeleft = tegra_wdt_get_timeleft, }; static int tegra_wdt_probe(struct platform_device *pdev) { struct watchdog_device *wdd; struct tegra_wdt *wdt; struct resource *res; void __iomem *regs; int ret; /* This is the timer base. */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(regs)) return PTR_ERR(regs); /* * Allocate our watchdog driver data, which has the * struct watchdog_device nested within it. */ wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) return -ENOMEM; /* Initialize struct tegra_wdt. */ wdt->wdt_regs = regs + WDT_BASE; wdt->tmr_regs = regs + WDT_TIMER_BASE; /* Initialize struct watchdog_device. */ wdd = &wdt->wdd; wdd->timeout = heartbeat; wdd->info = &tegra_wdt_info; wdd->ops = &tegra_wdt_ops; wdd->min_timeout = MIN_WDT_TIMEOUT; wdd->max_timeout = MAX_WDT_TIMEOUT; wdd->parent = &pdev->dev; watchdog_set_drvdata(wdd, wdt); watchdog_set_nowayout(wdd, nowayout); ret = watchdog_register_device(wdd); if (ret) { dev_err(&pdev->dev, "failed to register watchdog device\n"); return ret; } platform_set_drvdata(pdev, wdt); dev_info(&pdev->dev, "initialized (heartbeat = %d sec, nowayout = %d)\n", heartbeat, nowayout); return 0; } static int tegra_wdt_remove(struct platform_device *pdev) { struct tegra_wdt *wdt = platform_get_drvdata(pdev); tegra_wdt_stop(&wdt->wdd); watchdog_unregister_device(&wdt->wdd); dev_info(&pdev->dev, "removed wdt\n"); return 0; } #ifdef CONFIG_PM_SLEEP static int tegra_wdt_runtime_suspend(struct device *dev) { struct tegra_wdt *wdt = dev_get_drvdata(dev); if (watchdog_active(&wdt->wdd)) tegra_wdt_stop(&wdt->wdd); return 0; } static int tegra_wdt_runtime_resume(struct device *dev) { struct tegra_wdt *wdt = dev_get_drvdata(dev); if (watchdog_active(&wdt->wdd)) tegra_wdt_start(&wdt->wdd); return 0; } #endif static const struct of_device_id tegra_wdt_of_match[] = { { .compatible = "nvidia,tegra30-timer-wdt", }, { .compatible = "nvidia,tegra210-timer-wdt", }, { }, }; MODULE_DEVICE_TABLE(of, tegra_wdt_of_match); static const struct dev_pm_ops tegra_wdt_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(tegra_wdt_runtime_suspend, tegra_wdt_runtime_resume) }; static struct platform_driver tegra_wdt_driver = { .probe = tegra_wdt_probe, .remove = tegra_wdt_remove, .driver = { .name = "tegra-wdt", .pm = &tegra_wdt_pm_ops, .of_match_table = tegra_wdt_of_match, }, }; module_platform_driver(tegra_wdt_driver); MODULE_AUTHOR("NVIDIA Corporation"); MODULE_DESCRIPTION("Tegra Watchdog Driver"); MODULE_LICENSE("GPL v2");