[PATCH v6 2/3] drivers: timer: add timer driver for ARMv7 based Tegra devices

Svyatoslav Ryhel clamor95 at gmail.com
Tue Jan 24 07:57:50 CET 2023


Add timer support for T20/T30/T114 and T124 based devices.
Driver is based on DM, has device tree support and can be
used on SPL and early boot stage.

Arm64 Tegra according to comment in tegra-common.h uses
architected timer.

Tested-by: Andreas Westman Dorcsak <hedmoo at yahoo.com> # ASUS TF600T T30
Tested-by: Jonas Schwöbel <jonasschwoebel at yahoo.de> # Surface RT T30
Tested-by: Robert Eckelmann <longnoserob at gmail.com> # ASUS TF101 T20
Tested-by: Agneli <poczt at protomail.ch> # Toshiba AC100 T20
Tested-by: Svyatoslav Ryhel <clamor95 at gmail.com> # LG P895 T30
Co-developed-by: Jonas Schwöbel <jonasschwoebel at yahoo.de>
Signed-off-by: Jonas Schwöbel <jonasschwoebel at yahoo.de>
Signed-off-by: Svyatoslav Ryhel <clamor95 at gmail.com>
Reviewed-by: Simon Glass <sjg at chromium.org>

---
Changed in v6:
 - added paz00 tester

Changed in v5:
 - added comments

Changed in v4:
 - removed BOOTSTAGE ifdefs
 - use early timer on boot stage unconditionally
---
 drivers/timer/Kconfig       |   8 +++
 drivers/timer/Makefile      |   1 +
 drivers/timer/tegra-timer.c | 126 ++++++++++++++++++++++++++++++++++++
 3 files changed, 135 insertions(+)
 create mode 100644 drivers/timer/tegra-timer.c

diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index 6d6665005c..f32bd16227 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -252,6 +252,14 @@ config STM32_TIMER
 	  Select this to enable support for the timer found on
 	  STM32 devices.
 
+config TEGRA_TIMER
+	bool "Tegra timer support"
+	depends on TIMER
+	select TIMER_EARLY
+	help
+	  Select this to enable support for the timer found on
+	  Tegra devices.
+
 config X86_TSC_TIMER
 	bool "x86 Time-Stamp Counter (TSC) timer support"
 	depends on TIMER && X86
diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
index 6470fd5426..3c92113fc6 100644
--- a/drivers/timer/Makefile
+++ b/drivers/timer/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_SP804_TIMER)	+= sp804_timer.o
 obj-$(CONFIG_$(SPL_)SIFIVE_CLINT) += sifive_clint_timer.o
 obj-$(CONFIG_ARM_GLOBAL_TIMER)	+= arm_global_timer.o
 obj-$(CONFIG_STM32_TIMER)	+= stm32_timer.o
+obj-$(CONFIG_TEGRA_TIMER)	+= tegra-timer.o
 obj-$(CONFIG_X86_TSC_TIMER)	+= tsc_timer.o
 obj-$(CONFIG_MTK_TIMER)		+= mtk_timer.o
 obj-$(CONFIG_MCHP_PIT64B_TIMER)	+= mchp-pit64b-timer.o
diff --git a/drivers/timer/tegra-timer.c b/drivers/timer/tegra-timer.c
new file mode 100644
index 0000000000..d2d163cf3f
--- /dev/null
+++ b/drivers/timer/tegra-timer.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Svyatoslav Ryhel <clamor95 at gmail.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <timer.h>
+
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/arch/tegra.h>
+
+#define TEGRA_OSC_CLK_ENB_L_SET		(NV_PA_CLK_RST_BASE + 0x320)
+#define TEGRA_OSC_SET_CLK_ENB_TMR	BIT(5)
+
+#define TEGRA_TIMER_USEC_CNTR		(NV_PA_TMRUS_BASE + 0)
+#define TEGRA_TIMER_USEC_CFG		(NV_PA_TMRUS_BASE + 4)
+
+#define TEGRA_TIMER_RATE		1000000 /* 1 MHz */
+
+/*
+ * On pre-DM stage timer should be left configured by
+ * previous bootloader for correct 1MHz clock.
+ * In the case of reset default value set is 1/13 of
+ * osc clock which should be decent enough to safely
+ * get to DM stage.
+ */
+u64 notrace timer_early_get_count(void)
+{
+	/* At this stage raw timer is used */
+	return readl(TEGRA_TIMER_USEC_CNTR);
+}
+
+unsigned long notrace timer_early_get_rate(void)
+{
+	return TEGRA_TIMER_RATE;
+}
+
+ulong timer_get_boot_us(void)
+{
+	return timer_early_get_count();
+}
+
+/*
+ * At moment of calling get_count, timer driver is already
+ * probed and is configured to have precise 1MHz clock.
+ * Tegra timer has a step of 1 microsecond which removes
+ * need of using adjusments involving uc_priv->clock_rate.
+ */
+static notrace u64 tegra_timer_get_count(struct udevice *dev)
+{
+	u32 val = timer_early_get_count();
+	return timer_conv_64(val);
+}
+
+static int tegra_timer_probe(struct udevice *dev)
+{
+	struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+	u32 usec_config, value;
+
+	/* Timer rate has to be set unconditionally */
+	uc_priv->clock_rate = TEGRA_TIMER_RATE;
+
+	/*
+	 * Configure microsecond timers to have 1MHz clock
+	 * Config register is 0xqqww, where qq is "dividend", ww is "divisor"
+	 * Uses n+1 scheme
+	 */
+	switch (clock_get_osc_freq()) {
+	case CLOCK_OSC_FREQ_13_0:
+		usec_config = 0x000c; /* (12+1)/(0+1) */
+		break;
+	case CLOCK_OSC_FREQ_19_2:
+		usec_config = 0x045f; /* (95+1)/(4+1) */
+		break;
+	case CLOCK_OSC_FREQ_12_0:
+		usec_config = 0x000b; /* (11+1)/(0+1) */
+		break;
+	case CLOCK_OSC_FREQ_26_0:
+		usec_config = 0x0019; /* (25+1)/(0+1) */
+		break;
+	case CLOCK_OSC_FREQ_16_8:
+		usec_config = 0x0453; /* (83+1)/(4+1) */
+		break;
+	case CLOCK_OSC_FREQ_38_4:
+		usec_config = 0x04bf; /* (191+1)/(4+1) */
+		break;
+	case CLOCK_OSC_FREQ_48_0:
+		usec_config = 0x002f; /* (47+1)/(0+1) */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Enable clock to timer hardware */
+	value = readl_relaxed(TEGRA_OSC_CLK_ENB_L_SET);
+	writel_relaxed(value | TEGRA_OSC_SET_CLK_ENB_TMR,
+		       TEGRA_OSC_CLK_ENB_L_SET);
+
+	writel_relaxed(usec_config, TEGRA_TIMER_USEC_CFG);
+
+	return 0;
+}
+
+static const struct timer_ops tegra_timer_ops = {
+	.get_count = tegra_timer_get_count,
+};
+
+static const struct udevice_id tegra_timer_ids[] = {
+	{ .compatible = "nvidia,tegra20-timer" },
+	{ .compatible = "nvidia,tegra30-timer" },
+	{ .compatible = "nvidia,tegra114-timer" },
+	{ .compatible = "nvidia,tegra124-timer" },
+	{ }
+};
+
+U_BOOT_DRIVER(tegra_timer) = {
+	.name		= "tegra_timer",
+	.id		= UCLASS_TIMER,
+	.of_match	= tegra_timer_ids,
+	.probe		= tegra_timer_probe,
+	.ops		= &tegra_timer_ops,
+	.flags		= DM_FLAG_PRE_RELOC,
+};
-- 
2.37.2



More information about the U-Boot mailing list