diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2007-10-11 23:46:09 +0100 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2007-10-11 23:46:09 +0100 |
commit | 7bcf7717b6a047c272410d0cd00213185fe6b99d (patch) | |
tree | 81c5d6bbc2130815713e22bb5408ea80b6e1c499 /arch/mips/kernel/time.c | |
parent | 91a2fcc88634663e9e13dcdfad0e4a860e64aeee (diff) | |
download | op-kernel-dev-7bcf7717b6a047c272410d0cd00213185fe6b99d.zip op-kernel-dev-7bcf7717b6a047c272410d0cd00213185fe6b99d.tar.gz |
[MIPS] Implement clockevents for R4000-style cp0 count/compare interrupt
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/kernel/time.c')
-rw-r--r-- | arch/mips/kernel/time.c | 256 |
1 files changed, 118 insertions, 138 deletions
diff --git a/arch/mips/kernel/time.c b/arch/mips/kernel/time.c index d23e682..3598884 100644 --- a/arch/mips/kernel/time.c +++ b/arch/mips/kernel/time.c @@ -11,6 +11,7 @@ * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. */ +#include <linux/clockchips.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/init.h> @@ -34,6 +35,8 @@ #include <asm/sections.h> #include <asm/time.h> +#include <irq.h> + /* * The integer part of the number of usecs per jiffy is taken from tick, * but the fractional part is not recorded, so we calculate it using the @@ -70,10 +73,6 @@ int update_persistent_clock(struct timespec now) /* how many counter cycles in a jiffy */ static unsigned long cycles_per_jiffy __read_mostly; -/* expirelo is the count value for next CPU timer interrupt */ -static unsigned int expirelo; - - /* * Null timer ack for systems not needing one (e.g. i8254). */ @@ -92,18 +91,7 @@ static cycle_t null_hpt_read(void) */ static void c0_timer_ack(void) { - unsigned int count; - - /* Ack this timer interrupt and set the next one. */ - expirelo += cycles_per_jiffy; - write_c0_compare(expirelo); - - /* Check to see if we have missed any timer interrupts. */ - while (((count = read_c0_count()) - expirelo) < 0x7fffffff) { - /* missed_timer_count++; */ - expirelo = count + cycles_per_jiffy; - write_c0_compare(expirelo); - } + write_c0_compare(read_c0_compare()); } /* @@ -114,13 +102,6 @@ static cycle_t c0_hpt_read(void) return read_c0_count(); } -/* For use both as a high precision timer and an interrupt source. */ -static void __init c0_hpt_timer_init(void) -{ - expirelo = read_c0_count() + cycles_per_jiffy; - write_c0_compare(expirelo); -} - int (*mips_timer_state)(void); void (*mips_timer_ack)(void); @@ -140,35 +121,6 @@ void local_timer_interrupt(int irq, void *dev_id) update_process_times(user_mode(get_irq_regs())); } -/* - * High-level timer interrupt service routines. This function - * is set as irqaction->handler and is invoked through do_IRQ. - */ -static irqreturn_t timer_interrupt(int irq, void *dev_id) -{ - write_seqlock(&xtime_lock); - - mips_timer_ack(); - - /* - * call the generic timer interrupt handling - */ - do_timer(1); - - write_sequnlock(&xtime_lock); - - /* - * In UP mode, we call local_timer_interrupt() to do profiling - * and process accouting. - * - * In SMP mode, local_timer_interrupt() is invoked by appropriate - * low-level local timer interrupt handler. - */ - local_timer_interrupt(irq, dev_id); - - return IRQ_HANDLED; -} - int null_perf_irq(void) { return 0; @@ -209,81 +161,6 @@ static inline int handle_perf_irq (int r2) !r2; } -void ll_timer_interrupt(int irq, void *dev_id) -{ - int cpu = smp_processor_id(); - -#ifdef CONFIG_MIPS_MT_SMTC - /* - * In an SMTC system, one Count/Compare set exists per VPE. - * Which TC within a VPE gets the interrupt is essentially - * random - we only know that it shouldn't be one with - * IXMT set. Whichever TC gets the interrupt needs to - * send special interprocessor interrupts to the other - * TCs to make sure that they schedule, etc. - * - * That code is specific to the SMTC kernel, not to - * the a particular platform, so it's invoked from - * the general MIPS timer_interrupt routine. - */ - - /* - * We could be here due to timer interrupt, - * perf counter overflow, or both. - */ - (void) handle_perf_irq(1); - - if (read_c0_cause() & (1 << 30)) { - /* - * There are things we only want to do once per tick - * in an "MP" system. One TC of each VPE will take - * the actual timer interrupt. The others will get - * timer broadcast IPIs. We use whoever it is that takes - * the tick on VPE 0 to run the full timer_interrupt(). - */ - if (cpu_data[cpu].vpe_id == 0) { - timer_interrupt(irq, NULL); - } else { - write_c0_compare(read_c0_count() + - (mips_hpt_frequency/HZ)); - local_timer_interrupt(irq, dev_id); - } - smtc_timer_broadcast(cpu_data[cpu].vpe_id); - } -#else /* CONFIG_MIPS_MT_SMTC */ - int r2 = cpu_has_mips_r2; - - if (handle_perf_irq(r2)) - return; - - if (r2 && ((read_c0_cause() & (1 << 30)) == 0)) - return; - - if (cpu == 0) { - /* - * CPU 0 handles the global timer interrupt job and process - * accounting resets count/compare registers to trigger next - * timer int. - */ - timer_interrupt(irq, NULL); - } else { - /* Everyone else needs to reset the timer int here as - ll_local_timer_interrupt doesn't */ - /* - * FIXME: need to cope with counter underflow. - * More support needs to be added to kernel/time for - * counter/timer interrupts on multiple CPU's - */ - write_c0_compare(read_c0_count() + (mips_hpt_frequency/HZ)); - - /* - * Other CPUs should do profiling and process accounting - */ - local_timer_interrupt(irq, dev_id); - } -#endif /* CONFIG_MIPS_MT_SMTC */ -} - /* * time_init() - it does the following things. * @@ -301,12 +178,6 @@ void ll_timer_interrupt(int irq, void *dev_id) unsigned int mips_hpt_frequency; -static struct irqaction timer_irqaction = { - .handler = timer_interrupt, - .flags = IRQF_DISABLED | IRQF_PERCPU, - .name = "timer", -}; - static unsigned int __init calibrate_hpt(void) { cycle_t frequency, hpt_start, hpt_end, hpt_count, hz; @@ -355,6 +226,65 @@ struct clocksource clocksource_mips = { .flags = CLOCK_SOURCE_IS_CONTINUOUS, }; +static int mips_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + unsigned int cnt; + + cnt = read_c0_count(); + cnt += delta; + write_c0_compare(cnt); + + return ((long)(read_c0_count() - cnt ) > 0) ? -ETIME : 0; +} + +static void mips_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + /* Nothing to do ... */ +} + +struct clock_event_device mips_clockevent; + +static struct clock_event_device *global_cd[NR_CPUS]; +static int cp0_timer_irq_installed; + +static irqreturn_t timer_interrupt(int irq, void *dev_id) +{ + const int r2 = cpu_has_mips_r2; + struct clock_event_device *cd; + int cpu = smp_processor_id(); + + /* + * Suckage alert: + * Before R2 of the architecture there was no way to see if a + * performance counter interrupt was pending, so we have to run + * the performance counter interrupt handler anyway. + */ + if (handle_perf_irq(r2)) + goto out; + + /* + * The same applies to performance counter interrupts. But with the + * above we now know that the reason we got here must be a timer + * interrupt. Being the paranoiacs we are we check anyway. + */ + if (!r2 || (read_c0_cause() & (1 << 30))) { + c0_timer_ack(); + cd = global_cd[cpu]; + cd->event_handler(cd); + } + +out: + return IRQ_HANDLED; +} + +static struct irqaction timer_irqaction = { + .handler = timer_interrupt, + .flags = IRQF_DISABLED | IRQF_PERCPU, + .name = "timer", +}; + static void __init init_mips_clocksource(void) { u64 temp; @@ -382,6 +312,56 @@ void __init __weak plat_time_init(void) { } +void __init __weak plat_timer_setup(struct irqaction *irq) +{ +} + +void __cpuinit mips_clockevent_init(void) +{ + uint64_t mips_freq = mips_hpt_frequency; + unsigned int cpu = smp_processor_id(); + struct clock_event_device *cd; + unsigned int irq = MIPS_CPU_IRQ_BASE + 7; + + if (!cpu_has_counter) + return; + + if (cpu == 0) + cd = &mips_clockevent; + else + cd = kzalloc(sizeof(*cd), GFP_ATOMIC); + if (!cd) + return; /* We're probably roadkill ... */ + + cd->name = "MIPS"; + cd->features = CLOCK_EVT_FEAT_ONESHOT; + + /* Calculate the min / max delta */ + cd->mult = div_sc((unsigned long) mips_freq, NSEC_PER_SEC, 32); + cd->shift = 32; + cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd); + cd->min_delta_ns = clockevent_delta2ns(0x30, cd); + + cd->rating = 300; + cd->irq = irq; + cd->cpumask = cpumask_of_cpu(cpu); + cd->set_next_event = mips_next_event; + cd->set_mode = mips_set_mode; + + global_cd[cpu] = cd; + clockevents_register_device(cd); + + if (!cp0_timer_irq_installed) { +#ifdef CONFIG_MIPS_MT_SMTC +#define CPUCTR_IMASKBIT (0x100 << cp0_compare_irq) + setup_irq_smtc(irq, &timer_irqaction, CPUCTR_IMASKBIT); +#else + setup_irq(irq, &timer_irqaction); +#endif /* CONFIG_MIPS_MT_SMTC */ + cp0_timer_irq_installed = 1; + } +} + void __init time_init(void) { plat_time_init(); @@ -407,11 +387,6 @@ void __init time_init(void) /* Calculate cache parameters. */ cycles_per_jiffy = (mips_hpt_frequency + HZ / 2) / HZ; - /* - * This sets up the high precision - * timer for the first interrupt. - */ - c0_hpt_timer_init(); } } if (!mips_hpt_frequency) @@ -421,6 +396,10 @@ void __init time_init(void) printk("Using %u.%03u MHz high precision timer.\n", ((mips_hpt_frequency + 500) / 1000) / 1000, ((mips_hpt_frequency + 500) / 1000) % 1000); + +#ifdef CONFIG_IRQ_CPU + setup_irq(MIPS_CPU_IRQ_BASE + 7, &timer_irqaction); +#endif } if (!mips_timer_ack) @@ -441,4 +420,5 @@ void __init time_init(void) plat_timer_setup(&timer_irqaction); init_mips_clocksource(); + mips_clockevent_init(); } |