diff options
Diffstat (limited to 'arch/arm/mm/cache-feroceon-l2.c')
| -rw-r--r-- | arch/arm/mm/cache-feroceon-l2.c | 148 |
1 files changed, 113 insertions, 35 deletions
diff --git a/arch/arm/mm/cache-feroceon-l2.c b/arch/arm/mm/cache-feroceon-l2.c index 20eec4ba173..e028a7f2ebc 100644 --- a/arch/arm/mm/cache-feroceon-l2.c +++ b/arch/arm/mm/cache-feroceon-l2.c @@ -13,9 +13,15 @@ */ #include <linux/init.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/highmem.h> +#include <linux/io.h> #include <asm/cacheflush.h> -#include <asm/plat-orion/cache-feroceon-l2.h> +#include <asm/cp15.h> +#include <asm/hardware/cache-feroceon-l2.h> +#define L2_WRITETHROUGH_KIRKWOOD BIT(4) /* * Low-level cache maintenance operations. @@ -34,31 +40,55 @@ * The range operations require two successive cp15 writes, in * between which we don't want to be preempted. */ + +static inline unsigned long l2_get_va(unsigned long paddr) +{ +#ifdef CONFIG_HIGHMEM + /* + * Because range ops can't be done on physical addresses, + * we simply install a virtual mapping for it only for the + * TLB lookup to occur, hence no need to flush the untouched + * memory mapping afterwards (note: a cache flush may happen + * in some circumstances depending on the path taken in kunmap_atomic). + */ + void *vaddr = kmap_atomic_pfn(paddr >> PAGE_SHIFT); + return (unsigned long)vaddr + (paddr & ~PAGE_MASK); +#else + return __phys_to_virt(paddr); +#endif +} + +static inline void l2_put_va(unsigned long vaddr) +{ +#ifdef CONFIG_HIGHMEM + kunmap_atomic((void *)vaddr); +#endif +} + static inline void l2_clean_pa(unsigned long addr) { __asm__("mcr p15, 1, %0, c15, c9, 3" : : "r" (addr)); } -static inline void l2_clean_mva_range(unsigned long start, unsigned long end) +static inline void l2_clean_pa_range(unsigned long start, unsigned long end) { - unsigned long flags; + unsigned long va_start, va_end, flags; /* * Make sure 'start' and 'end' reference the same page, as * L2 is PIPT and range operations only do a TLB lookup on * the start address. */ - BUG_ON((start ^ end) & ~(PAGE_SIZE - 1)); + BUG_ON((start ^ end) >> PAGE_SHIFT); + va_start = l2_get_va(start); + va_end = va_start + (end - start); raw_local_irq_save(flags); - __asm__("mcr p15, 1, %0, c15, c9, 4" : : "r" (start)); - __asm__("mcr p15, 1, %0, c15, c9, 5" : : "r" (end)); + __asm__("mcr p15, 1, %0, c15, c9, 4\n\t" + "mcr p15, 1, %1, c15, c9, 5" + : : "r" (va_start), "r" (va_end)); raw_local_irq_restore(flags); -} - -static inline void l2_clean_pa_range(unsigned long start, unsigned long end) -{ - l2_clean_mva_range(__phys_to_virt(start), __phys_to_virt(end)); + l2_put_va(va_start); } static inline void l2_clean_inv_pa(unsigned long addr) @@ -71,29 +101,32 @@ static inline void l2_inv_pa(unsigned long addr) __asm__("mcr p15, 1, %0, c15, c11, 3" : : "r" (addr)); } -static inline void l2_inv_mva_range(unsigned long start, unsigned long end) +static inline void l2_inv_pa_range(unsigned long start, unsigned long end) { - unsigned long flags; + unsigned long va_start, va_end, flags; /* * Make sure 'start' and 'end' reference the same page, as * L2 is PIPT and range operations only do a TLB lookup on * the start address. */ - BUG_ON((start ^ end) & ~(PAGE_SIZE - 1)); + BUG_ON((start ^ end) >> PAGE_SHIFT); + va_start = l2_get_va(start); + va_end = va_start + (end - start); raw_local_irq_save(flags); - __asm__("mcr p15, 1, %0, c15, c11, 4" : : "r" (start)); - __asm__("mcr p15, 1, %0, c15, c11, 5" : : "r" (end)); + __asm__("mcr p15, 1, %0, c15, c11, 4\n\t" + "mcr p15, 1, %1, c15, c11, 5" + : : "r" (va_start), "r" (va_end)); raw_local_irq_restore(flags); + l2_put_va(va_start); } -static inline void l2_inv_pa_range(unsigned long start, unsigned long end) +static inline void l2_inv_all(void) { - l2_inv_mva_range(__phys_to_virt(start), __phys_to_virt(end)); + __asm__("mcr p15, 1, %0, c15, c11, 0" : : "r" (0)); } - /* * Linux primitives. * @@ -148,7 +181,7 @@ static void feroceon_l2_inv_range(unsigned long start, unsigned long end) /* * Clean and invalidate partial last cache line. */ - if (end & (CACHE_LINE_SIZE - 1)) { + if (start < end && end & (CACHE_LINE_SIZE - 1)) { l2_clean_inv_pa(end & ~(CACHE_LINE_SIZE - 1)); end &= ~(CACHE_LINE_SIZE - 1); } @@ -156,7 +189,7 @@ static void feroceon_l2_inv_range(unsigned long start, unsigned long end) /* * Invalidate all full cache lines between 'start' and 'end'. */ - while (start != end) { + while (start < end) { unsigned long range_end = calc_range_end(start, end); l2_inv_pa_range(start, range_end - CACHE_LINE_SIZE); start = range_end; @@ -205,7 +238,7 @@ static void feroceon_l2_flush_range(unsigned long start, unsigned long end) * time. These are necessary because the L2 cache can only be enabled * or disabled while the L1 Dcache and Icache are both disabled. */ -static void __init invalidate_and_disable_dcache(void) +static int __init flush_and_disable_dcache(void) { u32 cr; @@ -217,7 +250,9 @@ static void __init invalidate_and_disable_dcache(void) flush_cache_all(); set_cr(cr & ~CR_C); raw_local_irq_restore(flags); + return 1; } + return 0; } static void __init enable_dcache(void) @@ -225,18 +260,15 @@ static void __init enable_dcache(void) u32 cr; cr = get_cr(); - if (!(cr & CR_C)) - set_cr(cr | CR_C); + set_cr(cr | CR_C); } static void __init __invalidate_icache(void) { - int dummy; - - __asm__ __volatile__("mcr p15, 0, %0, c7, c5, 0\n" : "=r" (dummy)); + __asm__("mcr p15, 0, %0, c7, c5, 0" : : "r" (0)); } -static void __init invalidate_and_disable_icache(void) +static int __init invalidate_and_disable_icache(void) { u32 cr; @@ -244,7 +276,9 @@ static void __init invalidate_and_disable_icache(void) if (cr & CR_I) { set_cr(cr & ~CR_I); __invalidate_icache(); + return 1; } + return 0; } static void __init enable_icache(void) @@ -252,8 +286,7 @@ static void __init enable_icache(void) u32 cr; cr = get_cr(); - if (!(cr & CR_I)) - set_cr(cr | CR_I); + set_cr(cr | CR_I); } static inline u32 read_extra_features(void) @@ -291,14 +324,21 @@ static void __init enable_l2(void) u = read_extra_features(); if (!(u & 0x00400000)) { + int i, d; + printk(KERN_INFO "Feroceon L2: Enabling L2\n"); - invalidate_and_disable_dcache(); - invalidate_and_disable_icache(); + d = flush_and_disable_dcache(); + i = invalidate_and_disable_icache(); + l2_inv_all(); write_extra_features(u | 0x00400000); - enable_icache(); - enable_dcache(); - } + if (i) + enable_icache(); + if (d) + enable_dcache(); + } else + pr_err(FW_BUG + "Feroceon L2: bootloader left the L2 cache on!\n"); } void __init feroceon_l2_init(int __l2_wt_override) @@ -316,3 +356,41 @@ void __init feroceon_l2_init(int __l2_wt_override) printk(KERN_INFO "Feroceon L2: Cache support initialised%s.\n", l2_wt_override ? ", in WT override mode" : ""); } +#ifdef CONFIG_OF +static const struct of_device_id feroceon_ids[] __initconst = { + { .compatible = "marvell,kirkwood-cache"}, + { .compatible = "marvell,feroceon-cache"}, + {} +}; + +int __init feroceon_of_init(void) +{ + struct device_node *node; + void __iomem *base; + bool l2_wt_override = false; + struct resource res; + +#if defined(CONFIG_CACHE_FEROCEON_L2_WRITETHROUGH) + l2_wt_override = true; +#endif + + node = of_find_matching_node(NULL, feroceon_ids); + if (node && of_device_is_compatible(node, "marvell,kirkwood-cache")) { + if (of_address_to_resource(node, 0, &res)) + return -ENODEV; + + base = ioremap(res.start, resource_size(&res)); + if (!base) + return -ENOMEM; + + if (l2_wt_override) + writel(readl(base) | L2_WRITETHROUGH_KIRKWOOD, base); + else + writel(readl(base) & ~L2_WRITETHROUGH_KIRKWOOD, base); + } + + feroceon_l2_init(l2_wt_override); + + return 0; +} +#endif |
