/* * 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 /* * 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; }; /* * The total expiry count of Tegra WDTs is limited to HW design and depends * on skip configuration if supported. To be safe, we set the default expiry * count to 1. It should be updated later with value specified in device tree. */ static int expiry_count = 1; /* * Period value: Trigger Value is the time period and period value determines * number of periods for watchdog expiration i.e. effectively after time: * "period value" * "Trigger Vaue" */ #define WDT_TRG_PERIOD 1 static int trigger_period = WDT_TRG_PERIOD; #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; /* * The timeout needs to be divided by expiry_count here so as to * keep the ultimate watchdog reset timeout the same as the program * timeout requested by application. The program timeout should make * sure WDT FIQ will never be asserted in a valid use case. */ val = (wdd->timeout * USEC_PER_SEC) / expiry_count; 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 | (trigger_period << 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) { u32 val; struct tegra_wdt *wdt = watchdog_get_drvdata(wdd); /* Disable timer */ tegra_wdt_stop(wdd); /* Load the timeout value */ val = (wdd->timeout * USEC_PER_SEC) / expiry_count; val |= (TIMER_EN | TIMER_PERIODIC); writel(val, wdt->tmr_regs + TIMER_PTV); /* Restart */ 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 */ exp = (val >> WDT_STS_EXP_SHIFT) & WDT_STS_EXP_MASK; /* * The entire thing is divided by expiry_count because we are ticking * down expiry_count times faster due to needing to wait for the * expiry_count'th expiration. */ return (((3 - exp) * wdd->timeout) + count) / expiry_count; } static const struct watchdog_info tegra_wdt_info = { .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, .firmware_version = 0, .identity = "Tegra Watchdog", }; static 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 *wdt_res, *tmr_res; struct device_node *np = pdev->dev.of_node; u32 pval = 0; int ret = 0; wdt_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); tmr_res = platform_get_resource(pdev, IORESOURCE_MEM, 1); if (!wdt_res || !tmr_res) { dev_err(&pdev->dev, "incorrect wdt resources\n"); return -ENOENT; } ret = of_property_read_u32(np, "nvidia,expiry-count", &pval); if (!ret) expiry_count = pval; ret = of_property_read_u32(np, "nvidia,heartbeat-init", &pval); if (!ret) heartbeat = pval; /* * 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; wdt->wdt_regs = devm_ioremap_resource(&pdev->dev, wdt_res); if (IS_ERR(wdt->wdt_regs)) { dev_err(&pdev->dev, "failed ioremap wdt resource\n"); return PTR_ERR(wdt->wdt_regs); } wdt->tmr_regs = devm_ioremap_resource(&pdev->dev, tmr_res); if (IS_ERR(wdt->tmr_regs)) { dev_err(&pdev->dev, "failed ioremap tmr resource\n"); return PTR_ERR(wdt->tmr_regs); } /* 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 * expiry_count; wdd->max_timeout = MAX_WDT_TIMEOUT * expiry_count; wdd->parent = &pdev->dev; watchdog_set_drvdata(wdd, wdt); watchdog_init_timeout(wdd, heartbeat, &pdev->dev); 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 (timeout = %d sec, nowayout = %d)\n", wdt->wdd.timeout, 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,tegra-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");