Merge branch 'topic/jack-mechanical' into to-push
[linux-2.6] / arch / arm / mm / cache-feroceon-l2.c
1 /*
2  * arch/arm/mm/cache-feroceon-l2.c - Feroceon L2 cache controller support
3  *
4  * Copyright (C) 2008 Marvell Semiconductor
5  *
6  * This file is licensed under the terms of the GNU General Public
7  * License version 2.  This program is licensed "as is" without any
8  * warranty of any kind, whether express or implied.
9  *
10  * References:
11  * - Unified Layer 2 Cache for Feroceon CPU Cores,
12  *   Document ID MV-S104858-00, Rev. A, October 23 2007.
13  */
14
15 #include <linux/init.h>
16 #include <asm/cacheflush.h>
17 #include <plat/cache-feroceon-l2.h>
18
19
20 /*
21  * Low-level cache maintenance operations.
22  *
23  * As well as the regular 'clean/invalidate/flush L2 cache line by
24  * MVA' instructions, the Feroceon L2 cache controller also features
25  * 'clean/invalidate L2 range by MVA' operations.
26  *
27  * Cache range operations are initiated by writing the start and
28  * end addresses to successive cp15 registers, and process every
29  * cache line whose first byte address lies in the inclusive range
30  * [start:end].
31  *
32  * The cache range operations stall the CPU pipeline until completion.
33  *
34  * The range operations require two successive cp15 writes, in
35  * between which we don't want to be preempted.
36  */
37 static inline void l2_clean_pa(unsigned long addr)
38 {
39         __asm__("mcr p15, 1, %0, c15, c9, 3" : : "r" (addr));
40 }
41
42 static inline void l2_clean_mva_range(unsigned long start, unsigned long end)
43 {
44         unsigned long flags;
45
46         /*
47          * Make sure 'start' and 'end' reference the same page, as
48          * L2 is PIPT and range operations only do a TLB lookup on
49          * the start address.
50          */
51         BUG_ON((start ^ end) >> PAGE_SHIFT);
52
53         raw_local_irq_save(flags);
54         __asm__("mcr p15, 1, %0, c15, c9, 4\n\t"
55                 "mcr p15, 1, %1, c15, c9, 5"
56                 : : "r" (start), "r" (end));
57         raw_local_irq_restore(flags);
58 }
59
60 static inline void l2_clean_pa_range(unsigned long start, unsigned long end)
61 {
62         l2_clean_mva_range(__phys_to_virt(start), __phys_to_virt(end));
63 }
64
65 static inline void l2_clean_inv_pa(unsigned long addr)
66 {
67         __asm__("mcr p15, 1, %0, c15, c10, 3" : : "r" (addr));
68 }
69
70 static inline void l2_inv_pa(unsigned long addr)
71 {
72         __asm__("mcr p15, 1, %0, c15, c11, 3" : : "r" (addr));
73 }
74
75 static inline void l2_inv_mva_range(unsigned long start, unsigned long end)
76 {
77         unsigned long flags;
78
79         /*
80          * Make sure 'start' and 'end' reference the same page, as
81          * L2 is PIPT and range operations only do a TLB lookup on
82          * the start address.
83          */
84         BUG_ON((start ^ end) >> PAGE_SHIFT);
85
86         raw_local_irq_save(flags);
87         __asm__("mcr p15, 1, %0, c15, c11, 4\n\t"
88                 "mcr p15, 1, %1, c15, c11, 5"
89                 : : "r" (start), "r" (end));
90         raw_local_irq_restore(flags);
91 }
92
93 static inline void l2_inv_pa_range(unsigned long start, unsigned long end)
94 {
95         l2_inv_mva_range(__phys_to_virt(start), __phys_to_virt(end));
96 }
97
98
99 /*
100  * Linux primitives.
101  *
102  * Note that the end addresses passed to Linux primitives are
103  * noninclusive, while the hardware cache range operations use
104  * inclusive start and end addresses.
105  */
106 #define CACHE_LINE_SIZE         32
107 #define MAX_RANGE_SIZE          1024
108
109 static int l2_wt_override;
110
111 static unsigned long calc_range_end(unsigned long start, unsigned long end)
112 {
113         unsigned long range_end;
114
115         BUG_ON(start & (CACHE_LINE_SIZE - 1));
116         BUG_ON(end & (CACHE_LINE_SIZE - 1));
117
118         /*
119          * Try to process all cache lines between 'start' and 'end'.
120          */
121         range_end = end;
122
123         /*
124          * Limit the number of cache lines processed at once,
125          * since cache range operations stall the CPU pipeline
126          * until completion.
127          */
128         if (range_end > start + MAX_RANGE_SIZE)
129                 range_end = start + MAX_RANGE_SIZE;
130
131         /*
132          * Cache range operations can't straddle a page boundary.
133          */
134         if (range_end > (start | (PAGE_SIZE - 1)) + 1)
135                 range_end = (start | (PAGE_SIZE - 1)) + 1;
136
137         return range_end;
138 }
139
140 static void feroceon_l2_inv_range(unsigned long start, unsigned long end)
141 {
142         /*
143          * Clean and invalidate partial first cache line.
144          */
145         if (start & (CACHE_LINE_SIZE - 1)) {
146                 l2_clean_inv_pa(start & ~(CACHE_LINE_SIZE - 1));
147                 start = (start | (CACHE_LINE_SIZE - 1)) + 1;
148         }
149
150         /*
151          * Clean and invalidate partial last cache line.
152          */
153         if (start < end && end & (CACHE_LINE_SIZE - 1)) {
154                 l2_clean_inv_pa(end & ~(CACHE_LINE_SIZE - 1));
155                 end &= ~(CACHE_LINE_SIZE - 1);
156         }
157
158         /*
159          * Invalidate all full cache lines between 'start' and 'end'.
160          */
161         while (start < end) {
162                 unsigned long range_end = calc_range_end(start, end);
163                 l2_inv_pa_range(start, range_end - CACHE_LINE_SIZE);
164                 start = range_end;
165         }
166
167         dsb();
168 }
169
170 static void feroceon_l2_clean_range(unsigned long start, unsigned long end)
171 {
172         /*
173          * If L2 is forced to WT, the L2 will always be clean and we
174          * don't need to do anything here.
175          */
176         if (!l2_wt_override) {
177                 start &= ~(CACHE_LINE_SIZE - 1);
178                 end = (end + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
179                 while (start != end) {
180                         unsigned long range_end = calc_range_end(start, end);
181                         l2_clean_pa_range(start, range_end - CACHE_LINE_SIZE);
182                         start = range_end;
183                 }
184         }
185
186         dsb();
187 }
188
189 static void feroceon_l2_flush_range(unsigned long start, unsigned long end)
190 {
191         start &= ~(CACHE_LINE_SIZE - 1);
192         end = (end + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
193         while (start != end) {
194                 unsigned long range_end = calc_range_end(start, end);
195                 if (!l2_wt_override)
196                         l2_clean_pa_range(start, range_end - CACHE_LINE_SIZE);
197                 l2_inv_pa_range(start, range_end - CACHE_LINE_SIZE);
198                 start = range_end;
199         }
200
201         dsb();
202 }
203
204
205 /*
206  * Routines to disable and re-enable the D-cache and I-cache at run
207  * time.  These are necessary because the L2 cache can only be enabled
208  * or disabled while the L1 Dcache and Icache are both disabled.
209  */
210 static int __init flush_and_disable_dcache(void)
211 {
212         u32 cr;
213
214         cr = get_cr();
215         if (cr & CR_C) {
216                 unsigned long flags;
217
218                 raw_local_irq_save(flags);
219                 flush_cache_all();
220                 set_cr(cr & ~CR_C);
221                 raw_local_irq_restore(flags);
222                 return 1;
223         }
224         return 0;
225 }
226
227 static void __init enable_dcache(void)
228 {
229         u32 cr;
230
231         cr = get_cr();
232         set_cr(cr | CR_C);
233 }
234
235 static void __init __invalidate_icache(void)
236 {
237         int dummy;
238
239         __asm__ __volatile__("mcr p15, 0, %0, c7, c5, 0" : "=r" (dummy));
240 }
241
242 static int __init invalidate_and_disable_icache(void)
243 {
244         u32 cr;
245
246         cr = get_cr();
247         if (cr & CR_I) {
248                 set_cr(cr & ~CR_I);
249                 __invalidate_icache();
250                 return 1;
251         }
252         return 0;
253 }
254
255 static void __init enable_icache(void)
256 {
257         u32 cr;
258
259         cr = get_cr();
260         set_cr(cr | CR_I);
261 }
262
263 static inline u32 read_extra_features(void)
264 {
265         u32 u;
266
267         __asm__("mrc p15, 1, %0, c15, c1, 0" : "=r" (u));
268
269         return u;
270 }
271
272 static inline void write_extra_features(u32 u)
273 {
274         __asm__("mcr p15, 1, %0, c15, c1, 0" : : "r" (u));
275 }
276
277 static void __init disable_l2_prefetch(void)
278 {
279         u32 u;
280
281         /*
282          * Read the CPU Extra Features register and verify that the
283          * Disable L2 Prefetch bit is set.
284          */
285         u = read_extra_features();
286         if (!(u & 0x01000000)) {
287                 printk(KERN_INFO "Feroceon L2: Disabling L2 prefetch.\n");
288                 write_extra_features(u | 0x01000000);
289         }
290 }
291
292 static void __init enable_l2(void)
293 {
294         u32 u;
295
296         u = read_extra_features();
297         if (!(u & 0x00400000)) {
298                 int i, d;
299
300                 printk(KERN_INFO "Feroceon L2: Enabling L2\n");
301
302                 d = flush_and_disable_dcache();
303                 i = invalidate_and_disable_icache();
304                 write_extra_features(u | 0x00400000);
305                 if (i)
306                         enable_icache();
307                 if (d)
308                         enable_dcache();
309         }
310 }
311
312 void __init feroceon_l2_init(int __l2_wt_override)
313 {
314         l2_wt_override = __l2_wt_override;
315
316         disable_l2_prefetch();
317
318         outer_cache.inv_range = feroceon_l2_inv_range;
319         outer_cache.clean_range = feroceon_l2_clean_range;
320         outer_cache.flush_range = feroceon_l2_flush_range;
321
322         enable_l2();
323
324         printk(KERN_INFO "Feroceon L2: Cache support initialised%s.\n",
325                          l2_wt_override ? ", in WT override mode" : "");
326 }