clocksource/drivers/timer-tegra186: Add WDIOC_GETTIMELEFT support
authorPohsun Su <pohsuns@nvidia.com>
Wed, 7 May 2025 04:43:09 +0000 (12:43 +0800)
committerDaniel Lezcano <daniel.lezcano@linaro.org>
Fri, 16 May 2025 09:10:32 +0000 (11:10 +0200)
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 <pohsuns@nvidia.com>
Signed-off-by: Robert Lin <robelin@nvidia.com>
Link: https://lore.kernel.org/r/20250507044311.3751033-2-robelin@nvidia.com
Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org>
drivers/clocksource/timer-tegra186.c

index 5d4cf5237a113cdd6a300072500f878b08d09163..5eb6b7e3fe2fcc046f8daed3502982a4a9404e14 100644 (file)
@@ -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 <linux/bitfield.h>
 #include <linux/clocksource.h>
 #include <linux/module.h>
 #include <linux/interrupt.h>
@@ -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,