diff options
Diffstat (limited to 'drivers/clocksource/timer-tegra186.c')
-rw-r--r-- | drivers/clocksource/timer-tegra186.c | 100 |
1 files changed, 63 insertions, 37 deletions
diff --git a/drivers/clocksource/timer-tegra186.c b/drivers/clocksource/timer-tegra186.c index 5d4cf5237a11..e5394f98a02e 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 <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) @@ -169,18 +174,6 @@ static void tegra186_wdt_enable(struct tegra186_wdt *wdt) value &= ~WDTCR_PERIOD_MASK; value |= WDTCR_PERIOD(1); - /* enable local interrupt for WDT petting */ - value |= WDTCR_LOCAL_INT_ENABLE; - - /* enable local FIQ and remote interrupt for debug dump */ - if (0) - value |= WDTCR_REMOTE_INT_ENABLE | - WDTCR_LOCAL_FIQ_ENABLE; - - /* enable system debug reset (doesn't properly reboot) */ - if (0) - value |= WDTCR_SYSTEM_DEBUG_RESET_ENABLE; - /* enable system POR reset */ value |= WDTCR_SYSTEM_POR_RESET_ENABLE; @@ -234,12 +227,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, @@ -365,23 +415,10 @@ static int tegra186_timer_usec_init(struct tegra186_timer *tegra) return clocksource_register_hz(&tegra->usec, USEC_PER_SEC); } -static irqreturn_t tegra186_timer_irq(int irq, void *data) -{ - struct tegra186_timer *tegra = data; - - if (watchdog_active(&tegra->wdt->base)) { - tegra186_wdt_disable(tegra->wdt); - tegra186_wdt_enable(tegra->wdt); - } - - return IRQ_HANDLED; -} - static int tegra186_timer_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct tegra186_timer *tegra; - unsigned int irq; int err; tegra = devm_kzalloc(dev, sizeof(*tegra), GFP_KERNEL); @@ -400,8 +437,6 @@ static int tegra186_timer_probe(struct platform_device *pdev) if (err < 0) return err; - irq = err; - /* create a watchdog using a preconfigured timer */ tegra->wdt = tegra186_wdt_create(tegra, 0); if (IS_ERR(tegra->wdt)) { @@ -428,17 +463,8 @@ static int tegra186_timer_probe(struct platform_device *pdev) goto unregister_osc; } - err = devm_request_irq(dev, irq, tegra186_timer_irq, 0, - "tegra186-timer", tegra); - if (err < 0) { - dev_err(dev, "failed to request IRQ#%u: %d\n", irq, err); - goto unregister_usec; - } - return 0; -unregister_usec: - clocksource_unregister(&tegra->usec); unregister_osc: clocksource_unregister(&tegra->osc); unregister_tsc: |