summaryrefslogtreecommitdiff
path: root/arch/arm/mach-ux500/timer-prcmu.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-ux500/timer-prcmu.c')
-rw-r--r--arch/arm/mach-ux500/timer-prcmu.c122
1 files changed, 122 insertions, 0 deletions
diff --git a/arch/arm/mach-ux500/timer-prcmu.c b/arch/arm/mach-ux500/timer-prcmu.c
new file mode 100644
index 00000000000..64dc5730506
--- /dev/null
+++ b/arch/arm/mach-ux500/timer-prcmu.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson
+ * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson
+ * sched_clock implementation is based on:
+ * plat-nomadik/timer.c Linus Walleij <linus.walleij@stericsson.com>
+ *
+ * UX500 PRCMU Timer
+ * The PRCMU has 5 timers which are available in a always-on
+ * power domain. we use the Timer 4 for our always-on clock source.
+ */
+#include <linux/clockchips.h>
+#include <linux/clk.h>
+#include <linux/jiffies.h>
+#include <linux/boottime.h>
+#include <linux/cnt32_to_63.h>
+#include <linux/sched.h>
+#include <mach/setup.h>
+#include <mach/db8500-regs.h>
+#include <mach/hardware.h>
+
+#define RATE_32K (32768)
+
+#define TIMER_MODE_CONTINOUS (0x1)
+#define TIMER_DOWNCOUNT_VAL (0xffffffff)
+
+/* PRCMU Timer 4 */
+#define PRCMU_TIMER_4_REF (prcmu_base + 0x450)
+#define PRCMU_TIMER_4_DOWNCOUNT (prcmu_base + 0x454)
+#define PRCMU_TIMER_4_MODE (prcmu_base + 0x458)
+
+static __iomem void *prcmu_base;
+
+#define SCHED_CLOCK_MIN_WRAP (131072) /* 2^32 / 32768 */
+
+static cycle_t prcmu_read_timer_nop(struct clocksource *cs)
+{
+ return 0;
+}
+
+static struct clocksource prcmu_clksrc = {
+ .name = "prcmu-timer4",
+ .rating = 300,
+ .read = prcmu_read_timer_nop,
+ .shift = 10,
+ .mask = CLOCKSOURCE_MASK(32),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static cycle_t prcmu_read_timer(struct clocksource *cs)
+{
+ u32 count, count2;
+
+ do {
+ count = readl(PRCMU_TIMER_4_DOWNCOUNT);
+ count2 = readl(PRCMU_TIMER_4_DOWNCOUNT);
+ } while (count2 != count);
+
+ /*
+ * clocksource: the prcmu timer is a decrementing counters, so we negate
+ * the value being read.
+ */
+ return ~count;
+}
+
+#ifdef CONFIG_UX500_PRCMU_TIMER
+unsigned long long notrace sched_clock(void)
+{
+ return clocksource_cyc2ns(prcmu_clksrc.read(
+ &prcmu_clksrc),
+ prcmu_clksrc.mult,
+ prcmu_clksrc.shift);
+}
+#endif
+
+#ifdef CONFIG_BOOTTIME
+
+static unsigned long __init boottime_get_time(void)
+{
+ return div_s64(clocksource_cyc2ns(prcmu_clksrc.read(
+ &prcmu_clksrc),
+ prcmu_clksrc.mult,
+ prcmu_clksrc.shift), 1000);
+}
+
+static struct boottime_timer __initdata boottime_timer = {
+ .init = NULL,
+ .get_time = boottime_get_time,
+ .finalize = NULL,
+};
+#endif
+
+void __init prcmu_timer_init(void)
+{
+ if (ux500_is_svp())
+ return;
+
+ prcmu_base = __io_address(U8500_PRCMU_BASE);
+
+ clocksource_calc_mult_shift(&prcmu_clksrc,
+ RATE_32K, SCHED_CLOCK_MIN_WRAP);
+
+ /*
+ * The A9 sub system expects the timer to be configured as
+ * a continous looping timer.
+ * The PRCMU should configure it but if it for some reason
+ * don't we do it here.
+ */
+ if (readl(PRCMU_TIMER_4_MODE) != TIMER_MODE_CONTINOUS) {
+ writel(TIMER_MODE_CONTINOUS, PRCMU_TIMER_4_MODE);
+ writel(TIMER_DOWNCOUNT_VAL, PRCMU_TIMER_4_REF);
+ }
+ prcmu_clksrc.read = prcmu_read_timer;
+
+ clocksource_register(&prcmu_clksrc);
+
+ if (!ux500_is_svp())
+ boottime_activate(&boottime_timer);
+}
+