Skip to content

Commit 28c842c

Browse files
Pohsun Sudlezcano
authored andcommitted
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 <[email protected]> Signed-off-by: Robert Lin <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Daniel Lezcano <[email protected]>
1 parent eb7bc69 commit 28c842c

File tree

1 file changed

+63
-1
lines changed

1 file changed

+63
-1
lines changed

drivers/clocksource/timer-tegra186.c

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// SPDX-License-Identifier: GPL-2.0-only
22
/*
3-
* Copyright (c) 2019-2020 NVIDIA Corporation. All rights reserved.
3+
* Copyright (c) 2019-2025 NVIDIA Corporation. All rights reserved.
44
*/
55

6+
#include <linux/bitfield.h>
67
#include <linux/clocksource.h>
78
#include <linux/module.h>
89
#include <linux/interrupt.h>
@@ -29,6 +30,7 @@
2930

3031
#define TMRSR 0x004
3132
#define TMRSR_INTR_CLR BIT(30)
33+
#define TMRSR_PCV GENMASK(28, 0)
3234

3335
#define TMRCSSR 0x008
3436
#define TMRCSSR_SRC_USEC (0 << 0)
@@ -45,6 +47,9 @@
4547
#define WDTCR_TIMER_SOURCE_MASK 0xf
4648
#define WDTCR_TIMER_SOURCE(x) ((x) & 0xf)
4749

50+
#define WDTSR 0x004
51+
#define WDTSR_CURRENT_EXPIRATION_COUNT GENMASK(14, 12)
52+
4853
#define WDTCMDR 0x008
4954
#define WDTCMDR_DISABLE_COUNTER BIT(1)
5055
#define WDTCMDR_START_COUNTER BIT(0)
@@ -234,12 +239,69 @@ static int tegra186_wdt_set_timeout(struct watchdog_device *wdd,
234239
return 0;
235240
}
236241

242+
static unsigned int tegra186_wdt_get_timeleft(struct watchdog_device *wdd)
243+
{
244+
struct tegra186_wdt *wdt = to_tegra186_wdt(wdd);
245+
u32 expiration, val;
246+
u64 timeleft;
247+
248+
if (!watchdog_active(&wdt->base)) {
249+
/* return zero if the watchdog timer is not activated. */
250+
return 0;
251+
}
252+
253+
/*
254+
* Reset occurs on the fifth expiration of the
255+
* watchdog timer and so when the watchdog timer is configured,
256+
* the actual value programmed into the counter is 1/5 of the
257+
* timeout value. Once the counter reaches 0, expiration count
258+
* will be increased by 1 and the down counter restarts.
259+
* Hence to get the time left before system reset we must
260+
* combine 2 parts:
261+
* 1. value of the current down counter
262+
* 2. (number of counter expirations remaining) * (timeout/5)
263+
*/
264+
265+
/* Get the current number of counter expirations. Should be a
266+
* value between 0 and 4
267+
*/
268+
val = readl_relaxed(wdt->regs + WDTSR);
269+
expiration = FIELD_GET(WDTSR_CURRENT_EXPIRATION_COUNT, val);
270+
if (WARN_ON_ONCE(expiration > 4))
271+
return 0;
272+
273+
/* Get the current counter value in microsecond. */
274+
val = readl_relaxed(wdt->tmr->regs + TMRSR);
275+
timeleft = FIELD_GET(TMRSR_PCV, val);
276+
277+
/*
278+
* Calculate the time remaining by adding the time for the
279+
* counter value to the time of the counter expirations that
280+
* remain.
281+
*/
282+
timeleft += (((u64)wdt->base.timeout * USEC_PER_SEC) / 5) * (4 - expiration);
283+
284+
/*
285+
* Convert the current counter value to seconds,
286+
* rounding up to the nearest second. Cast u64 to
287+
* u32 under the assumption that no overflow happens
288+
* when coverting to seconds.
289+
*/
290+
timeleft = DIV_ROUND_CLOSEST_ULL(timeleft, USEC_PER_SEC);
291+
292+
if (WARN_ON_ONCE(timeleft > U32_MAX))
293+
return U32_MAX;
294+
295+
return lower_32_bits(timeleft);
296+
}
297+
237298
static const struct watchdog_ops tegra186_wdt_ops = {
238299
.owner = THIS_MODULE,
239300
.start = tegra186_wdt_start,
240301
.stop = tegra186_wdt_stop,
241302
.ping = tegra186_wdt_ping,
242303
.set_timeout = tegra186_wdt_set_timeout,
304+
.get_timeleft = tegra186_wdt_get_timeleft,
243305
};
244306

245307
static struct tegra186_wdt *tegra186_wdt_create(struct tegra186_timer *tegra,

0 commit comments

Comments
 (0)