#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__ */
 
 
 #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
        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)
 #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));    \
 {
        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);
        /* 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);
 }
 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);
 }
 
 #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
  * 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
 
 #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, l2_unbypass
+#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
 
        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
+
+       /* The L2 is bypassed - go initialise it */
+       b               l2_init
+
+l2_unbypass:
+# 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               2f
+# endif
+1:     mfc0            t0, CP0_CONFIG, 2
+       xor             t0, t0, MIPS_CONF2_L2B
+       mtc0            t0, CP0_CONFIG, 2
+       ehb
+
+2:
+#endif
 
-3:     jr      ra
+return:
+       jr      ra
        END(mips_cache_reset)
 
 /*