mips32: detect L1 cache sizes if they're not defined

For boards such as the MIPS Malta with an FPGA core card it is desirable
to be able to detect the L1 cache sizes at runtime, since they are not
dependant upon the board but on the FPGA bitstream in use. This patch
performs that detection when the CONFIG_SYS_[DI]CACHE_SIZE macros are
not defined by the board configuration. In cases where the sizes are
detected this patch also removes the restriction that the I-cache &
D-cache line sizes must be the same, as this is not necessarily true.

If the cache sizes are defined by a configuration then they will be
hardcoded as before, so this patch will not add overhead to such
boards.

Signed-off-by: Paul Burton <paul.burton@imgtec.com>
diff --git a/arch/mips/cpu/mips32/cache.S b/arch/mips/cpu/mips32/cache.S
index 12f656c..22bd844 100644
--- a/arch/mips/cpu/mips32/cache.S
+++ b/arch/mips/cpu/mips32/cache.S
@@ -20,15 +20,6 @@
 
 #define RA		t9
 
-/*
- * 16kB is the maximum size of instruction and data caches on MIPS 4K,
- * 64kB is on 4KE, 24K, 5K, etc. Set bigger size for convenience.
- *
- * Note that the above size is the maximum size of primary cache. U-Boot
- * doesn't have L2 cache support for now.
- */
-#define MIPS_MAX_CACHE_SIZE	0x10000
-
 #define INDEX_BASE	CKSEG0
 
 	.macro	cache_op op addr
@@ -126,12 +117,85 @@
  */
 NESTED(mips_cache_reset, 0, ra)
 	move	RA, ra
-	li	t2, CONFIG_SYS_ICACHE_SIZE
-	li	t3, CONFIG_SYS_DCACHE_SIZE
+
+#if !defined(CONFIG_SYS_ICACHE_SIZE) || !defined(CONFIG_SYS_DCACHE_SIZE) || \
+    !defined(CONFIG_SYS_CACHELINE_SIZE)
+	/* read Config1 for use below */
+	mfc0	t5, CP0_CONFIG, 1
+#endif
+
+#ifdef CONFIG_SYS_CACHELINE_SIZE
+	li	t7, CONFIG_SYS_CACHELINE_SIZE
 	li	t8, CONFIG_SYS_CACHELINE_SIZE
+#else
+	/* Detect I-cache line size. */
+	srl	t8, t5, MIPS_CONF1_IL_SHIFT
+	andi	t8, t8, (MIPS_CONF1_IL >> MIPS_CONF1_IL_SHIFT)
+	beqz	t8, 1f
+	li	t6, 2
+	sllv	t8, t6, t8
 
-	li	v0, MIPS_MAX_CACHE_SIZE
+1:	/* Detect D-cache line size. */
+	srl	t7, t5, MIPS_CONF1_DL_SHIFT
+	andi	t7, t7, (MIPS_CONF1_DL >> MIPS_CONF1_DL_SHIFT)
+	beqz	t7, 1f
+	li	t6, 2
+	sllv	t7, t6, t7
+1:
+#endif
 
+#ifdef CONFIG_SYS_ICACHE_SIZE
+	li	t2, CONFIG_SYS_ICACHE_SIZE
+#else
+	/* Detect I-cache size. */
+	srl	t6, t5, MIPS_CONF1_IS_SHIFT
+	andi	t6, t6, (MIPS_CONF1_IS >> MIPS_CONF1_IS_SHIFT)
+	li	t4, 32
+	xori	t2, t6, 0x7
+	beqz	t2, 1f
+	addi	t6, t6, 1
+	sllv	t4, t4, t6
+1:	/* At this point t4 == I-cache sets. */
+	mul	t2, t4, t8
+	srl	t6, t5, MIPS_CONF1_IA_SHIFT
+	andi	t6, t6, (MIPS_CONF1_IA >> MIPS_CONF1_IA_SHIFT)
+	addi	t6, t6, 1
+	/* At this point t6 == I-cache ways. */
+	mul	t2, t2, t6
+#endif
+
+#ifdef CONFIG_SYS_DCACHE_SIZE
+	li	t3, CONFIG_SYS_DCACHE_SIZE
+#else
+	/* Detect D-cache size. */
+	srl	t6, t5, MIPS_CONF1_DS_SHIFT
+	andi	t6, t6, (MIPS_CONF1_DS >> MIPS_CONF1_DS_SHIFT)
+	li	t4, 32
+	xori	t3, t6, 0x7
+	beqz	t3, 1f
+	addi	t6, t6, 1
+	sllv	t4, t4, t6
+1:	/* At this point t4 == I-cache sets. */
+	mul	t3, t4, t7
+	srl	t6, t5, MIPS_CONF1_DA_SHIFT
+	andi	t6, t6, (MIPS_CONF1_DA >> MIPS_CONF1_DA_SHIFT)
+	addi	t6, t6, 1
+	/* At this point t6 == I-cache ways. */
+	mul	t3, t3, t6
+#endif
+
+	/* Determine the largest L1 cache size */
+#if defined(CONFIG_SYS_ICACHE_SIZE) && defined(CONFIG_SYS_DCACHE_SIZE)
+#if CONFIG_SYS_ICACHE_SIZE > CONFIG_SYS_DCACHE_SIZE
+	li	v0, CONFIG_SYS_ICACHE_SIZE
+#else
+	li	v0, CONFIG_SYS_DCACHE_SIZE
+#endif
+#else
+	move	v0, t2
+	sltu	t1, t2, t3
+	movn	v0, t3, t1
+#endif
 	/*
 	 * Now clear that much memory starting from zero.
 	 */
@@ -163,7 +227,7 @@
 	 * then initialize D-cache.
 	 */
 	move	a1, t3
-	move	a2, t8
+	move	a2, t7
 	PTR_LA	v1, mips_init_dcache
 	jalr	v1
 
diff --git a/arch/mips/cpu/mips32/cpu.c b/arch/mips/cpu/mips32/cpu.c
index 28d5c45..278865b 100644
--- a/arch/mips/cpu/mips32/cpu.c
+++ b/arch/mips/cpu/mips32/cpu.c
@@ -34,28 +34,89 @@
 	return 0;
 }
 
+#ifdef CONFIG_SYS_CACHELINE_SIZE
+
+static inline unsigned long icache_line_size(void)
+{
+	return CONFIG_SYS_CACHELINE_SIZE;
+}
+
+static inline unsigned long dcache_line_size(void)
+{
+	return CONFIG_SYS_CACHELINE_SIZE;
+}
+
+#else /* !CONFIG_SYS_CACHELINE_SIZE */
+
+static inline unsigned long icache_line_size(void)
+{
+	unsigned long conf1, il;
+	conf1 = read_c0_config1();
+	il = (conf1 & MIPS_CONF1_IL) >> MIPS_CONF1_IL_SHIFT;
+	if (!il)
+		return 0;
+	return 2 << il;
+}
+
+static inline unsigned long dcache_line_size(void)
+{
+	unsigned long conf1, dl;
+	conf1 = read_c0_config1();
+	dl = (conf1 & MIPS_CONF1_DL) >> MIPS_CONF1_DL_SHIFT;
+	if (!dl)
+		return 0;
+	return 2 << dl;
+}
+
+#endif /* !CONFIG_SYS_CACHELINE_SIZE */
+
 void flush_cache(ulong start_addr, ulong size)
 {
-	unsigned long lsize = CONFIG_SYS_CACHELINE_SIZE;
-	unsigned long addr = start_addr & ~(lsize - 1);
-	unsigned long aend = (start_addr + size - 1) & ~(lsize - 1);
+	unsigned long ilsize = icache_line_size();
+	unsigned long dlsize = dcache_line_size();
+	unsigned long addr, aend;
 
 	/* aend will be miscalculated when size is zero, so we return here */
 	if (size == 0)
 		return;
 
+	addr = start_addr & ~(dlsize - 1);
+	aend = (start_addr + size - 1) & ~(dlsize - 1);
+
+	if (ilsize == dlsize) {
+		/* flush I-cache & D-cache simultaneously */
+		while (1) {
+			cache_op(HIT_WRITEBACK_INV_D, addr);
+			cache_op(HIT_INVALIDATE_I, addr);
+			if (addr == aend)
+				break;
+			addr += dlsize;
+		}
+		return;
+	}
+
+	/* flush D-cache */
 	while (1) {
 		cache_op(HIT_WRITEBACK_INV_D, addr);
+		if (addr == aend)
+			break;
+		addr += dlsize;
+	}
+
+	/* flush I-cache */
+	addr = start_addr & ~(ilsize - 1);
+	aend = (start_addr + size - 1) & ~(ilsize - 1);
+	while (1) {
 		cache_op(HIT_INVALIDATE_I, addr);
 		if (addr == aend)
 			break;
-		addr += lsize;
+		addr += ilsize;
 	}
 }
 
 void flush_dcache_range(ulong start_addr, ulong stop)
 {
-	unsigned long lsize = CONFIG_SYS_CACHELINE_SIZE;
+	unsigned long lsize = dcache_line_size();
 	unsigned long addr = start_addr & ~(lsize - 1);
 	unsigned long aend = (stop - 1) & ~(lsize - 1);
 
@@ -69,7 +130,7 @@
 
 void invalidate_dcache_range(ulong start_addr, ulong stop)
 {
-	unsigned long lsize = CONFIG_SYS_CACHELINE_SIZE;
+	unsigned long lsize = dcache_line_size();
 	unsigned long addr = start_addr & ~(lsize - 1);
 	unsigned long aend = (stop - 1) & ~(lsize - 1);
 
diff --git a/arch/mips/include/asm/mipsregs.h b/arch/mips/include/asm/mipsregs.h
index be7e5c6..3571e4f 100644
--- a/arch/mips/include/asm/mipsregs.h
+++ b/arch/mips/include/asm/mipsregs.h
@@ -494,11 +494,17 @@
 #define MIPS_CONF1_PC		(_ULCAST_(1) <<  4)
 #define MIPS_CONF1_MD		(_ULCAST_(1) <<  5)
 #define MIPS_CONF1_C2		(_ULCAST_(1) <<  6)
+#define MIPS_CONF1_DA_SHIFT	7
 #define MIPS_CONF1_DA		(_ULCAST_(7) <<  7)
+#define MIPS_CONF1_DL_SHIFT	10
 #define MIPS_CONF1_DL		(_ULCAST_(7) << 10)
+#define MIPS_CONF1_DS_SHIFT	13
 #define MIPS_CONF1_DS		(_ULCAST_(7) << 13)
+#define MIPS_CONF1_IA_SHIFT	16
 #define MIPS_CONF1_IA		(_ULCAST_(7) << 16)
+#define MIPS_CONF1_IL_SHIFT	19
 #define MIPS_CONF1_IL		(_ULCAST_(7) << 19)
+#define MIPS_CONF1_IS_SHIFT	22
 #define MIPS_CONF1_IS		(_ULCAST_(7) << 22)
 #define MIPS_CONF1_TLBS		(_ULCAST_(63)<< 25)