mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 16:41:39 +00:00
91bc51d8a1
One-shot timer mode on PXA has various bugs which prevent kernels build with NO_HZ enabled booting. They end up spinning on a permanently asserted timer interrupt because we don't properly clear it down - clearing the OIER bit does not stop the pending interrupt status. Fix this in the set_mode handler as well. Moreover, the code which sets the next expiry point may race with the hardware, and we might not set the match register sufficiently in the future. If we encounter that situation, return -ETIME so the generic time code retries. Acked-by: Thomas Gleixner <tglx@linutronix.de> Acked-by: Nicolas Pitre <nico@cam.org> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
266 lines
6.8 KiB
C
266 lines
6.8 KiB
C
/*
|
|
* arch/arm/mach-pxa/time.c
|
|
*
|
|
* PXA clocksource, clockevents, and OST interrupt handlers.
|
|
* Copyright (c) 2007 by Bill Gatliff <bgat@billgatliff.com>.
|
|
*
|
|
* Derived from Nicolas Pitre's PXA timer handler Copyright (c) 2001
|
|
* by MontaVista Software, Inc. (Nico, your code rocks!)
|
|
*
|
|
* 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/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/clockchips.h>
|
|
#include <linux/sched.h>
|
|
|
|
#include <asm/div64.h>
|
|
#include <asm/cnt32_to_63.h>
|
|
#include <asm/mach/irq.h>
|
|
#include <asm/mach/time.h>
|
|
#include <asm/arch/pxa-regs.h>
|
|
#include <asm/mach-types.h>
|
|
|
|
/*
|
|
* This is PXA's sched_clock implementation. This has a resolution
|
|
* of at least 308 ns and a maximum value of 208 days.
|
|
*
|
|
* The return value is guaranteed to be monotonic in that range as
|
|
* long as there is always less than 582 seconds between successive
|
|
* calls to sched_clock() which should always be the case in practice.
|
|
*/
|
|
|
|
#define OSCR2NS_SCALE_FACTOR 10
|
|
|
|
static unsigned long oscr2ns_scale;
|
|
|
|
static void __init set_oscr2ns_scale(unsigned long oscr_rate)
|
|
{
|
|
unsigned long long v = 1000000000ULL << OSCR2NS_SCALE_FACTOR;
|
|
do_div(v, oscr_rate);
|
|
oscr2ns_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 (oscr2ns_scale & 1)
|
|
oscr2ns_scale++;
|
|
}
|
|
|
|
unsigned long long sched_clock(void)
|
|
{
|
|
unsigned long long v = cnt32_to_63(OSCR);
|
|
return (v * oscr2ns_scale) >> OSCR2NS_SCALE_FACTOR;
|
|
}
|
|
|
|
|
|
static irqreturn_t
|
|
pxa_ost0_interrupt(int irq, void *dev_id)
|
|
{
|
|
int next_match;
|
|
struct clock_event_device *c = dev_id;
|
|
|
|
if (c->mode == CLOCK_EVT_MODE_ONESHOT) {
|
|
/* Disarm the compare/match, signal the event. */
|
|
OIER &= ~OIER_E0;
|
|
OSSR = OSSR_M0;
|
|
c->event_handler(c);
|
|
} else if (c->mode == CLOCK_EVT_MODE_PERIODIC) {
|
|
/* Call the event handler as many times as necessary
|
|
* to recover missed events, if any (if we update
|
|
* OSMR0 and OSCR0 is still ahead of us, we've missed
|
|
* the event). As we're dealing with that, re-arm the
|
|
* compare/match for the next event.
|
|
*
|
|
* HACK ALERT:
|
|
*
|
|
* There's a latency between the instruction that
|
|
* writes to OSMR0 and the actual commit to the
|
|
* physical hardware, because the CPU doesn't (have
|
|
* to) run at bus speed, there's a write buffer
|
|
* between the CPU and the bus, etc. etc. So if the
|
|
* target OSCR0 is "very close", to the OSMR0 load
|
|
* value, the update to OSMR0 might not get to the
|
|
* hardware in time and we'll miss that interrupt.
|
|
*
|
|
* To be safe, if the new OSMR0 is "very close" to the
|
|
* target OSCR0 value, we call the event_handler as
|
|
* though the event actually happened. According to
|
|
* Nico's comment in the previous version of this
|
|
* code, experience has shown that 6 OSCR ticks is
|
|
* "very close" but he went with 8. We will use 16,
|
|
* based on the results of testing on PXA270.
|
|
*
|
|
* To be doubly sure, we also tell clkevt via
|
|
* clockevents_register_device() not to ask for
|
|
* anything that might put us "very close".
|
|
*/
|
|
#define MIN_OSCR_DELTA 16
|
|
do {
|
|
OSSR = OSSR_M0;
|
|
next_match = (OSMR0 += LATCH);
|
|
c->event_handler(c);
|
|
} while (((signed long)(next_match - OSCR) <= MIN_OSCR_DELTA)
|
|
&& (c->mode == CLOCK_EVT_MODE_PERIODIC));
|
|
}
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int
|
|
pxa_osmr0_set_next_event(unsigned long delta, struct clock_event_device *dev)
|
|
{
|
|
unsigned long flags, next, oscr;
|
|
|
|
raw_local_irq_save(flags);
|
|
OIER |= OIER_E0;
|
|
next = OSCR + delta;
|
|
OSMR0 = next;
|
|
oscr = OSCR;
|
|
raw_local_irq_restore(flags);
|
|
|
|
return (signed)(next - oscr) <= MIN_OSCR_DELTA ? -ETIME : 0;
|
|
}
|
|
|
|
static void
|
|
pxa_osmr0_set_mode(enum clock_event_mode mode, struct clock_event_device *dev)
|
|
{
|
|
unsigned long irqflags;
|
|
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_PERIODIC:
|
|
raw_local_irq_save(irqflags);
|
|
OSSR = OSSR_M0;
|
|
OIER |= OIER_E0;
|
|
OSMR0 = OSCR + LATCH;
|
|
raw_local_irq_restore(irqflags);
|
|
break;
|
|
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
raw_local_irq_save(irqflags);
|
|
OIER &= ~OIER_E0;
|
|
OSSR = OSSR_M0;
|
|
raw_local_irq_restore(irqflags);
|
|
break;
|
|
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
/* initializing, released, or preparing for suspend */
|
|
raw_local_irq_save(irqflags);
|
|
OIER &= ~OIER_E0;
|
|
OSSR = OSSR_M0;
|
|
raw_local_irq_restore(irqflags);
|
|
break;
|
|
|
|
case CLOCK_EVT_MODE_RESUME:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct clock_event_device ckevt_pxa_osmr0 = {
|
|
.name = "osmr0",
|
|
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
|
|
.shift = 32,
|
|
.rating = 200,
|
|
.cpumask = CPU_MASK_CPU0,
|
|
.set_next_event = pxa_osmr0_set_next_event,
|
|
.set_mode = pxa_osmr0_set_mode,
|
|
};
|
|
|
|
static cycle_t pxa_read_oscr(void)
|
|
{
|
|
return OSCR;
|
|
}
|
|
|
|
static struct clocksource cksrc_pxa_oscr0 = {
|
|
.name = "oscr0",
|
|
.rating = 200,
|
|
.read = pxa_read_oscr,
|
|
.mask = CLOCKSOURCE_MASK(32),
|
|
.shift = 20,
|
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
};
|
|
|
|
static struct irqaction pxa_ost0_irq = {
|
|
.name = "ost0",
|
|
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
|
|
.handler = pxa_ost0_interrupt,
|
|
.dev_id = &ckevt_pxa_osmr0,
|
|
};
|
|
|
|
static void __init pxa_timer_init(void)
|
|
{
|
|
unsigned long clock_tick_rate;
|
|
|
|
OIER = 0;
|
|
OSSR = OSSR_M0 | OSSR_M1 | OSSR_M2 | OSSR_M3;
|
|
|
|
if (cpu_is_pxa21x() || cpu_is_pxa25x())
|
|
clock_tick_rate = 3686400;
|
|
else if (machine_is_mainstone())
|
|
clock_tick_rate = 3249600;
|
|
else
|
|
clock_tick_rate = 3250000;
|
|
|
|
set_oscr2ns_scale(clock_tick_rate);
|
|
|
|
ckevt_pxa_osmr0.mult =
|
|
div_sc(clock_tick_rate, NSEC_PER_SEC, ckevt_pxa_osmr0.shift);
|
|
ckevt_pxa_osmr0.max_delta_ns =
|
|
clockevent_delta2ns(0x7fffffff, &ckevt_pxa_osmr0);
|
|
ckevt_pxa_osmr0.min_delta_ns =
|
|
clockevent_delta2ns(MIN_OSCR_DELTA, &ckevt_pxa_osmr0) + 1;
|
|
|
|
cksrc_pxa_oscr0.mult =
|
|
clocksource_hz2mult(clock_tick_rate, cksrc_pxa_oscr0.shift);
|
|
|
|
setup_irq(IRQ_OST0, &pxa_ost0_irq);
|
|
|
|
clocksource_register(&cksrc_pxa_oscr0);
|
|
clockevents_register_device(&ckevt_pxa_osmr0);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static unsigned long osmr[4], oier;
|
|
|
|
static void pxa_timer_suspend(void)
|
|
{
|
|
osmr[0] = OSMR0;
|
|
osmr[1] = OSMR1;
|
|
osmr[2] = OSMR2;
|
|
osmr[3] = OSMR3;
|
|
oier = OIER;
|
|
}
|
|
|
|
static void pxa_timer_resume(void)
|
|
{
|
|
OSMR0 = osmr[0];
|
|
OSMR1 = osmr[1];
|
|
OSMR2 = osmr[2];
|
|
OSMR3 = osmr[3];
|
|
OIER = oier;
|
|
|
|
/*
|
|
* OSCR0 is the system timer, which has to increase
|
|
* monotonically until it rolls over in hardware. The value
|
|
* (OSMR0 - LATCH) is OSCR0 at the most recent system tick,
|
|
* which is a handy value to restore to OSCR0.
|
|
*/
|
|
OSCR = OSMR0 - LATCH;
|
|
}
|
|
#else
|
|
#define pxa_timer_suspend NULL
|
|
#define pxa_timer_resume NULL
|
|
#endif
|
|
|
|
struct sys_timer pxa_timer = {
|
|
.init = pxa_timer_init,
|
|
.suspend = pxa_timer_suspend,
|
|
.resume = pxa_timer_resume,
|
|
};
|