[ARM] tegra: timer: Add suspend wakeup and 32khz timers
authorGary King <gking@nvidia.com>
Fri, 16 Apr 2010 21:49:19 +0000 (14:49 -0700)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:26:24 +0000 (16:26 -0700)
the LP2 idle state can not be woken by the internal ARM timers,
so reserve the last APB system timer for use as an LP2 wakeup
trigger

Signed-off-by: Colin Cross <ccross@android.com>
arch/arm/mach-tegra/timer.c

index 363217308285c38cefa78ccee3a072fcdc680b88..cc9ec7020533efb7ad8a4c29da248e99acf4fc16 100644 (file)
 #include "board.h"
 #include "clock.h"
 
+#define RTC_SECONDS            0x08
+#define RTC_SHADOW_SECONDS     0x0c
+#define RTC_MILLISECONDS       0x10
+
 #define TIMERUS_CNTR_1US 0x10
 #define TIMERUS_USEC_CFG 0x14
 #define TIMERUS_CNTR_FREEZE 0x4c
 #define TIMER_PTV 0x0
 #define TIMER_PCR 0x4
 
-struct tegra_timer;
-
-static void __iomem *timer_reg_base = IO_ADDRESS(TEGRA_TMR1_BASE);
+static void __iomem *timer_base = IO_ADDRESS(TEGRA_TMR1_BASE);
+static void __iomem *rtc_base = IO_ADDRESS(TEGRA_RTC_BASE);
 
 #define timer_writel(value, reg) \
-       __raw_writel(value, (u32)timer_reg_base + (reg))
+       __raw_writel(value, (u32)timer_base + (reg))
 #define timer_readl(reg) \
-       __raw_readl((u32)timer_reg_base + (reg))
+       __raw_readl((u32)timer_base + (reg))
 
 static int tegra_timer_set_next_event(unsigned long cycles,
                                         struct clock_event_device *evt)
@@ -91,9 +94,29 @@ static void tegra_timer_set_mode(enum clock_event_mode mode,
        }
 }
 
-static cycle_t tegra_clocksource_read(struct clocksource *cs)
+static u64 tegra_us_clocksource_offset;
+static u64 tegra_us_resume_offset;
+static cycle_t tegra_clocksource_us_read(struct clocksource *cs)
 {
-       return cnt32_to_63(timer_readl(TIMERUS_CNTR_1US));
+       return tegra_us_clocksource_offset +
+               cnt32_to_63(timer_readl(TIMERUS_CNTR_1US));
+}
+
+void tegra_clocksource_us_suspend(struct clocksource *cs)
+{
+       tegra_us_resume_offset = tegra_clocksource_us_read(cs);
+}
+
+void tegra_clocksource_us_resume(struct clocksource *cs)
+{
+       tegra_us_clocksource_offset = tegra_us_resume_offset;
+}
+
+static cycle_t tegra_clocksource_32k_read(struct clocksource *cs)
+{
+       u32 ms = readl(rtc_base + RTC_MILLISECONDS);
+       u32 s = readl(rtc_base + RTC_SHADOW_SECONDS);
+       return (u64)s * 1000 + ms;
 }
 
 static struct clock_event_device tegra_clockevent = {
@@ -104,18 +127,54 @@ static struct clock_event_device tegra_clockevent = {
        .set_mode       = tegra_timer_set_mode,
 };
 
-static struct clocksource tegra_clocksource = {
+static struct clocksource tegra_clocksource_us = {
        .name   = "timer_us",
        .rating = 300,
-       .read   = tegra_clocksource_read,
+       .read   = tegra_clocksource_us_read,
+       .suspend= tegra_clocksource_us_suspend,
+       .resume = tegra_clocksource_us_resume,
+       .mask   = 0x7FFFFFFFFFFFFFFFULL,
+       .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static struct clocksource tegra_clocksource_32k = {
+       .name   = "rtc_32k",
+       .rating = 100,
+       .read   = tegra_clocksource_32k_read,
        .mask   = 0x7FFFFFFFFFFFFFFFULL,
        .flags  = CLOCK_SOURCE_IS_CONTINUOUS,
 };
 
 unsigned long long sched_clock(void)
 {
-       return clocksource_cyc2ns(tegra_clocksource.read(&tegra_clocksource),
-               tegra_clocksource.mult, tegra_clocksource.shift);
+       return tegra_clocksource_us.read(&tegra_clocksource_us) * 1000;
+}
+
+/**
+ * read_persistent_clock -  Return time from a persistent clock.
+ *
+ * Reads the time from a source which isn't disabled during PM, the
+ * 32k sync timer.  Convert the cycles elapsed since last read into
+ * nsecs and adds to a monotonically increasing timespec.
+ */
+static struct timespec persistent_ts;
+static cycles_t cycles, last_cycles;
+void read_persistent_clock(struct timespec *ts)
+{
+       unsigned long long nsecs;
+       cycles_t delta;
+       struct timespec *tsp = &persistent_ts;
+
+       last_cycles = cycles;
+       cycles = tegra_clocksource_32k.read(&tegra_clocksource_32k);
+       delta = cycles - last_cycles;
+
+       nsecs = clocksource_cyc2ns(delta,
+                                  tegra_clocksource_32k.mult,
+                                  tegra_clocksource_32k.shift);
+
+       timespec_add_ns(tsp, nsecs);
+       *ts = *tsp;
 }
 
 static irqreturn_t tegra_timer_interrupt(int irq, void *dev_id)
@@ -134,6 +193,20 @@ static struct irqaction tegra_timer_irq = {
        .irq            = INT_TMR3,
 };
 
+static irqreturn_t tegra_lp2wake_interrupt(int irq, void *dev_id)
+{
+       timer_writel(1<<30, TIMER4_BASE + TIMER_PCR);
+       return IRQ_HANDLED;
+}
+
+static struct irqaction tegra_lp2wake_irq = {
+       .name           = "timer_lp2wake",
+       .flags          = IRQF_DISABLED,
+       .handler        = tegra_lp2wake_interrupt,
+       .dev_id         = NULL,
+       .irq            = INT_TMR4,
+};
+
 static void __init tegra_init_timer(void)
 {
        unsigned long rate = clk_measure_input_freq();
@@ -160,8 +233,13 @@ static void __init tegra_init_timer(void)
                WARN(1, "Unknown clock rate");
        }
 
-       if (clocksource_register_hz(&tegra_clocksource, 1000000)) {
-               printk(KERN_ERR "Failed to register clocksource\n");
+       if (clocksource_register_hz(&tegra_clocksource_us, 1000000)) {
+               printk(KERN_ERR "Failed to register us clocksource\n");
+               BUG();
+       }
+
+       if (clocksource_register_hz(&tegra_clocksource_32k, 1000)) {
+               printk(KERN_ERR "Failed to register 32k clocksource\n");
                BUG();
        }
 
@@ -171,6 +249,12 @@ static void __init tegra_init_timer(void)
                BUG();
        }
 
+       ret = setup_irq(tegra_lp2wake_irq.irq, &tegra_lp2wake_irq);
+       if (ret) {
+               printk(KERN_ERR "Failed to register LP2 timer IRQ: %d\n", ret);
+               BUG();
+       }
+
        clockevents_calc_mult_shift(&tegra_clockevent, 1000000, 5);
        tegra_clockevent.max_delta_ns =
                clockevent_delta2ns(0x1fffffff, &tegra_clockevent);
@@ -186,3 +270,29 @@ static void __init tegra_init_timer(void)
 struct sys_timer tegra_timer = {
        .init = tegra_init_timer,
 };
+
+void tegra_lp2_set_trigger(unsigned long cycles)
+{
+       timer_writel(0, TIMER4_BASE + TIMER_PTV);
+       if (cycles) {
+               u32 reg = 0x80000000ul | min(0x1ffffffful, cycles);
+               timer_writel(reg, TIMER4_BASE + TIMER_PTV);
+       }
+}
+EXPORT_SYMBOL(tegra_lp2_set_trigger);
+
+unsigned long tegra_lp2_timer_remain(void)
+{
+       return timer_readl(TIMER4_BASE + TIMER_PCR) & 0x1ffffffful;
+}
+
+static u32 usec_config;
+void tegra_timer_suspend(void)
+{
+       usec_config = timer_readl(TIMERUS_USEC_CFG);
+}
+
+void tegra_timer_resume(void)
+{
+       timer_writel(usec_config, TIMERUS_USEC_CFG);
+}