From 28c842c8b0f5d1c2da823b11326e63cdfdbc3def Mon Sep 17 00:00:00 2001 From: Pohsun Su Date: Wed, 7 May 2025 12:43:09 +0800 Subject: [PATCH] clocksource/drivers/timer-tegra186: Add WDIOC_GETTIMELEFT support This change adds support for WDIOC_GETTIMELEFT so userspace programs can get the number of seconds before system reset by the watchdog timer via ioctl. Signed-off-by: Pohsun Su Signed-off-by: Robert Lin Link: https://lore.kernel.org/r/20250507044311.3751033-2-robelin@nvidia.com Signed-off-by: Daniel Lezcano --- drivers/clocksource/timer-tegra186.c | 64 +++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c index 5d4cf5237a11..5eb6b7e3fe2f 100644 --- a/drivers/clocksource/timer-tegra186.c +++ b/drivers/clocksource/timer-tegra186.c @@ -1,8 +1,9 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2019-2020 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2019-2025 NVIDIA Corporation. All rights reserved. */ +#include #include #include #include @@ -29,6 +30,7 @@ #define TMRSR 0x004 #define TMRSR_INTR_CLR BIT(30) +#define TMRSR_PCV GENMASK(28, 0) #define TMRCSSR 0x008 #define TMRCSSR_SRC_USEC (0 << 0) @@ -45,6 +47,9 @@ #define WDTCR_TIMER_SOURCE_MASK 0xf #define WDTCR_TIMER_SOURCE(x) ((x) & 0xf) +#define WDTSR 0x004 +#define WDTSR_CURRENT_EXPIRATION_COUNT GENMASK(14, 12) + #define WDTCMDR 0x008 #define WDTCMDR_DISABLE_COUNTER BIT(1) #define WDTCMDR_START_COUNTER BIT(0) @@ -234,12 +239,69 @@ static int tegra186_wdt_set_timeout(struct watchdog_device *wdd, return 0; } +static unsigned int tegra186_wdt_get_timeleft(struct watchdog_device *wdd) +{ + struct tegra186_wdt *wdt = to_tegra186_wdt(wdd); + u32 expiration, val; + u64 timeleft; + + if (!watchdog_active(&wdt->base)) { + /* return zero if the watchdog timer is not activated. */ + return 0; + } + + /* + * Reset occurs on the fifth expiration of the + * watchdog timer and so when the watchdog timer is configured, + * the actual value programmed into the counter is 1/5 of the + * timeout value. Once the counter reaches 0, expiration count + * will be increased by 1 and the down counter restarts. + * Hence to get the time left before system reset we must + * combine 2 parts: + * 1. value of the current down counter + * 2. (number of counter expirations remaining) * (timeout/5) + */ + + /* Get the current number of counter expirations. Should be a + * value between 0 and 4 + */ + val = readl_relaxed(wdt->regs + WDTSR); + expiration = FIELD_GET(WDTSR_CURRENT_EXPIRATION_COUNT, val); + if (WARN_ON_ONCE(expiration > 4)) + return 0; + + /* Get the current counter value in microsecond. */ + val = readl_relaxed(wdt->tmr->regs + TMRSR); + timeleft = FIELD_GET(TMRSR_PCV, val); + + /* + * Calculate the time remaining by adding the time for the + * counter value to the time of the counter expirations that + * remain. + */ + timeleft += (((u64)wdt->base.timeout * USEC_PER_SEC) / 5) * (4 - expiration); + + /* + * Convert the current counter value to seconds, + * rounding up to the nearest second. Cast u64 to + * u32 under the assumption that no overflow happens + * when coverting to seconds. + */ + timeleft = DIV_ROUND_CLOSEST_ULL(timeleft, USEC_PER_SEC); + + if (WARN_ON_ONCE(timeleft > U32_MAX)) + return U32_MAX; + + return lower_32_bits(timeleft); +} + static const struct watchdog_ops tegra186_wdt_ops = { .owner = THIS_MODULE, .start = tegra186_wdt_start, .stop = tegra186_wdt_stop, .ping = tegra186_wdt_ping, .set_timeout = tegra186_wdt_set_timeout, + .get_timeleft = tegra186_wdt_get_timeleft, }; static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra, -- 2.25.1