/* * linux/arch/arm/mach-mmp/time.c * * Support for clocksource and clockevents * * Copyright (C) 2008 Marvell International Ltd. * All rights reserved. * * 2008-04-11: Jason Chagas * 2008-10-08: Bin Yang * * The timers module actually includes three timers, each timer with upto * three match comparators. Timer #0 is used here in free-running mode as * the clock source, and match comparator #1 used as clock event device. * * 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 #include #include #include #include #include #include #include #include #include #include #include "clock.h" #define TIMERS_VIRT_BASE TIMERS1_VIRT_BASE #define MAX_DELTA (0xfffffffe) #define MIN_DELTA (16) #define TCR2NS_SCALE_FACTOR 10 static unsigned long tcr2ns_scale; static void __init set_tcr2ns_scale(unsigned long tcr_rate) { unsigned long long v = 1000000000ULL << TCR2NS_SCALE_FACTOR; do_div(v, tcr_rate); tcr2ns_scale = v; /* * 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 (tcr2ns_scale & 1) tcr2ns_scale++; } /* * FIXME: the timer needs some delay to stablize the counter capture */ static inline uint32_t timer_read(void) { int delay = 100; __raw_writel(1, TIMERS_VIRT_BASE + TMR_CVWR(0)); while (delay--) cpu_relax(); return __raw_readl(TIMERS_VIRT_BASE + TMR_CVWR(0)); } unsigned long long sched_clock(void) { unsigned long long v = cnt32_to_63(timer_read()); return (v * tcr2ns_scale) >> TCR2NS_SCALE_FACTOR; } static irqreturn_t timer_interrupt(int irq, void *dev_id) { struct clock_event_device *c = dev_id; /* disable and clear pending interrupt status */ __raw_writel(0x0, TIMERS_VIRT_BASE + TMR_IER(0)); __raw_writel(0x1, TIMERS_VIRT_BASE + TMR_ICR(0)); c->event_handler(c); return IRQ_HANDLED; } static int timer_set_next_event(unsigned long delta, struct clock_event_device *dev) { unsigned long flags, next; local_irq_save(flags); /* clear pending interrupt status and enable */ __raw_writel(0x01, TIMERS_VIRT_BASE + TMR_ICR(0)); __raw_writel(0x01, TIMERS_VIRT_BASE + TMR_IER(0)); next = timer_read() + delta; __raw_writel(next, TIMERS_VIRT_BASE + TMR_TN_MM(0, 0)); local_irq_restore(flags); return 0; } static void timer_set_mode(enum clock_event_mode mode, struct clock_event_device *dev) { unsigned long flags; local_irq_save(flags); switch (mode) { case CLOCK_EVT_MODE_ONESHOT: case CLOCK_EVT_MODE_UNUSED: case CLOCK_EVT_MODE_SHUTDOWN: /* disable the matching interrupt */ __raw_writel(0x00, TIMERS_VIRT_BASE + TMR_IER(0)); break; case CLOCK_EVT_MODE_RESUME: case CLOCK_EVT_MODE_PERIODIC: break; } local_irq_restore(flags); } static struct clock_event_device ckevt = { .name = "clockevent", .features = CLOCK_EVT_FEAT_ONESHOT, .shift = 32, .rating = 200, .set_next_event = timer_set_next_event, .set_mode = timer_set_mode, }; static cycle_t clksrc_read(void) { return timer_read(); } static struct clocksource cksrc = { .name = "clocksource", .shift = 20, .rating = 200, .read = clksrc_read, .mask = CLOCKSOURCE_MASK(32), .flags = CLOCK_SOURCE_IS_CONTINUOUS, }; static void __init timer_config(void) { uint32_t ccr = __raw_readl(TIMERS_VIRT_BASE + TMR_CCR); uint32_t cer = __raw_readl(TIMERS_VIRT_BASE + TMR_CER); uint32_t cmr = __raw_readl(TIMERS_VIRT_BASE + TMR_CMR); __raw_writel(cer & ~0x1, TIMERS_VIRT_BASE + TMR_CER); /* disable */ ccr &= TMR_CCR_CS_0(0x3); __raw_writel(ccr, TIMERS_VIRT_BASE + TMR_CCR); /* free-running mode */ __raw_writel(cmr | 0x01, TIMERS_VIRT_BASE + TMR_CMR); __raw_writel(0x0, TIMERS_VIRT_BASE + TMR_PLCR(0)); /* free-running */ __raw_writel(0x7, TIMERS_VIRT_BASE + TMR_ICR(0)); /* clear status */ __raw_writel(0x0, TIMERS_VIRT_BASE + TMR_IER(0)); /* enable timer counter */ __raw_writel(cer | 0x01, TIMERS_VIRT_BASE + TMR_CER); } static struct irqaction timer_irq = { .name = "timer", .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, .handler = timer_interrupt, .dev_id = &ckevt, }; void __init timer_init(int irq) { timer_config(); set_tcr2ns_scale(CLOCK_TICK_RATE); ckevt.mult = div_sc(CLOCK_TICK_RATE, NSEC_PER_SEC, ckevt.shift); ckevt.max_delta_ns = clockevent_delta2ns(MAX_DELTA, &ckevt); ckevt.min_delta_ns = clockevent_delta2ns(MIN_DELTA, &ckevt); ckevt.cpumask = cpumask_of(0); cksrc.mult = clocksource_hz2mult(CLOCK_TICK_RATE, cksrc.shift); setup_irq(irq, &timer_irq); clocksource_register(&cksrc); clockevents_register_device(&ckevt); }