diff options
Diffstat (limited to 'arch/mips/sni/time.c')
-rw-r--r-- | arch/mips/sni/time.c | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/arch/mips/sni/time.c b/arch/mips/sni/time.c new file mode 100644 index 000000000000..20028fc7757e --- /dev/null +++ b/arch/mips/sni/time.c | |||
@@ -0,0 +1,148 @@ | |||
1 | #include <linux/types.h> | ||
2 | #include <linux/interrupt.h> | ||
3 | #include <linux/time.h> | ||
4 | |||
5 | #include <asm/sni.h> | ||
6 | #include <asm/time.h> | ||
7 | |||
8 | #define SNI_CLOCK_TICK_RATE 3686400 | ||
9 | #define SNI_COUNTER2_DIV 64 | ||
10 | #define SNI_COUNTER0_DIV ((SNI_CLOCK_TICK_RATE / SNI_COUNTER2_DIV) / HZ) | ||
11 | |||
12 | static void sni_a20r_timer_ack(void) | ||
13 | { | ||
14 | *(volatile u8 *)A20R_PT_TIM0_ACK = 0x0; wmb(); | ||
15 | } | ||
16 | |||
17 | /* | ||
18 | * a20r platform uses 2 counters to divide the input frequency. | ||
19 | * Counter 2 output is connected to Counter 0 & 1 input. | ||
20 | */ | ||
21 | static void __init sni_a20r_timer_setup(struct irqaction *irq) | ||
22 | { | ||
23 | *(volatile u8 *)(A20R_PT_CLOCK_BASE + 12) = 0x34; wmb(); | ||
24 | *(volatile u8 *)(A20R_PT_CLOCK_BASE + 0) = (SNI_COUNTER0_DIV) & 0xff; wmb(); | ||
25 | *(volatile u8 *)(A20R_PT_CLOCK_BASE + 0) = (SNI_COUNTER0_DIV >> 8) & 0xff; wmb(); | ||
26 | |||
27 | *(volatile u8 *)(A20R_PT_CLOCK_BASE + 12) = 0xb4; wmb(); | ||
28 | *(volatile u8 *)(A20R_PT_CLOCK_BASE + 8) = (SNI_COUNTER2_DIV) & 0xff; wmb(); | ||
29 | *(volatile u8 *)(A20R_PT_CLOCK_BASE + 8) = (SNI_COUNTER2_DIV >> 8) & 0xff; wmb(); | ||
30 | |||
31 | setup_irq(SNI_A20R_IRQ_TIMER, irq); | ||
32 | mips_timer_ack = sni_a20r_timer_ack; | ||
33 | } | ||
34 | |||
35 | #define SNI_8254_TICK_RATE 1193182UL | ||
36 | |||
37 | #define SNI_8254_TCSAMP_COUNTER ((SNI_8254_TICK_RATE / HZ) + 255) | ||
38 | |||
39 | static __init unsigned long dosample(void) | ||
40 | { | ||
41 | u32 ct0, ct1; | ||
42 | volatile u8 msb, lsb; | ||
43 | |||
44 | /* Start the counter. */ | ||
45 | outb_p (0x34, 0x43); | ||
46 | outb_p(SNI_8254_TCSAMP_COUNTER & 0xff, 0x40); | ||
47 | outb (SNI_8254_TCSAMP_COUNTER >> 8, 0x40); | ||
48 | |||
49 | /* Get initial counter invariant */ | ||
50 | ct0 = read_c0_count(); | ||
51 | |||
52 | /* Latch and spin until top byte of counter0 is zero */ | ||
53 | do { | ||
54 | outb (0x00, 0x43); | ||
55 | lsb = inb (0x40); | ||
56 | msb = inb (0x40); | ||
57 | ct1 = read_c0_count(); | ||
58 | } while (msb); | ||
59 | |||
60 | /* Stop the counter. */ | ||
61 | outb (0x38, 0x43); | ||
62 | /* | ||
63 | * Return the difference, this is how far the r4k counter increments | ||
64 | * for every 1/HZ seconds. We round off the nearest 1 MHz of master | ||
65 | * clock (= 1000000 / HZ / 2). | ||
66 | */ | ||
67 | /*return (ct1 - ct0 + (500000/HZ/2)) / (500000/HZ) * (500000/HZ);*/ | ||
68 | return (ct1 - ct0) / (500000/HZ) * (500000/HZ); | ||
69 | } | ||
70 | |||
71 | /* | ||
72 | * Here we need to calibrate the cycle counter to at least be close. | ||
73 | */ | ||
74 | __init void sni_cpu_time_init(void) | ||
75 | { | ||
76 | unsigned long r4k_ticks[3]; | ||
77 | unsigned long r4k_tick; | ||
78 | |||
79 | /* | ||
80 | * Figure out the r4k offset, the algorithm is very simple and works in | ||
81 | * _all_ cases as long as the 8254 counter register itself works ok (as | ||
82 | * an interrupt driving timer it does not because of bug, this is why | ||
83 | * we are using the onchip r4k counter/compare register to serve this | ||
84 | * purpose, but for r4k_offset calculation it will work ok for us). | ||
85 | * There are other very complicated ways of performing this calculation | ||
86 | * but this one works just fine so I am not going to futz around. ;-) | ||
87 | */ | ||
88 | printk(KERN_INFO "Calibrating system timer... "); | ||
89 | dosample(); /* Prime cache. */ | ||
90 | dosample(); /* Prime cache. */ | ||
91 | /* Zero is NOT an option. */ | ||
92 | do { | ||
93 | r4k_ticks[0] = dosample(); | ||
94 | } while (!r4k_ticks[0]); | ||
95 | do { | ||
96 | r4k_ticks[1] = dosample(); | ||
97 | } while (!r4k_ticks[1]); | ||
98 | |||
99 | if (r4k_ticks[0] != r4k_ticks[1]) { | ||
100 | printk("warning: timer counts differ, retrying... "); | ||
101 | r4k_ticks[2] = dosample(); | ||
102 | if (r4k_ticks[2] == r4k_ticks[0] | ||
103 | || r4k_ticks[2] == r4k_ticks[1]) | ||
104 | r4k_tick = r4k_ticks[2]; | ||
105 | else { | ||
106 | printk("disagreement, using average... "); | ||
107 | r4k_tick = (r4k_ticks[0] + r4k_ticks[1] | ||
108 | + r4k_ticks[2]) / 3; | ||
109 | } | ||
110 | } else | ||
111 | r4k_tick = r4k_ticks[0]; | ||
112 | |||
113 | printk("%d [%d.%04d MHz CPU]\n", (int) r4k_tick, | ||
114 | (int) (r4k_tick / (500000 / HZ)), | ||
115 | (int) (r4k_tick % (500000 / HZ))); | ||
116 | |||
117 | mips_hpt_frequency = r4k_tick * HZ; | ||
118 | } | ||
119 | |||
120 | /* | ||
121 | * R4k counter based timer interrupt. Works on RM200-225 and possibly | ||
122 | * others but not on RM400 | ||
123 | */ | ||
124 | static void __init sni_cpu_timer_setup(struct irqaction *irq) | ||
125 | { | ||
126 | setup_irq(SNI_MIPS_IRQ_CPU_TIMER, irq); | ||
127 | } | ||
128 | |||
129 | void __init plat_timer_setup(struct irqaction *irq) | ||
130 | { | ||
131 | switch (sni_brd_type) { | ||
132 | case SNI_BRD_10: | ||
133 | case SNI_BRD_10NEW: | ||
134 | case SNI_BRD_TOWER_OASIC: | ||
135 | case SNI_BRD_MINITOWER: | ||
136 | sni_a20r_timer_setup (irq); | ||
137 | break; | ||
138 | |||
139 | case SNI_BRD_PCI_TOWER: | ||
140 | case SNI_BRD_RM200: | ||
141 | case SNI_BRD_PCI_MTOWER: | ||
142 | case SNI_BRD_PCI_DESKTOP: | ||
143 | case SNI_BRD_PCI_TOWER_CPLUS: | ||
144 | case SNI_BRD_PCI_MTOWER_CPLUS: | ||
145 | sni_cpu_timer_setup (irq); | ||
146 | break; | ||
147 | } | ||
148 | } | ||