summaryrefslogtreecommitdiff
path: root/arch/arm
diff options
context:
space:
mode:
authorPhilippe Langlais <philippe.langlais@linaro.org>2011-04-08 15:00:55 +0200
committerUlf Hansson <ulf.hansson@stericsson.com>2011-09-19 15:14:47 +0200
commit8c0e04373dc7c0f79aea0178ba487f0d808df059 (patch)
tree72a89dde5a1b7e3ad4bd636079dc9c38f5345a15 /arch/arm
parent8cb29cb1dedecd14119e8055fb1dc5e7010f5dc4 (diff)
mach-ux500: Add timers for power management
Diffstat (limited to 'arch/arm')
-rw-r--r--arch/arm/mach-ux500/Makefile3
-rw-r--r--arch/arm/mach-ux500/include/mach/mtu.h59
-rw-r--r--arch/arm/mach-ux500/timer-db8500-prcmu.c122
-rw-r--r--arch/arm/mach-ux500/timer-db8500.c84
-rw-r--r--arch/arm/mach-ux500/timer-mtu.c296
5 files changed, 563 insertions, 1 deletions
diff --git a/arch/arm/mach-ux500/Makefile b/arch/arm/mach-ux500/Makefile
index 5bf4105f401..91cd1acd49a 100644
--- a/arch/arm/mach-ux500/Makefile
+++ b/arch/arm/mach-ux500/Makefile
@@ -7,12 +7,13 @@ ifeq ($(CONFIG_AB8500_BATTERY_THERM_ON_BATCTRL), y)
endif
obj-y := clock.o cpu.o devices.o devices-common.o \
- id.o pins.o timer-rtt.o usb.o
+ id.o pins.o timer-mtu.o timer-rtt.o usb.o
obj-y += pm/
obj-$(CONFIG_UX500_SOC_DB5500) += cpu-db5500.o dma-db5500.o prcmu-db5500.o \
devices-db5500.o
obj-$(CONFIG_UX500_SOC_DB8500) += cpu-db8500.o devices-db8500.o \
prcmu-db8500.o clock-db8500.o \
+ timer-db8500.o timer-db8500-prcmu.o \
regulator-db8500.o
obj-$(CONFIG_MACH_U8500) += board-mop500.o board-mop500-sdi.o \
diff --git a/arch/arm/mach-ux500/include/mach/mtu.h b/arch/arm/mach-ux500/include/mach/mtu.h
new file mode 100644
index 00000000000..d778c24e3e3
--- /dev/null
+++ b/arch/arm/mach-ux500/include/mach/mtu.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2009 ST-Ericsson SA
+ * MultiTimerUnit register definitions, copied from Nomadik 8815
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#ifndef __ASM_ARCH_MTU_H
+#define __ASM_ARCH_MTU_H
+
+/*
+ * The MTU device hosts four different counters, with 4 set of
+ * registers. These are register names.
+ */
+
+#define MTU_IMSC 0x00 /* Interrupt mask set/clear */
+#define MTU_RIS 0x04 /* Raw interrupt status */
+#define MTU_MIS 0x08 /* Masked interrupt status */
+#define MTU_ICR 0x0C /* Interrupt clear register */
+
+/* per-timer registers take 0..3 as argument */
+#define MTU_LR(x) (0x10 + 0x10 * (x) + 0x00) /* Load value */
+#define MTU_VAL(x) (0x10 + 0x10 * (x) + 0x04) /* Current value */
+#define MTU_CR(x) (0x10 + 0x10 * (x) + 0x08) /* Control reg */
+#define MTU_BGLR(x) (0x10 + 0x10 * (x) + 0x0c) /* At next overflow */
+
+/* bits for the control register */
+#define MTU_CRn_ENA 0x80
+#define MTU_CRn_PERIODIC 0x40 /* if 0 = free-running */
+#define MTU_CRn_PRESCALE_MASK 0x0c
+#define MTU_CRn_PRESCALE_1 0x00
+#define MTU_CRn_PRESCALE_16 0x04
+#define MTU_CRn_PRESCALE_256 0x08
+#define MTU_CRn_32BITS 0x02
+#define MTU_CRn_ONESHOT 0x01 /* if 0 = wraps reloading from BGLR*/
+
+/* Other registers are usual amba/primecell registers, currently not used */
+#define MTU_ITCR 0xff0
+#define MTU_ITOP 0xff4
+
+#define MTU_PERIPH_ID0 0xfe0
+#define MTU_PERIPH_ID1 0xfe4
+#define MTU_PERIPH_ID2 0xfe8
+#define MTU_PERIPH_ID3 0xfeC
+
+#define MTU_PCELL0 0xff0
+#define MTU_PCELL1 0xff4
+#define MTU_PCELL2 0xff8
+#define MTU_PCELL3 0xffC
+
+
+#ifdef CONFIG_LOCAL_TIMERS
+extern void __iomem *twd_base;
+#endif
+
+#endif /* __ASM_ARCH_MTU_H */
+
diff --git a/arch/arm/mach-ux500/timer-db8500-prcmu.c b/arch/arm/mach-ux500/timer-db8500-prcmu.c
new file mode 100644
index 00000000000..606efa3c0f8
--- /dev/null
+++ b/arch/arm/mach-ux500/timer-db8500-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>
+ *
+ * DB8500-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 db8500_prcmu_read_timer_nop(struct clocksource *cs)
+{
+ return 0;
+}
+
+static struct clocksource db8500_prcmu_clksrc = {
+ .name = "db8500-prcmu-timer4",
+ .rating = 300,
+ .read = db8500_prcmu_read_timer_nop,
+ .shift = 10,
+ .mask = CLOCKSOURCE_MASK(32),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static cycle_t db8500_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_U8500_PRCMU_TIMER
+unsigned long long notrace sched_clock(void)
+{
+ return clocksource_cyc2ns(db8500_prcmu_clksrc.read(
+ &db8500_prcmu_clksrc),
+ db8500_prcmu_clksrc.mult,
+ db8500_prcmu_clksrc.shift);
+}
+#endif
+
+#ifdef CONFIG_BOOTTIME
+
+static unsigned long __init boottime_get_time(void)
+{
+ return div_s64(clocksource_cyc2ns(db8500_prcmu_clksrc.read(
+ &db8500_prcmu_clksrc),
+ db8500_prcmu_clksrc.mult,
+ db8500_prcmu_clksrc.shift), 1000);
+}
+
+static struct boottime_timer __initdata boottime_timer = {
+ .init = NULL,
+ .get_time = boottime_get_time,
+ .finalize = NULL,
+};
+#endif
+
+void __init db8500_prcmu_timer_init(void)
+{
+ if (ux500_is_svp())
+ return;
+
+ prcmu_base = __io_address(U8500_PRCMU_BASE);
+
+ clocksource_calc_mult_shift(&db8500_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);
+ }
+ db8500_prcmu_clksrc.read = db8500_prcmu_read_timer;
+
+ clocksource_register(&db8500_prcmu_clksrc);
+
+ if (!ux500_is_svp())
+ boottime_activate(&boottime_timer);
+}
+
diff --git a/arch/arm/mach-ux500/timer-db8500.c b/arch/arm/mach-ux500/timer-db8500.c
new file mode 100644
index 00000000000..6bf78bc2816
--- /dev/null
+++ b/arch/arm/mach-ux500/timer-db8500.c
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * License Terms: GNU General Public License v2
+ * Author: Mattias Wallin <mattias.wallin@stericsson.com> for ST-Ericsson
+ */
+#include <linux/io.h>
+#include <mach/setup.h>
+#include <mach/hardware.h>
+
+#include "pm/context.h"
+
+void u8500_rtc_init(unsigned int cpu);
+void db8500_prcmu_timer_init(void);
+void mtu_timer_init(void);
+void mtu_timer_reset(void);
+
+#ifdef CONFIG_LOCAL_TIMERS
+extern void *twd_base;
+#endif
+
+#ifdef CONFIG_UX500_CONTEXT
+static int mtu_context_notifier_call(struct notifier_block *this,
+ unsigned long event, void *data)
+{
+ if (event == CONTEXT_APE_RESTORE)
+ mtu_timer_reset();
+ return NOTIFY_OK;
+}
+
+static struct notifier_block mtu_context_notifier = {
+ .notifier_call = mtu_context_notifier_call,
+};
+#endif
+
+static void u8500_timer_reset(void)
+{
+ mtu_timer_reset();
+}
+
+static void u8500_timer_suspend(void)
+{
+}
+
+static void __init u8500_timer_init(void)
+{
+
+#ifdef CONFIG_LOCAL_TIMERS
+ if (cpu_is_u5500())
+ twd_base = __io_address(U5500_TWD_BASE);
+ else if (cpu_is_u8500())
+ twd_base = __io_address(U8500_TWD_BASE);
+ else
+ ux500_unknown_soc();
+#endif
+/*
+ * Here we register the timerblocks active in the system.
+ * Localtimers (twd) is started when both cpu is up and running.
+ * MTU register a clocksource, clockevent and sched_clock.
+ * Since the MTU is located in the VAPE power domain
+ * it will be cleared in sleep which makes it unsuitible.
+ * We however need it as a timer tick (clockevent)
+ * during boot to calibrate delay until twd is started.
+ * RTC-RTT have problems as timer tick during boot since it is depending
+ * on delay which is not yet calibrated. RTC-RTT is in the always-on
+ * powerdomain and is used as clockevent instead of twd when sleeping.
+ * The PRCMU timer 4 register a clocksource and sched_clock with higher
+ * rating then MTU since is always-on.
+ *
+ */
+ mtu_timer_init();
+ u8500_rtc_init(0);
+ db8500_prcmu_timer_init();
+
+#ifdef CONFIG_UX500_CONTEXT
+ WARN_ON(context_ape_notifier_register(&mtu_context_notifier));
+#endif
+}
+
+struct sys_timer u8500_timer = {
+ .init = u8500_timer_init,
+ .suspend = u8500_timer_suspend,
+ .resume = u8500_timer_reset,
+};
diff --git a/arch/arm/mach-ux500/timer-mtu.c b/arch/arm/mach-ux500/timer-mtu.c
new file mode 100644
index 00000000000..f9a4c405aa7
--- /dev/null
+++ b/arch/arm/mach-ux500/timer-mtu.c
@@ -0,0 +1,296 @@
+/*
+ * linux/arch/arm/mach-u8500/timer.c
+ *
+ * Copyright (C) 2008 STMicroelectronics
+ * Copyright (C) 2009 Alessandro Rubini, somewhat based on at91sam926x
+ * Copyright (C) 2009 ST-Ericsson SA
+ * added support to u8500 platform, heavily based on 8815
+ * Author: Srinidhi KASAGAR <srinidhi.kasagar@stericsson.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ */
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/clockchips.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/boottime.h>
+#include <linux/cnt32_to_63.h>
+#include <asm/mach/time.h>
+#include <mach/mtu.h>
+#include <mach/setup.h>
+
+#define TIMER_CTRL 0x80 /* No divisor */
+#define TIMER_PERIODIC 0x40
+#define TIMER_SZ32BIT 0x02
+
+static u32 u8500_count; /* accumulated count */
+static u32 u8500_cycle; /* write-once */
+static __iomem void *mtu0_base;
+static __iomem void *mtu1_base;
+
+/*
+ * U8500 sched_clock implementation. It has a resolution of
+ * at least 7.5ns (133MHz MTU rate) and a maximum value of 834 days.
+ *
+ * Because the hardware timer period is quite short (32.3 secs
+ * and because cnt32_to_63() needs to be called at
+ * least once per half period to work properly, a kernel timer is
+ * set up to ensure this requirement is always met.
+ *
+ * Based on plat-orion time.c implementation.
+ */
+#define TCLK2NS_SCALE_FACTOR 8
+
+#ifdef CONFIG_UX500_MTU_TIMER
+static unsigned long tclk2ns_scale;
+static struct timer_list cnt32_to_63_keepwarm_timer;
+
+unsigned long long sched_clock(void)
+{
+ unsigned long long v;
+
+ if (unlikely(!mtu1_base))
+ return 0;
+
+ v = cnt32_to_63(0xffffffff - readl(mtu1_base + MTU_VAL(0)));
+ return (v * tclk2ns_scale) >> TCLK2NS_SCALE_FACTOR;
+}
+
+static void cnt32_to_63_keepwarm(unsigned long data)
+{
+ mod_timer(&cnt32_to_63_keepwarm_timer, round_jiffies(jiffies + data));
+ (void) sched_clock();
+}
+
+static void __init setup_sched_clock(unsigned long tclk)
+{
+ unsigned long long v;
+ unsigned long data;
+
+ v = NSEC_PER_SEC;
+ v <<= TCLK2NS_SCALE_FACTOR;
+ v += tclk / 2;
+ do_div(v, tclk);
+ /*
+ * We want an even value to automatically clear the top bit
+ * returned by cnt32_to_63() without an additional run time
+ * instruction. So if the LSB is 1 then round it up.
+ */
+ if (v & 1)
+ v++;
+ tclk2ns_scale = v;
+
+ data = (0xffffffffUL / tclk / 2 - 2) * HZ;
+ setup_timer(&cnt32_to_63_keepwarm_timer, cnt32_to_63_keepwarm, data);
+ mod_timer(&cnt32_to_63_keepwarm_timer, round_jiffies(jiffies + data));
+}
+#else
+static void __init setup_sched_clock(unsigned long tclk)
+{
+}
+#endif
+/*
+ * clocksource: the MTU device is a decrementing counters, so we negate
+ * the value being read.
+ */
+static cycle_t u8500_read_timer(struct clocksource *cs)
+{
+ u32 count = readl(mtu1_base + MTU_VAL(0));
+ return ~count;
+}
+/*
+ * Kernel assumes that sched_clock can be called early
+ * but the MTU may not yet be initialized.
+ */
+static cycle_t u8500_read_timer_dummy(struct clocksource *cs)
+{
+ return 0;
+}
+
+void mtu_timer_reset(void);
+
+static void u8500_clocksource_resume(struct clocksource *cs)
+{
+ mtu_timer_reset();
+}
+
+static struct clocksource u8500_clksrc = {
+ .name = "mtu_1",
+ .rating = 120,
+ .read = u8500_read_timer_dummy,
+ .shift = 20,
+ .mask = CLOCKSOURCE_MASK(32),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+ .resume = u8500_clocksource_resume,
+};
+
+#ifdef ARCH_HAS_READ_CURRENT_TIMER
+void mtu_timer_delay_loop(unsigned long loops)
+{
+ unsigned long bclock, now;
+
+ bclock = u8500_read_timer(&u8500_clksrc);
+ do {
+ now = u8500_read_timer(&u8500_clksrc);
+ /* If timer have been cleared (suspend) or wrapped we exit */
+ if (unlikely(now < bclock))
+ return;
+ } while ((now - bclock) < loops);
+}
+
+/* Used to calibrate the delay */
+int read_current_timer(unsigned long *timer_val)
+{
+ *timer_val = u8500_read_timer(&u8500_clksrc);
+ return 0;
+}
+
+#endif
+
+/*
+ * Clockevent device: currently only periodic mode is supported
+ */
+static void u8500_clkevt_mode(enum clock_event_mode mode,
+ struct clock_event_device *dev)
+{
+ unsigned long flags;
+
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ /* enable interrupts -- and count current value? */
+ raw_local_irq_save(flags);
+ writel(1, mtu0_base + MTU_IMSC);
+ raw_local_irq_restore(flags);
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ BUG(); /* Not yet supported */
+ /* FALLTHROUGH */
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ case CLOCK_EVT_MODE_UNUSED:
+ /* disable irq */
+ raw_local_irq_save(flags);
+ writel(0, mtu0_base + MTU_IMSC);
+ raw_local_irq_restore(flags);
+ break;
+ case CLOCK_EVT_MODE_RESUME:
+ break;
+ }
+}
+
+static struct clock_event_device u8500_clkevt = {
+ .name = "mtu_0",
+ .features = CLOCK_EVT_FEAT_PERIODIC,
+ .shift = 32,
+ .rating = 100,
+ .set_mode = u8500_clkevt_mode,
+ .irq = IRQ_MTU0,
+};
+
+/*
+ * IRQ Handler for the timer 0 of the MTU block. The irq is not shared
+ * as we are the only users of mtu0 by now.
+ */
+static irqreturn_t u8500_timer_interrupt(int irq, void *dev_id)
+{
+ /* ack: "interrupt clear register" */
+ writel(1 << 0, mtu0_base + MTU_ICR);
+
+ u8500_clkevt.event_handler(&u8500_clkevt);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Set up timer interrupt, and return the current time in seconds.
+ */
+static struct irqaction u8500_timer_irq = {
+ .name = "MTU Timer Tick",
+ .flags = IRQF_DISABLED | IRQF_TIMER,
+ .handler = u8500_timer_interrupt,
+};
+
+void mtu_timer_reset(void)
+{
+ u32 cr;
+
+ writel(0, mtu0_base + MTU_CR(0)); /* off */
+ writel(0, mtu1_base + MTU_CR(0)); /* off */
+
+ /* Timer: configure load and background-load, and fire it up */
+ writel(u8500_cycle, mtu0_base + MTU_LR(0));
+ writel(u8500_cycle, mtu0_base + MTU_BGLR(0));
+ cr = MTU_CRn_PERIODIC | (MTU_CRn_PRESCALE_1 << 2) | MTU_CRn_32BITS;
+ writel(cr, mtu0_base + MTU_CR(0));
+ writel(cr | MTU_CRn_ENA, mtu0_base + MTU_CR(0));
+
+ /* CS: configure load and background-load, and fire it up */
+ writel(u8500_cycle, mtu1_base + MTU_LR(0));
+ writel(u8500_cycle, mtu1_base + MTU_BGLR(0));
+ cr = (MTU_CRn_PRESCALE_1 << 2) | MTU_CRn_32BITS;
+ writel(cr, mtu1_base + MTU_CR(0));
+ writel(cr | MTU_CRn_ENA, mtu1_base + MTU_CR(0));
+}
+
+void __init mtu_timer_init(void)
+{
+ unsigned long rate;
+ struct clk *clk0;
+ struct clk *clk1;
+ int bits;
+
+ clk0 = clk_get_sys("mtu0", NULL);
+ BUG_ON(IS_ERR(clk0));
+
+ clk1 = clk_get_sys("mtu1", NULL);
+ BUG_ON(IS_ERR(clk1));
+
+ clk_enable(clk0);
+ clk_enable(clk1);
+
+ rate = clk_get_rate(clk0);
+ /*
+ * Set scale and timer for sched_clock
+ */
+ setup_sched_clock(rate);
+ u8500_cycle = (rate + HZ/2) / HZ;
+
+ /* Save global pointer to mtu, used by functions above */
+ if (cpu_is_u5500()) {
+ mtu0_base = __io_address(U5500_MTU0_BASE);
+ mtu1_base = __io_address(U5500_MTU1_BASE);
+ } else if (cpu_is_u8500ed()) {
+ mtu0_base = __io_address(U8500_MTU0_BASE_ED);
+ mtu1_base = __io_address(U8500_MTU1_BASE_ED);
+ } else if (cpu_is_u8500()) {
+ mtu0_base = __io_address(U8500_MTU0_BASE);
+ mtu1_base = __io_address(U8500_MTU1_BASE);
+ } else
+ ux500_unknown_soc();
+
+ /* Init the timer and register clocksource */
+ mtu_timer_reset();
+
+ /* Now the scheduling clock is ready */
+ u8500_clksrc.read = u8500_read_timer;
+ u8500_clksrc.mult = clocksource_hz2mult(rate, u8500_clksrc.shift);
+ bits = 8*sizeof(u8500_count);
+
+ clocksource_register(&u8500_clksrc);
+
+ /* Register irq and clockevents */
+ setup_irq(IRQ_MTU0, &u8500_timer_irq);
+ u8500_clkevt.mult = div_sc(rate, NSEC_PER_SEC, u8500_clkevt.shift);
+ u8500_clkevt.cpumask = cpumask_of(0);
+ clockevents_register_device(&u8500_clkevt);
+#ifdef ARCH_HAS_READ_CURRENT_TIMER
+ set_delay_fn(mtu_timer_delay_loop);
+#endif
+}
+