summaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-tegra/tegra20-timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-tegra/tegra20-timer.c')
-rw-r--r--arch/arm/mach-tegra/tegra20-timer.c119
1 files changed, 119 insertions, 0 deletions
diff --git a/arch/arm/mach-tegra/tegra20-timer.c b/arch/arm/mach-tegra/tegra20-timer.c
new file mode 100644
index 0000000000..aafbfd43c7
--- /dev/null
+++ b/arch/arm/mach-tegra/tegra20-timer.c
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013 Lucas Stach <l.stach@pengutronix.de>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file
+ * @brief Device driver for the Tegra 20 timer, which exposes a clocksource.
+ */
+
+#include <common.h>
+#include <clock.h>
+#include <init.h>
+#include <io.h>
+#include <linux/clk.h>
+
+/* register definitions */
+#define TIMERUS_CNTR_1US 0x10
+#define TIMERUS_USEC_CFG 0x14
+
+static void __iomem *timer_base;
+
+static uint64_t tegra20_timer_cs_read(void)
+{
+ return readl(timer_base + TIMERUS_CNTR_1US);
+}
+
+static struct clocksource cs = {
+ .read = tegra20_timer_cs_read,
+ .mask = CLOCKSOURCE_MASK(32),
+};
+
+static int tegra20_timer_probe(struct device_d *dev)
+{
+ struct clk *timer_clk;
+ unsigned long rate;
+ u32 reg;
+
+ /* use only one timer */
+ if (timer_base)
+ return -EBUSY;
+
+ timer_base = dev_request_mem_region(dev, 0);
+ if (!timer_base) {
+ dev_err(dev, "could not get memory region\n");
+ return -ENODEV;
+ }
+
+ timer_clk = clk_get(dev, NULL);
+ if (!timer_clk) {
+ dev_err(dev, "could not get clock\n");
+ return -ENODEV;
+ }
+
+ clk_enable(timer_clk);
+
+ /*
+ * calibrate timer to run at 1MHz
+ * TIMERUS_USEC_CFG selects the scale down factor with bits [0:7]
+ * representing the divisor and bits [8:15] representing the dividend
+ * each in n+1 form.
+ */
+ rate = clk_get_rate(timer_clk);
+ switch (rate) {
+ case 12000000:
+ reg = 0x000b;
+ break;
+ case 13000000:
+ reg = 0x000c;
+ break;
+ case 19200000:
+ reg = 0x045f;
+ break;
+ case 26000000:
+ reg = 0x0019;
+ break;
+ default:
+ reg = 0;
+ dev_warn(dev, "unknown timer clock rate\n");
+ break;
+ }
+ writel(reg, timer_base + TIMERUS_USEC_CFG);
+
+ cs.mult = clocksource_hz2mult(1000000, cs.shift);
+ init_clock(&cs);
+
+ return 0;
+}
+
+static __maybe_unused struct of_device_id tegra20_timer_dt_ids[] = {
+ {
+ .compatible = "nvidia,tegra20-timer",
+ }, {
+ /* sentinel */
+ }
+};
+
+static struct driver_d tegra20_timer_driver = {
+ .probe = tegra20_timer_probe,
+ .name = "tegra20-timer",
+ .of_compatible = DRV_OF_COMPAT(tegra20_timer_dt_ids),
+};
+
+static int tegra20_timer_init(void)
+{
+ return platform_driver_register(&tegra20_timer_driver);
+}
+coredevice_initcall(tegra20_timer_init);