aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ubicom32/files/arch/ubicom32/mach-common/cachectl.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/ubicom32/files/arch/ubicom32/mach-common/cachectl.c')
-rw-r--r--target/linux/ubicom32/files/arch/ubicom32/mach-common/cachectl.c136
1 files changed, 136 insertions, 0 deletions
diff --git a/target/linux/ubicom32/files/arch/ubicom32/mach-common/cachectl.c b/target/linux/ubicom32/files/arch/ubicom32/mach-common/cachectl.c
new file mode 100644
index 000000000..afb9dc4d4
--- /dev/null
+++ b/target/linux/ubicom32/files/arch/ubicom32/mach-common/cachectl.c
@@ -0,0 +1,136 @@
+/*
+ * arch/ubicom32/mach-common/cachectl.c
+ * Architecture cache control support
+ *
+ * (C) Copyright 2009, Ubicom, Inc.
+ *
+ * This file is part of the Ubicom32 Linux Kernel Port.
+ *
+ * The Ubicom32 Linux Kernel Port 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.
+ *
+ * The Ubicom32 Linux Kernel Port is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+ * the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with the Ubicom32 Linux Kernel Port. If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Ubicom32 implementation derived from (with many thanks):
+ * arch/m68knommu
+ * arch/blackfin
+ * arch/parisc
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <asm/cachectl.h>
+
+/*
+ * The write queue flush procedure in mem_cache_control needs to make
+ * DCACHE_WRITE_QUEUE_LENGTH writes to DDR (not OCM). Here we reserve some
+ * memory for this operation.
+ * Allocate array of cache lines of least DCACHE_WRITE_QUEUE_LENGTH + 1 words in
+ * length rounded up to the nearest cache line.
+ */
+#define CACHE_WRITE_QUEUE_FLUSH_AREA_SIZE \
+ ALIGN(sizeof(int) * (DCACHE_WRITE_QUEUE_LENGTH + 1), CACHE_LINE_SIZE)
+
+static char cache_write_queue_flush_area[CACHE_WRITE_QUEUE_FLUSH_AREA_SIZE]
+ __attribute__((aligned(CACHE_LINE_SIZE)));
+
+/*
+ * ONE_CCR_ADDR_OP is a helper macro that executes a single CCR operation.
+ */
+#define ONE_CCR_ADDR_OP(cc, op_addr, op) \
+ do { \
+ asm volatile ( \
+ " btst "D(CCR_CTRL)"(%0), #"D(CCR_CTRL_VALID)" \n\t" \
+ " jmpne.f .-4 \n\t" \
+ " move.4 "D(CCR_ADDR)"(%0), %1 \n\t" \
+ " move.1 "D(CCR_CTRL+3)"(%0), %2 \n\t" \
+ " bset "D(CCR_CTRL)"(%0), "D(CCR_CTRL)"(%0), #"D(CCR_CTRL_VALID)" \n\t" \
+ " cycles 2 \n\t" \
+ " btst "D(CCR_CTRL)"(%0), #"D(CCR_CTRL_DONE)" \n\t" \
+ " jmpeq.f .-4 \n\t" \
+ : \
+ : "a"(cc), "r"(op_addr), "r"(op & 0xff) \
+ : "cc" \
+ ); \
+ } while (0)
+
+/*
+ * mem_cache_control()
+ * Special cache control operation
+ */
+void mem_cache_control(unsigned long cc, unsigned long begin_addr,
+ unsigned long end_addr, unsigned long op)
+{
+ unsigned long op_addr;
+ int dccr = cc == DCCR_BASE;
+ if (dccr && op == CCR_CTRL_FLUSH_ADDR) {
+ /*
+ * We ensure all previous writes have left the data cache write
+ * queue by sending DCACHE_WRITE_QUEUE_LENGTH writes (to
+ * different words) down the queue. If this is not done it's
+ * possible that the data we are trying to flush hasn't even
+ * entered the data cache.
+ * The +1 ensure that the final 'flush' is actually a flush.
+ */
+ int *flush_area = (int *)cache_write_queue_flush_area;
+ asm volatile(
+ " .rept "D(DCACHE_WRITE_QUEUE_LENGTH + 1)" \n\t"
+ " move.4 (%0)4++, d0 \n\t"
+ " .endr \n\t"
+ : "+a"(flush_area)
+ );
+ }
+
+ if (dccr)
+ UBICOM32_LOCK(DCCR_LOCK_BIT);
+ else
+ UBICOM32_LOCK(ICCR_LOCK_BIT);
+
+ /*
+ * Calculate the cache lines we need to operate on that include
+ * begin_addr though end_addr.
+ */
+ begin_addr = begin_addr & ~(CACHE_LINE_SIZE - 1);
+ end_addr = (end_addr + CACHE_LINE_SIZE - 1) & ~(CACHE_LINE_SIZE - 1);
+ op_addr = begin_addr;
+
+ do {
+ ONE_CCR_ADDR_OP(cc, op_addr, op);
+ op_addr += CACHE_LINE_SIZE;
+ } while (likely(op_addr < end_addr));
+
+ if (dccr && op == CCR_CTRL_FLUSH_ADDR) {
+ /*
+ * It turns out that when flushing the data cache the last flush
+ * isn't actually complete at this point. This is because there
+ * is another write buffer on the DDR side of the cache that is
+ * arbitrated with the I-Cache.
+ *
+ * The only foolproof method that ensures that the last data
+ * cache flush *actually* completed is to do another flush on a
+ * dirty cache line. This flush will block until the DDR write
+ * buffer is empty.
+ *
+ * Rather than creating a another dirty cache line, we use the
+ * flush_area above as we know that it is dirty from previous
+ * writes.
+ */
+ ONE_CCR_ADDR_OP(cc, cache_write_queue_flush_area, op);
+ }
+
+ if (dccr)
+ UBICOM32_UNLOCK(DCCR_LOCK_BIT);
+ else
+ UBICOM32_UNLOCK(ICCR_LOCK_BIT);
+
+}
+EXPORT_SYMBOL(mem_cache_control);