blob: 2830f656fe2fbdc51228e6b1f0a5a1c7942bddbd [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * Carsten Langgaard, carstenl@mips.com
3 * Copyright (C) 1999,2000 MIPS Technologies, Inc. All rights reserved.
4 *
5 * This program is free software; you can distribute it and/or modify it
6 * under the terms of the GNU General Public License (Version 2) as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
17 *
18 * Setting up the clock on the MIPS boards.
19 */
20
21#include <linux/types.h>
22#include <linux/config.h>
23#include <linux/init.h>
24#include <linux/kernel_stat.h>
25#include <linux/sched.h>
26#include <linux/spinlock.h>
27#include <linux/interrupt.h>
28#include <linux/time.h>
29#include <linux/timex.h>
30#include <linux/mc146818rtc.h>
31
32#include <asm/mipsregs.h>
33#include <asm/ptrace.h>
Ralf Baechlee01402b2005-07-14 15:57:16 +000034#include <asm/hardirq.h>
35#include <asm/irq.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070036#include <asm/div64.h>
37#include <asm/cpu.h>
38#include <asm/time.h>
39#include <asm/mc146818-time.h>
Ralf Baechlee01402b2005-07-14 15:57:16 +000040#include <asm/msc01_ic.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070041
42#include <asm/mips-boards/generic.h>
43#include <asm/mips-boards/prom.h>
Ralf Baechlee01402b2005-07-14 15:57:16 +000044#include <asm/mips-boards/maltaint.h>
45#include <asm/mc146818-time.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070046
47unsigned long cpu_khz;
48
Linus Torvalds1da177e2005-04-16 15:20:36 -070049#if defined(CONFIG_MIPS_ATLAS)
50static char display_string[] = " LINUX ON ATLAS ";
51#endif
52#if defined(CONFIG_MIPS_MALTA)
53static char display_string[] = " LINUX ON MALTA ";
54#endif
55#if defined(CONFIG_MIPS_SEAD)
56static char display_string[] = " LINUX ON SEAD ";
57#endif
58static unsigned int display_count = 0;
59#define MAX_DISPLAY_COUNT (sizeof(display_string) - 8)
60
Linus Torvalds1da177e2005-04-16 15:20:36 -070061static unsigned int timer_tick_count=0;
Ralf Baechlee01402b2005-07-14 15:57:16 +000062static int mips_cpu_timer_irq;
Linus Torvalds1da177e2005-04-16 15:20:36 -070063
Ralf Baechle340ee4b2005-08-17 17:44:08 +000064static inline void scroll_display_message(void)
65{
66 if ((timer_tick_count++ % HZ) == 0) {
67 mips_display_message(&display_string[display_count++]);
68 if (display_count == MAX_DISPLAY_COUNT)
69 display_count = 0;
70 }
71}
72
Ralf Baechlee01402b2005-07-14 15:57:16 +000073static void mips_timer_dispatch (struct pt_regs *regs)
Linus Torvalds1da177e2005-04-16 15:20:36 -070074{
Ralf Baechlee01402b2005-07-14 15:57:16 +000075 do_IRQ (mips_cpu_timer_irq, regs);
76}
77
78irqreturn_t mips_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
79{
Ralf Baechle340ee4b2005-08-17 17:44:08 +000080 int cpu = smp_processor_id();
81
82 if (cpu == 0) {
83 /*
84 * CPU 0 handles the global timer interrupt job and process accounting
85 * resets count/compare registers to trigger next timer int.
86 */
Ralf Baechle11e6df62005-12-09 12:09:22 +000087 timer_interrupt(irq, dev_id, regs);
Ralf Baechle340ee4b2005-08-17 17:44:08 +000088 scroll_display_message();
Ralf Baechle11e6df62005-12-09 12:09:22 +000089 } else {
Ralf Baechle340ee4b2005-08-17 17:44:08 +000090 /* Everyone else needs to reset the timer int here as
91 ll_local_timer_interrupt doesn't */
92 /*
93 * FIXME: need to cope with counter underflow.
94 * More support needs to be added to kernel/time for
95 * counter/timer interrupts on multiple CPU's
96 */
97 write_c0_compare (read_c0_count() + (mips_hpt_frequency/HZ));
98 /*
99 * other CPUs should do profiling and process accounting
100 */
101 local_timer_interrupt (irq, dev_id, regs);
102 }
103
104 return IRQ_HANDLED;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700105}
106
107/*
108 * Estimate CPU frequency. Sets mips_counter_frequency as a side-effect
109 */
110static unsigned int __init estimate_cpu_frequency(void)
111{
112 unsigned int prid = read_c0_prid() & 0xffff00;
113 unsigned int count;
114
115#ifdef CONFIG_MIPS_SEAD
116 /*
117 * The SEAD board doesn't have a real time clock, so we can't
118 * really calculate the timer frequency
119 * For now we hardwire the SEAD board frequency to 12MHz.
120 */
Ralf Baechle42a3b4f2005-09-03 15:56:17 -0700121
Linus Torvalds1da177e2005-04-16 15:20:36 -0700122 if ((prid == (PRID_COMP_MIPS | PRID_IMP_20KC)) ||
123 (prid == (PRID_COMP_MIPS | PRID_IMP_25KF)))
124 count = 12000000;
125 else
126 count = 6000000;
127#endif
128#if defined(CONFIG_MIPS_ATLAS) || defined(CONFIG_MIPS_MALTA)
129 unsigned int flags;
130
131 local_irq_save(flags);
132
133 /* Start counter exactly on falling edge of update flag */
134 while (CMOS_READ(RTC_REG_A) & RTC_UIP);
135 while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));
136
137 /* Start r4k counter. */
138 write_c0_count(0);
139
140 /* Read counter exactly on falling edge of update flag */
141 while (CMOS_READ(RTC_REG_A) & RTC_UIP);
142 while (!(CMOS_READ(RTC_REG_A) & RTC_UIP));
143
144 count = read_c0_count();
145
146 /* restore interrupts */
147 local_irq_restore(flags);
148#endif
149
150 mips_hpt_frequency = count;
151 if ((prid != (PRID_COMP_MIPS | PRID_IMP_20KC)) &&
152 (prid != (PRID_COMP_MIPS | PRID_IMP_25KF)))
153 count *= 2;
154
155 count += 5000; /* round */
156 count -= count%10000;
157
158 return count;
159}
160
161unsigned long __init mips_rtc_get_time(void)
162{
163 return mc146818_get_cmos_time();
164}
165
166void __init mips_time_init(void)
167{
168 unsigned int est_freq, flags;
169
170 local_irq_save(flags);
171
Linus Torvalds1da177e2005-04-16 15:20:36 -0700172 /* Set Data mode - binary. */
173 CMOS_WRITE(CMOS_READ(RTC_CONTROL) | RTC_DM_BINARY, RTC_CONTROL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174
175 est_freq = estimate_cpu_frequency ();
176
177 printk("CPU frequency %d.%02d MHz\n", est_freq/1000000,
178 (est_freq%1000000)*100/1000000);
179
180 cpu_khz = est_freq / 1000;
181
182 local_irq_restore(flags);
183}
184
185void __init mips_timer_setup(struct irqaction *irq)
186{
Ralf Baechlee01402b2005-07-14 15:57:16 +0000187 if (cpu_has_veic) {
188 set_vi_handler (MSC01E_INT_CPUCTR, mips_timer_dispatch);
189 mips_cpu_timer_irq = MSC01E_INT_BASE + MSC01E_INT_CPUCTR;
190 }
191 else {
192 if (cpu_has_vint)
193 set_vi_handler (MIPSCPU_INT_CPUCTR, mips_timer_dispatch);
194 mips_cpu_timer_irq = MIPSCPU_INT_BASE + MIPSCPU_INT_CPUCTR;
195 }
196
197
Linus Torvalds1da177e2005-04-16 15:20:36 -0700198 /* we are using the cpu counter for timer interrupts */
Ralf Baechlee01402b2005-07-14 15:57:16 +0000199 irq->handler = mips_timer_interrupt; /* we use our own handler */
200 setup_irq(mips_cpu_timer_irq, irq);
201
Ralf Baechle340ee4b2005-08-17 17:44:08 +0000202#ifdef CONFIG_SMP
203 /* irq_desc(riptor) is a global resource, when the interrupt overlaps
204 on seperate cpu's the first one tries to handle the second interrupt.
205 The effect is that the int remains disabled on the second cpu.
206 Mark the interrupt with IRQ_PER_CPU to avoid any confusion */
207 irq_desc[mips_cpu_timer_irq].status |= IRQ_PER_CPU;
208#endif
Linus Torvalds1da177e2005-04-16 15:20:36 -0700209
210 /* to generate the first timer interrupt */
211 write_c0_compare (read_c0_count() + mips_hpt_frequency/HZ);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212}