diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index 732d129..8f97727 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -301,6 +301,12 @@ config MIPS_L1_CACHE_SHIFT
 	default "4" if MIPS_L1_CACHE_SHIFT_4
 	default "5"
 
+config MIPS_L2_CACHE
+	bool
+	help
+	  Select this if your system includes an L2 cache and you want U-Boot
+	  to initialise & maintain it.
+
 config DYNAMIC_IO_PORT_BASE
 	bool
 
diff --git a/arch/mips/include/asm/cm.h b/arch/mips/include/asm/cm.h
index 0261733..62ecef2 100644
--- a/arch/mips/include/asm/cm.h
+++ b/arch/mips/include/asm/cm.h
@@ -12,8 +12,46 @@
 #define GCR_BASE			0x0008
 #define GCR_BASE_UPPER			0x000c
 #define GCR_REV				0x0030
+#define GCR_L2_CONFIG			0x0130
+#define GCR_L2_TAG_ADDR			0x0600
+#define GCR_L2_TAG_ADDR_UPPER		0x0604
+#define GCR_L2_TAG_STATE		0x0608
+#define GCR_L2_TAG_STATE_UPPER		0x060c
+#define GCR_L2_DATA			0x0610
+#define GCR_L2_DATA_UPPER		0x0614
 
 /* GCR_REV CM versions */
 #define GCR_REV_CM3			0x0800
 
+/* GCR_L2_CONFIG fields */
+#define GCR_L2_CONFIG_ASSOC_SHIFT	0
+#define GCR_L2_CONFIG_ASSOC_BITS	8
+#define GCR_L2_CONFIG_LINESZ_SHIFT	8
+#define GCR_L2_CONFIG_LINESZ_BITS	4
+#define GCR_L2_CONFIG_SETSZ_SHIFT	12
+#define GCR_L2_CONFIG_SETSZ_BITS	4
+#define GCR_L2_CONFIG_BYPASS		(1 << 20)
+
+#ifndef __ASSEMBLY__
+
+#include <asm/io.h>
+
+static inline void *mips_cm_base(void)
+{
+	return (void *)CKSEG1ADDR(CONFIG_MIPS_CM_BASE);
+}
+
+static inline unsigned long mips_cm_l2_line_size(void)
+{
+	unsigned long l2conf, line_sz;
+
+	l2conf = __raw_readl(mips_cm_base() + GCR_L2_CONFIG);
+
+	line_sz = l2conf >> GCR_L2_CONFIG_LINESZ_SHIFT;
+	line_sz &= GENMASK(GCR_L2_CONFIG_LINESZ_BITS - 1, 0);
+	return line_sz ? (2 << line_sz) : 0;
+}
+
+#endif /* !__ASSEMBLY__ */
+
 #endif /* __MIPS_ASM_CM_H__ */
diff --git a/arch/mips/include/asm/global_data.h b/arch/mips/include/asm/global_data.h
index 8533b69..0078bbe 100644
--- a/arch/mips/include/asm/global_data.h
+++ b/arch/mips/include/asm/global_data.h
@@ -25,6 +25,9 @@ struct arch_global_data {
 	unsigned short l1i_line_size;
 	unsigned short l1d_line_size;
 #endif
+#ifdef CONFIG_MIPS_L2_CACHE
+	unsigned short l2_line_size;
+#endif
 };
 
 #include <asm-generic/global_data.h>
diff --git a/arch/mips/include/asm/mipsregs.h b/arch/mips/include/asm/mipsregs.h
index cd4f952..b4c2dff 100644
--- a/arch/mips/include/asm/mipsregs.h
+++ b/arch/mips/include/asm/mipsregs.h
@@ -485,9 +485,13 @@
 #define MIPS_CONF1_TLBS_SIZE    (6)
 #define MIPS_CONF1_TLBS         (_ULCAST_(63) << MIPS_CONF1_TLBS_SHIFT)
 
+#define MIPS_CONF2_SA_SHF	0
 #define MIPS_CONF2_SA		(_ULCAST_(15) << 0)
+#define MIPS_CONF2_SL_SHF	4
 #define MIPS_CONF2_SL		(_ULCAST_(15) << 4)
+#define MIPS_CONF2_SS_SHF	8
 #define MIPS_CONF2_SS		(_ULCAST_(15) << 8)
+#define MIPS_CONF2_L2B		(_ULCAST_(1) << 12)
 #define MIPS_CONF2_SU		(_ULCAST_(15) << 12)
 #define MIPS_CONF2_TA		(_ULCAST_(15) << 16)
 #define MIPS_CONF2_TL		(_ULCAST_(15) << 20)
@@ -551,6 +555,7 @@
 #define MIPS_CONF5_MVH		(_ULCAST_(1) << 5)
 #define MIPS_CONF5_FRE		(_ULCAST_(1) << 8)
 #define MIPS_CONF5_UFE		(_ULCAST_(1) << 9)
+#define MIPS_CONF5_L2C		(_ULCAST_(1) << 10)
 #define MIPS_CONF5_MSAEN	(_ULCAST_(1) << 27)
 #define MIPS_CONF5_EVA		(_ULCAST_(1) << 28)
 #define MIPS_CONF5_CV		(_ULCAST_(1) << 29)
diff --git a/arch/mips/lib/cache.c b/arch/mips/lib/cache.c
index d8baf08..bd14ba6 100644
--- a/arch/mips/lib/cache.c
+++ b/arch/mips/lib/cache.c
@@ -7,10 +7,44 @@
 
 #include <common.h>
 #include <asm/cacheops.h>
+#include <asm/cm.h>
 #include <asm/mipsregs.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
+static void probe_l2(void)
+{
+#ifdef CONFIG_MIPS_L2_CACHE
+	unsigned long conf2, sl;
+	bool l2c = false;
+
+	if (!(read_c0_config1() & MIPS_CONF_M))
+		return;
+
+	conf2 = read_c0_config2();
+
+	if (__mips_isa_rev >= 6) {
+		l2c = conf2 & MIPS_CONF_M;
+		if (l2c)
+			l2c = read_c0_config3() & MIPS_CONF_M;
+		if (l2c)
+			l2c = read_c0_config4() & MIPS_CONF_M;
+		if (l2c)
+			l2c = read_c0_config5() & MIPS_CONF5_L2C;
+	}
+
+	if (l2c && config_enabled(CONFIG_MIPS_CM)) {
+		gd->arch.l2_line_size = mips_cm_l2_line_size();
+	} else if (l2c) {
+		/* We don't know how to retrieve L2 config on this system */
+		BUG();
+	} else {
+		sl = (conf2 & MIPS_CONF2_SL) >> MIPS_CONF2_SL_SHF;
+		gd->arch.l2_line_size = sl ? (2 << sl) : 0;
+	}
+#endif
+}
+
 void mips_cache_probe(void)
 {
 #ifdef CONFIG_SYS_CACHE_SIZE_AUTO
@@ -24,6 +58,7 @@ void mips_cache_probe(void)
 	gd->arch.l1i_line_size = il ? (2 << il) : 0;
 	gd->arch.l1d_line_size = dl ? (2 << dl) : 0;
 #endif
+	probe_l2();
 }
 
 static inline unsigned long icache_line_size(void)
@@ -44,6 +79,15 @@ static inline unsigned long dcache_line_size(void)
 #endif
 }
 
+static inline unsigned long scache_line_size(void)
+{
+#ifdef CONFIG_MIPS_L2_CACHE
+	return gd->arch.l2_line_size;
+#else
+	return 0;
+#endif
+}
+
 #define cache_loop(start, end, lsize, ops...) do {			\
 	const void *addr = (const void *)(start & ~(lsize - 1));	\
 	const void *aend = (const void *)((end - 1) & ~(lsize - 1));	\
@@ -60,12 +104,13 @@ void flush_cache(ulong start_addr, ulong size)
 {
 	unsigned long ilsize = icache_line_size();
 	unsigned long dlsize = dcache_line_size();
+	unsigned long slsize = scache_line_size();
 
 	/* aend will be miscalculated when size is zero, so we return here */
 	if (size == 0)
 		return;
 
-	if (ilsize == dlsize) {
+	if ((ilsize == dlsize) && !slsize) {
 		/* flush I-cache & D-cache simultaneously */
 		cache_loop(start_addr, start_addr + size, ilsize,
 			   HIT_WRITEBACK_INV_D, HIT_INVALIDATE_I);
@@ -75,6 +120,11 @@ void flush_cache(ulong start_addr, ulong size)
 	/* flush D-cache */
 	cache_loop(start_addr, start_addr + size, dlsize, HIT_WRITEBACK_INV_D);
 
+	/* flush L2 cache */
+	if (slsize)
+		cache_loop(start_addr, start_addr + size, slsize,
+			   HIT_WRITEBACK_INV_SD);
+
 	/* flush I-cache */
 	cache_loop(start_addr, start_addr + size, ilsize, HIT_INVALIDATE_I);
 }
@@ -82,21 +132,31 @@ void flush_cache(ulong start_addr, ulong size)
 void flush_dcache_range(ulong start_addr, ulong stop)
 {
 	unsigned long lsize = dcache_line_size();
+	unsigned long slsize = scache_line_size();
 
 	/* aend will be miscalculated when size is zero, so we return here */
 	if (start_addr == stop)
 		return;
 
 	cache_loop(start_addr, stop, lsize, HIT_WRITEBACK_INV_D);
+
+	/* flush L2 cache */
+	if (slsize)
+		cache_loop(start_addr, stop, slsize, HIT_WRITEBACK_INV_SD);
 }
 
 void invalidate_dcache_range(ulong start_addr, ulong stop)
 {
 	unsigned long lsize = dcache_line_size();
+	unsigned long slsize = scache_line_size();
 
 	/* aend will be miscalculated when size is zero, so we return here */
 	if (start_addr == stop)
 		return;
 
+	/* invalidate L2 cache */
+	if (slsize)
+		cache_loop(start_addr, stop, slsize, HIT_INVALIDATE_SD);
+
 	cache_loop(start_addr, stop, lsize, HIT_INVALIDATE_D);
 }
diff --git a/arch/mips/lib/cache_init.S b/arch/mips/lib/cache_init.S
index 2df3a82..599a85f 100644
--- a/arch/mips/lib/cache_init.S
+++ b/arch/mips/lib/cache_init.S
@@ -13,6 +13,7 @@
 #include <asm/mipsregs.h>
 #include <asm/addrspace.h>
 #include <asm/cacheops.h>
+#include <asm/cm.h>
 
 #ifndef CONFIG_SYS_MIPS_CACHE_MODE
 #define CONFIG_SYS_MIPS_CACHE_MODE CONF_CM_CACHABLE_NONCOHERENT
@@ -95,14 +96,135 @@
  * with good parity is available. This routine will initialise an area of
  * memory starting at location zero to be used as a source of parity.
  *
+ * Note that this function does not follow the standard calling convention &
+ * may clobber typically callee-saved registers.
+ *
  * RETURNS: N/A
  *
  */
-#define R_IC_SIZE	t2
-#define R_IC_LINE	t8
-#define R_DC_SIZE	t3
-#define R_DC_LINE	t9
+#define R_RETURN	s0
+#define R_IC_SIZE	s1
+#define R_IC_LINE	s2
+#define R_DC_SIZE	s3
+#define R_DC_LINE	s4
+#define R_L2_SIZE	s5
+#define R_L2_LINE	s6
+#define R_L2_BYPASSED	s7
+#define R_L2_L2C	t8
 LEAF(mips_cache_reset)
+	move	R_RETURN, ra
+
+#ifdef CONFIG_MIPS_L2_CACHE
+	/*
+	 * For there to be an L2 present, Config2 must be present. If it isn't
+	 * then we proceed knowing there's no L2 cache.
+	 */
+	move	R_L2_SIZE, zero
+	move	R_L2_LINE, zero
+	move	R_L2_BYPASSED, zero
+	move	R_L2_L2C, zero
+	mfc0	t0, CP0_CONFIG, 1
+	bgez	t0, l2_probe_done
+
+	/*
+	 * From MIPSr6 onwards the L2 cache configuration might not be reported
+	 * by Config2. The Config5.L2C bit indicates whether this is the case,
+	 * and if it is then we need knowledge of where else to look. For cores
+	 * from Imagination Technologies this is a CM GCR.
+	 */
+# if __mips_isa_rev >= 6
+	/* Check that Config5 exists */
+	mfc0	t0, CP0_CONFIG, 2
+	bgez	t0, l2_probe_cop0
+	mfc0	t0, CP0_CONFIG, 3
+	bgez	t0, l2_probe_cop0
+	mfc0	t0, CP0_CONFIG, 4
+	bgez	t0, l2_probe_cop0
+
+	/* Check Config5.L2C is set */
+	mfc0	t0, CP0_CONFIG, 5
+	and	R_L2_L2C, t0, MIPS_CONF5_L2C
+	beqz	R_L2_L2C, l2_probe_cop0
+
+	/* Config5.L2C is set */
+#  ifdef CONFIG_MIPS_CM
+	/* The CM will provide L2 configuration */
+	PTR_LI	t0, CKSEG1ADDR(CONFIG_MIPS_CM_BASE)
+	lw	t1, GCR_L2_CONFIG(t0)
+	bgez	t1, l2_probe_done
+
+	ext	R_L2_LINE, t1, \
+		GCR_L2_CONFIG_LINESZ_SHIFT, GCR_L2_CONFIG_LINESZ_BITS
+	beqz	R_L2_LINE, l2_probe_done
+	li	t2, 2
+	sllv	R_L2_LINE, t2, R_L2_LINE
+
+	ext	t2, t1, GCR_L2_CONFIG_ASSOC_SHIFT, GCR_L2_CONFIG_ASSOC_BITS
+	addiu	t2, t2, 1
+	mul	R_L2_SIZE, R_L2_LINE, t2
+
+	ext	t2, t1, GCR_L2_CONFIG_SETSZ_SHIFT, GCR_L2_CONFIG_SETSZ_BITS
+	sllv	R_L2_SIZE, R_L2_SIZE, t2
+	li	t2, 64
+	mul	R_L2_SIZE, R_L2_SIZE, t2
+
+	/* Bypass the L2 cache so that we can init the L1s early */
+	or	t1, t1, GCR_L2_CONFIG_BYPASS
+	sw	t1, GCR_L2_CONFIG(t0)
+	sync
+	li	R_L2_BYPASSED, 1
+
+	/* Zero the L2 tag registers */
+	sw	zero, GCR_L2_TAG_ADDR(t0)
+	sw	zero, GCR_L2_TAG_ADDR_UPPER(t0)
+	sw	zero, GCR_L2_TAG_STATE(t0)
+	sw	zero, GCR_L2_TAG_STATE_UPPER(t0)
+	sw	zero, GCR_L2_DATA(t0)
+	sw	zero, GCR_L2_DATA_UPPER(t0)
+	sync
+#  else
+	/* We don't know how to retrieve L2 configuration on this system */
+#  endif
+	b	l2_probe_done
+# endif
+
+	/*
+	 * For pre-r6 systems, or r6 systems with Config5.L2C==0, probe the L2
+	 * cache configuration from the cop0 Config2 register.
+	 */
+l2_probe_cop0:
+	mfc0	t0, CP0_CONFIG, 2
+
+	srl	R_L2_LINE, t0, MIPS_CONF2_SL_SHF
+	andi	R_L2_LINE, R_L2_LINE, MIPS_CONF2_SL >> MIPS_CONF2_SL_SHF
+	beqz	R_L2_LINE, l2_probe_done
+	li	t1, 2
+	sllv	R_L2_LINE, t1, R_L2_LINE
+
+	srl	t1, t0, MIPS_CONF2_SA_SHF
+	andi	t1, t1, MIPS_CONF2_SA >> MIPS_CONF2_SA_SHF
+	addiu	t1, t1, 1
+	mul	R_L2_SIZE, R_L2_LINE, t1
+
+	srl	t1, t0, MIPS_CONF2_SS_SHF
+	andi	t1, t1, MIPS_CONF2_SS >> MIPS_CONF2_SS_SHF
+	sllv	R_L2_SIZE, R_L2_SIZE, t1
+	li	t1, 64
+	mul	R_L2_SIZE, R_L2_SIZE, t1
+
+	/* Attempt to bypass the L2 so that we can init the L1s early */
+	or	t0, t0, MIPS_CONF2_L2B
+	mtc0	t0, CP0_CONFIG, 2
+	ehb
+	mfc0	t0, CP0_CONFIG, 2
+	and	R_L2_BYPASSED, t0, MIPS_CONF2_L2B
+
+	/* Zero the L2 tag registers */
+	mtc0	zero, CP0_TAGLO, 4
+	ehb
+l2_probe_done:
+#endif
+
 #ifndef CONFIG_SYS_CACHE_SIZE_AUTO
 	li	R_IC_SIZE, CONFIG_SYS_ICACHE_SIZE
 	li	R_IC_LINE, CONFIG_SYS_ICACHE_LINE_SIZE
@@ -142,11 +264,33 @@ LEAF(mips_cache_reset)
 
 #endif /* CONFIG_SYS_MIPS_CACHE_INIT_RAM_LOAD */
 
+#ifdef CONFIG_MIPS_L2_CACHE
+	/*
+	 * If the L2 is bypassed, init the L1 first so that we can execute the
+	 * rest of the cache initialisation using the L1 instruction cache.
+	 */
+	bnez		R_L2_BYPASSED, l1_init
+
+l2_init:
+	PTR_LI		t0, INDEX_BASE
+	PTR_ADDU	t1, t0, R_L2_SIZE
+1:	cache		INDEX_STORE_TAG_SD, 0(t0)
+	PTR_ADDU	t0, t0, R_L2_LINE
+	bne		t0, t1, 1b
+
+	/*
+	 * If the L2 was bypassed then we already initialised the L1s before
+	 * the L2, so we are now done.
+	 */
+	bnez		R_L2_BYPASSED, return
+#endif
+
 	/*
 	 * The TagLo registers used depend upon the CPU implementation, but the
 	 * architecture requires that it is safe for software to write to both
 	 * TagLo selects 0 & 2 covering supported cases.
 	 */
+l1_init:
 	mtc0		zero, CP0_TAGLO
 	mtc0		zero, CP0_TAGLO, 2
 
@@ -206,8 +350,34 @@ LEAF(mips_cache_reset)
 	PTR_LI		t0, INDEX_BASE
 	cache_loop	t0, t1, R_DC_LINE, INDEX_STORE_TAG_D
 #endif
+3:
+
+#ifdef CONFIG_MIPS_L2_CACHE
+	/* If the L2 isn't bypassed then we're done */
+	beqz		R_L2_BYPASSED, return
 
-3:	jr	ra
+	/* The L2 is bypassed - un-bypass it then initialise it */
+# if __mips_isa_rev >= 6
+	beqz		R_L2_L2C, 1f
+
+	li		t0, CKSEG1ADDR(CONFIG_MIPS_CM_BASE)
+	lw		t1, GCR_L2_CONFIG(t0)
+	xor		t1, t1, GCR_L2_CONFIG_BYPASS
+	sw		t1, GCR_L2_CONFIG(t0)
+	sync
+	ehb
+	b		l2_init
+# endif
+1:
+	mfc0		t0, CP0_CONFIG, 2
+	xor		t0, t0, MIPS_CONF2_L2B
+	mtc0		t0, CP0_CONFIG, 2
+	ehb
+	b		l2_init
+#endif
+
+return:
+	jr	ra
 	END(mips_cache_reset)
 
 /*
