5aa85c9fc4
The R4000 and R4400 have an errata where if the cp0 count register is read in the exact moment when it matches the compare register no interrupt will be generated. This bug may be triggered if the cp0 count register is being used as clocksource and the compare interrupt as clockevent. So a simple workaround is to avoid using the compare for both facilities on the affected CPUs. This is different from the workaround suggested in the old errata documents; at some opportunity probably the official version should be implemented and tested. Another thing to find out is which processor versions exactly are affected. I only have errata documents upto R4400 V3.0 available so for the moment the code treats all R4000 and R4400 as broken. This is potencially a problem for some machines that have no other decent clocksource available; this workaround will cause them to fall back to another clocksource, worst case the "jiffies" source.
193 lines
4.2 KiB
C
193 lines
4.2 KiB
C
/*
|
|
* Copyright 2001 MontaVista Software Inc.
|
|
* Author: Jun Sun, jsun@mvista.com or jsun@junsun.net
|
|
* Copyright (c) 2003, 2004 Maciej W. Rozycki
|
|
*
|
|
* Common time service routines for MIPS machines.
|
|
*
|
|
* 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.
|
|
*/
|
|
#include <linux/bug.h>
|
|
#include <linux/clockchips.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/param.h>
|
|
#include <linux/time.h>
|
|
#include <linux/timex.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <asm/cpu-features.h>
|
|
#include <asm/div64.h>
|
|
#include <asm/smtc_ipi.h>
|
|
#include <asm/time.h>
|
|
|
|
/*
|
|
* forward reference
|
|
*/
|
|
DEFINE_SPINLOCK(rtc_lock);
|
|
EXPORT_SYMBOL(rtc_lock);
|
|
|
|
int __weak rtc_mips_set_time(unsigned long sec)
|
|
{
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(rtc_mips_set_time);
|
|
|
|
int __weak rtc_mips_set_mmss(unsigned long nowtime)
|
|
{
|
|
return rtc_mips_set_time(nowtime);
|
|
}
|
|
|
|
int update_persistent_clock(struct timespec now)
|
|
{
|
|
return rtc_mips_set_mmss(now.tv_sec);
|
|
}
|
|
|
|
/*
|
|
* High precision timer functions for a R4k-compatible timer.
|
|
*/
|
|
static cycle_t c0_hpt_read(void)
|
|
{
|
|
return read_c0_count();
|
|
}
|
|
|
|
int (*mips_timer_state)(void);
|
|
|
|
int null_perf_irq(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
EXPORT_SYMBOL(null_perf_irq);
|
|
|
|
int (*perf_irq)(void) = null_perf_irq;
|
|
|
|
EXPORT_SYMBOL(perf_irq);
|
|
|
|
/*
|
|
* time_init() - it does the following things.
|
|
*
|
|
* 1) plat_time_init() -
|
|
* a) (optional) set up RTC routines,
|
|
* b) (optional) calibrate and set the mips_hpt_frequency
|
|
* (only needed if you intended to use cpu counter as timer interrupt
|
|
* source)
|
|
* 2) calculate a couple of cached variables for later usage
|
|
*/
|
|
|
|
unsigned int mips_hpt_frequency;
|
|
|
|
static struct clocksource clocksource_mips = {
|
|
.name = "MIPS",
|
|
.read = c0_hpt_read,
|
|
.mask = CLOCKSOURCE_MASK(32),
|
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
};
|
|
|
|
void __init clocksource_set_clock(struct clocksource *cs, unsigned int clock)
|
|
{
|
|
u64 temp;
|
|
u32 shift;
|
|
|
|
/* Find a shift value */
|
|
for (shift = 32; shift > 0; shift--) {
|
|
temp = (u64) NSEC_PER_SEC << shift;
|
|
do_div(temp, clock);
|
|
if ((temp >> 32) == 0)
|
|
break;
|
|
}
|
|
cs->shift = shift;
|
|
cs->mult = (u32) temp;
|
|
}
|
|
|
|
void __cpuinit clockevent_set_clock(struct clock_event_device *cd,
|
|
unsigned int clock)
|
|
{
|
|
u64 temp;
|
|
u32 shift;
|
|
|
|
/* Find a shift value */
|
|
for (shift = 32; shift > 0; shift--) {
|
|
temp = (u64) clock << shift;
|
|
do_div(temp, NSEC_PER_SEC);
|
|
if ((temp >> 32) == 0)
|
|
break;
|
|
}
|
|
cd->shift = shift;
|
|
cd->mult = (u32) temp;
|
|
}
|
|
|
|
static void __init init_mips_clocksource(void)
|
|
{
|
|
/* Calclate a somewhat reasonable rating value */
|
|
clocksource_mips.rating = 200 + mips_hpt_frequency / 10000000;
|
|
|
|
clocksource_set_clock(&clocksource_mips, mips_hpt_frequency);
|
|
|
|
clocksource_register(&clocksource_mips);
|
|
}
|
|
|
|
void __init __weak plat_time_init(void)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* This function exists in order to cause an error due to a duplicate
|
|
* definition if platform code should have its own implementation. The hook
|
|
* to use instead is plat_time_init. plat_time_init does not receive the
|
|
* irqaction pointer argument anymore. This is because any function which
|
|
* initializes an interrupt timer now takes care of its own request_irq rsp.
|
|
* setup_irq calls and each clock_event_device should use its own
|
|
* struct irqrequest.
|
|
*/
|
|
void __init plat_timer_setup(void)
|
|
{
|
|
BUG();
|
|
}
|
|
|
|
static __init int cpu_has_mfc0_count_bug(void)
|
|
{
|
|
switch (current_cpu_type()) {
|
|
case CPU_R4000PC:
|
|
case CPU_R4000SC:
|
|
case CPU_R4000MC:
|
|
/*
|
|
* V3.0 is documented as suffering from the mfc0 from count bug.
|
|
* Afaik this is the last version of the R4000. Later versions
|
|
* were marketed as R4400.
|
|
*/
|
|
return 1;
|
|
|
|
case CPU_R4400PC:
|
|
case CPU_R4400SC:
|
|
case CPU_R4400MC:
|
|
/*
|
|
* The published errata for the R4400 upto 3.0 say the CPU
|
|
* has the mfc0 from count bug.
|
|
*/
|
|
if ((current_cpu_data.processor_id & 0xff) <= 0x30)
|
|
return 1;
|
|
|
|
/*
|
|
* I don't have erratas for newer R4400 so be paranoid.
|
|
*/
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __init time_init(void)
|
|
{
|
|
plat_time_init();
|
|
|
|
if (mips_clockevent_init() || !cpu_has_mfc0_count_bug())
|
|
init_mips_clocksource();
|
|
}
|