Merge tag 'u-boot-atmel-fixes-2020.07-a' of https://gitlab.denx.de/u-boot/custodians...
[oweals/u-boot.git] / arch / arc / lib / cache.c
index ed8e8e74e2a19a414a5aee265605815b1c91ea22..85651b219cf8eee8f5808f5d33a1f1d673282893 100644 (file)
+// SPDX-License-Identifier: GPL-2.0+
 /*
  * Copyright (C) 2013-2014 Synopsys, Inc. All rights reserved.
- *
- * SPDX-License-Identifier:    GPL-2.0+
  */
 
 #include <config.h>
+#include <common.h>
+#include <cpu_func.h>
+#include <linux/bitops.h>
 #include <linux/compiler.h>
 #include <linux/kernel.h>
+#include <linux/log2.h>
 #include <asm/arcregs.h>
+#include <asm/arc-bcr.h>
 #include <asm/cache.h>
 
-#define CACHE_LINE_MASK                (~(CONFIG_SYS_CACHELINE_SIZE - 1))
+/*
+ * [ NOTE 1 ]:
+ * Data cache (L1 D$ or SL$) entire invalidate operation or data cache disable
+ * operation may result in unexpected behavior and data loss even if we flush
+ * data cache right before invalidation. That may happens if we store any context
+ * on stack (like we store BLINK register on stack before function call).
+ * BLINK register is the register where return address is automatically saved
+ * when we do function call with instructions like 'bl'.
+ *
+ * There is the real example:
+ * We may hang in the next code as we store any BLINK register on stack in
+ * invalidate_dcache_all() function.
+ *
+ * void flush_dcache_all() {
+ *     __dc_entire_op(OP_FLUSH);
+ *     // Other code //
+ * }
+ *
+ * void invalidate_dcache_all() {
+ *     __dc_entire_op(OP_INV);
+ *     // Other code //
+ * }
+ *
+ * void foo(void) {
+ *     flush_dcache_all();
+ *     invalidate_dcache_all();
+ * }
+ *
+ * Now let's see what really happens during that code execution:
+ *
+ * foo()
+ *   |->> call flush_dcache_all
+ *     [return address is saved to BLINK register]
+ *     [push BLINK] (save to stack)              ![point 1]
+ *     |->> call __dc_entire_op(OP_FLUSH)
+ *         [return address is saved to BLINK register]
+ *         [flush L1 D$]
+ *         return [jump to BLINK]
+ *     <<------
+ *     [other flush_dcache_all code]
+ *     [pop BLINK] (get from stack)
+ *     return [jump to BLINK]
+ *   <<------
+ *   |->> call invalidate_dcache_all
+ *     [return address is saved to BLINK register]
+ *     [push BLINK] (save to stack)               ![point 2]
+ *     |->> call __dc_entire_op(OP_FLUSH)
+ *         [return address is saved to BLINK register]
+ *         [invalidate L1 D$]                 ![point 3]
+ *         // Oops!!!
+ *         // We lose return address from invalidate_dcache_all function:
+ *         // we save it to stack and invalidate L1 D$ after that!
+ *         return [jump to BLINK]
+ *     <<------
+ *     [other invalidate_dcache_all code]
+ *     [pop BLINK] (get from stack)
+ *     // we don't have this data in L1 dcache as we invalidated it in [point 3]
+ *     // so we get it from next memory level (for example DDR memory)
+ *     // but in the memory we have value which we save in [point 1], which
+ *     // is return address from flush_dcache_all function (instead of
+ *     // address from current invalidate_dcache_all function which we
+ *     // saved in [point 2] !)
+ *     return [jump to BLINK]
+ *   <<------
+ *   // As BLINK points to invalidate_dcache_all, we call it again and
+ *   // loop forever.
+ *
+ * Fortunately we may fix that by using flush & invalidation of D$ with a single
+ * one instruction (instead of flush and invalidation instructions pair) and
+ * enabling force function inline with '__attribute__((always_inline))' gcc
+ * attribute to avoid any function call (and BLINK store) between cache flush
+ * and disable.
+ *
+ *
+ * [ NOTE 2 ]:
+ * As of today we only support the following cache configurations on ARC.
+ * Other configurations may exist in HW but we don't support it in SW.
+ * Configuration 1:
+ *        ______________________
+ *       |                      |
+ *       |   ARC CPU            |
+ *       |______________________|
+ *        ___|___        ___|___
+ *       |       |      |       |
+ *       | L1 I$ |      | L1 D$ |
+ *       |_______|      |_______|
+ *        on/off         on/off
+ *        ___|______________|____
+ *       |                      |
+ *       |   main memory        |
+ *       |______________________|
+ *
+ * Configuration 2:
+ *        ______________________
+ *       |                      |
+ *       |   ARC CPU            |
+ *       |______________________|
+ *        ___|___        ___|___
+ *       |       |      |       |
+ *       | L1 I$ |      | L1 D$ |
+ *       |_______|      |_______|
+ *        on/off         on/off
+ *        ___|______________|____
+ *       |                      |
+ *       |   L2 (SL$)           |
+ *       |______________________|
+ *          always on (ARCv2, HS <  3.0)
+ *          on/off    (ARCv2, HS >= 3.0)
+ *        ___|______________|____
+ *       |                      |
+ *       |   main memory        |
+ *       |______________________|
+ *
+ * Configuration 3:
+ *        ______________________
+ *       |                      |
+ *       |   ARC CPU            |
+ *       |______________________|
+ *        ___|___        ___|___
+ *       |       |      |       |
+ *       | L1 I$ |      | L1 D$ |
+ *       |_______|      |_______|
+ *        on/off        must be on
+ *        ___|______________|____      _______
+ *       |                      |     |       |
+ *       |   L2 (SL$)           |-----|  IOC  |
+ *       |______________________|     |_______|
+ *          always must be on          on/off
+ *        ___|______________|____
+ *       |                      |
+ *       |   main memory        |
+ *       |______________________|
+ */
+
+DECLARE_GLOBAL_DATA_PTR;
 
 /* Bit values in IC_CTRL */
-#define IC_CTRL_CACHE_DISABLE  (1 << 0)
+#define IC_CTRL_CACHE_DISABLE  BIT(0)
 
 /* Bit values in DC_CTRL */
-#define DC_CTRL_CACHE_DISABLE  (1 << 0)
-#define DC_CTRL_INV_MODE_FLUSH (1 << 6)
-#define DC_CTRL_FLUSH_STATUS   (1 << 8)
-#define CACHE_VER_NUM_MASK     0xF
-#define SLC_CTRL_SB            (1 << 2)
+#define DC_CTRL_CACHE_DISABLE  BIT(0)
+#define DC_CTRL_INV_MODE_FLUSH BIT(6)
+#define DC_CTRL_FLUSH_STATUS   BIT(8)
 
-#define OP_INV         0x1
-#define OP_FLUSH       0x2
-#define OP_INV_IC      0x3
+#define OP_INV                 BIT(0)
+#define OP_FLUSH               BIT(1)
+#define OP_FLUSH_N_INV         (OP_FLUSH | OP_INV)
+
+/* Bit val in SLC_CONTROL */
+#define SLC_CTRL_DIS           0x001
+#define SLC_CTRL_IM            0x040
+#define SLC_CTRL_BUSY          0x100
+#define SLC_CTRL_RGN_OP_INV    0x200
+
+#define CACHE_LINE_MASK                (~(gd->arch.l1_line_sz - 1))
 
-#ifdef CONFIG_ISA_ARCV2
 /*
- * By default that variable will fall into .bss section.
- * But .bss section is not relocated and so it will be initilized before
- * relocation but will be used after being zeroed.
+ * We don't want to use '__always_inline' macro here as it can be redefined
+ * to simple 'inline' in some cases which breaks stuff. See [ NOTE 1 ] for more
+ * details about the reasons we need to use always_inline functions.
  */
-int slc_line_sz __section(".data");
-int slc_exists __section(".data");
+#define inlined_cachefunc       inline __attribute__((always_inline))
 
-static unsigned int __before_slc_op(const int op)
+static inlined_cachefunc void __ic_entire_invalidate(void);
+static inlined_cachefunc void __dc_entire_op(const int cacheop);
+static inlined_cachefunc void __slc_entire_op(const int op);
+static inlined_cachefunc bool ioc_enabled(void);
+
+static inline bool pae_exists(void)
 {
-       unsigned int reg = reg;
+       /* TODO: should we compare mmu version from BCR and from CONFIG? */
+#if (CONFIG_ARC_MMU_VER >= 4)
+       union bcr_mmu_4 mmu4;
 
-       if (op == OP_INV) {
-               /*
-                * IM is set by default and implies Flush-n-inv
-                * Clear it here for vanilla inv
-                */
-               reg = read_aux_reg(ARC_AUX_SLC_CTRL);
-               write_aux_reg(ARC_AUX_SLC_CTRL, reg & ~DC_CTRL_INV_MODE_FLUSH);
-       }
+       mmu4.word = read_aux_reg(ARC_AUX_MMU_BCR);
+
+       if (mmu4.fields.pae)
+               return true;
+#endif /* (CONFIG_ARC_MMU_VER >= 4) */
 
-       return reg;
+       return false;
 }
 
-static void __after_slc_op(const int op, unsigned int reg)
+static inlined_cachefunc bool icache_exists(void)
 {
-       if (op & OP_FLUSH)      /* flush / flush-n-inv both wait */
-               while (read_aux_reg(ARC_AUX_SLC_CTRL) &
-                      DC_CTRL_FLUSH_STATUS)
-                       ;
+       union bcr_di_cache ibcr;
 
-       /* Switch back to default Invalidate mode */
-       if (op == OP_INV)
-               write_aux_reg(ARC_AUX_SLC_CTRL, reg | DC_CTRL_INV_MODE_FLUSH);
+       ibcr.word = read_aux_reg(ARC_BCR_IC_BUILD);
+       return !!ibcr.fields.ver;
 }
 
-static inline void __slc_line_loop(unsigned long paddr, unsigned long sz,
-                                  const int op)
+static inlined_cachefunc bool icache_enabled(void)
 {
-       unsigned int aux_cmd;
-       int num_lines;
+       if (!icache_exists())
+               return false;
 
-#define SLC_LINE_MASK  (~(slc_line_sz - 1))
+       return !(read_aux_reg(ARC_AUX_IC_CTRL) & IC_CTRL_CACHE_DISABLE);
+}
 
-       aux_cmd = op & OP_INV ? ARC_AUX_SLC_IVDL : ARC_AUX_SLC_FLDL;
+static inlined_cachefunc bool dcache_exists(void)
+{
+       union bcr_di_cache dbcr;
 
-       sz += paddr & ~SLC_LINE_MASK;
-       paddr &= SLC_LINE_MASK;
+       dbcr.word = read_aux_reg(ARC_BCR_DC_BUILD);
+       return !!dbcr.fields.ver;
+}
 
-       num_lines = DIV_ROUND_UP(sz, slc_line_sz);
+static inlined_cachefunc bool dcache_enabled(void)
+{
+       if (!dcache_exists())
+               return false;
 
-       while (num_lines-- > 0) {
-               write_aux_reg(aux_cmd, paddr);
-               paddr += slc_line_sz;
+       return !(read_aux_reg(ARC_AUX_DC_CTRL) & DC_CTRL_CACHE_DISABLE);
+}
+
+static inlined_cachefunc bool slc_exists(void)
+{
+       if (is_isa_arcv2()) {
+               union bcr_generic sbcr;
+
+               sbcr.word = read_aux_reg(ARC_BCR_SLC);
+               return !!sbcr.fields.ver;
        }
+
+       return false;
 }
 
-static inline void __slc_entire_op(const int cacheop)
+enum slc_dis_status {
+       ST_SLC_MISSING = 0,
+       ST_SLC_NO_DISABLE_CTRL,
+       ST_SLC_DISABLE_CTRL
+};
+
+/*
+ * ARCv1                                     -> ST_SLC_MISSING
+ * ARCv2 && SLC absent                       -> ST_SLC_MISSING
+ * ARCv2 && SLC exists && SLC version <= 2   -> ST_SLC_NO_DISABLE_CTRL
+ * ARCv2 && SLC exists && SLC version > 2    -> ST_SLC_DISABLE_CTRL
+ */
+static inlined_cachefunc enum slc_dis_status slc_disable_supported(void)
 {
-       int aux;
-       unsigned int ctrl_reg = __before_slc_op(cacheop);
+       if (is_isa_arcv2()) {
+               union bcr_generic sbcr;
+
+               sbcr.word = read_aux_reg(ARC_BCR_SLC);
+               if (sbcr.fields.ver == 0)
+                       return ST_SLC_MISSING;
+               else if (sbcr.fields.ver <= 2)
+                       return ST_SLC_NO_DISABLE_CTRL;
+               else
+                       return ST_SLC_DISABLE_CTRL;
+       }
 
-       if (cacheop & OP_INV)   /* Inv or flush-n-inv use same cmd reg */
-               aux = ARC_AUX_SLC_INVALIDATE;
+       return ST_SLC_MISSING;
+}
+
+static inlined_cachefunc bool __slc_enabled(void)
+{
+       return !(read_aux_reg(ARC_AUX_SLC_CTRL) & SLC_CTRL_DIS);
+}
+
+static inlined_cachefunc void __slc_enable(void)
+{
+       unsigned int ctrl;
+
+       ctrl = read_aux_reg(ARC_AUX_SLC_CTRL);
+       ctrl &= ~SLC_CTRL_DIS;
+       write_aux_reg(ARC_AUX_SLC_CTRL, ctrl);
+}
+
+static inlined_cachefunc void __slc_disable(void)
+{
+       unsigned int ctrl;
+
+       ctrl = read_aux_reg(ARC_AUX_SLC_CTRL);
+       ctrl |= SLC_CTRL_DIS;
+       write_aux_reg(ARC_AUX_SLC_CTRL, ctrl);
+}
+
+static inlined_cachefunc bool slc_enabled(void)
+{
+       enum slc_dis_status slc_status = slc_disable_supported();
+
+       if (slc_status == ST_SLC_MISSING)
+               return false;
+       else if (slc_status == ST_SLC_NO_DISABLE_CTRL)
+               return true;
        else
-               aux = ARC_AUX_SLC_FLUSH;
+               return __slc_enabled();
+}
 
-       write_aux_reg(aux, 0x1);
+static inlined_cachefunc bool slc_data_bypass(void)
+{
+       /*
+        * If L1 data cache is disabled SL$ is bypassed and all load/store
+        * requests are sent directly to main memory.
+        */
+       return !dcache_enabled();
+}
 
-       __after_slc_op(cacheop, ctrl_reg);
+void slc_enable(void)
+{
+       if (slc_disable_supported() != ST_SLC_DISABLE_CTRL)
+               return;
+
+       if (__slc_enabled())
+               return;
+
+       __slc_enable();
 }
 
-static inline void __slc_line_op(unsigned long paddr, unsigned long sz,
-                                const int cacheop)
+/* TODO: warn if we are not able to disable SLC */
+void slc_disable(void)
 {
-       unsigned int ctrl_reg = __before_slc_op(cacheop);
-       __slc_line_loop(paddr, sz, cacheop);
-       __after_slc_op(cacheop, ctrl_reg);
+       if (slc_disable_supported() != ST_SLC_DISABLE_CTRL)
+               return;
+
+       /* we don't support SLC disabling if we use IOC */
+       if (ioc_enabled())
+               return;
+
+       if (!__slc_enabled())
+               return;
+
+       /*
+        * We need to flush L1D$ to guarantee that we won't have any
+        * writeback operations during SLC disabling.
+        */
+       __dc_entire_op(OP_FLUSH);
+       __slc_entire_op(OP_FLUSH_N_INV);
+       __slc_disable();
 }
-#else
-#define __slc_entire_op(cacheop)
-#define __slc_line_op(paddr, sz, cacheop)
-#endif
 
-static inline int icache_exists(void)
+static inlined_cachefunc bool ioc_exists(void)
+{
+       if (is_isa_arcv2()) {
+               union bcr_clust_cfg cbcr;
+
+               cbcr.word = read_aux_reg(ARC_BCR_CLUSTER);
+               return cbcr.fields.c;
+       }
+
+       return false;
+}
+
+static inlined_cachefunc bool ioc_enabled(void)
+{
+       /*
+        * We check only CONFIG option instead of IOC HW state check as IOC
+        * must be disabled by default.
+        */
+       if (is_ioc_enabled())
+               return ioc_exists();
+
+       return false;
+}
+
+static inlined_cachefunc void __slc_entire_op(const int op)
 {
-       /* Check if Instruction Cache is available */
-       if (read_aux_reg(ARC_BCR_IC_BUILD) & CACHE_VER_NUM_MASK)
-               return 1;
+       unsigned int ctrl;
+
+       if (!slc_enabled())
+               return;
+
+       ctrl = read_aux_reg(ARC_AUX_SLC_CTRL);
+
+       if (!(op & OP_FLUSH))           /* i.e. OP_INV */
+               ctrl &= ~SLC_CTRL_IM;   /* clear IM: Disable flush before Inv */
+       else
+               ctrl |= SLC_CTRL_IM;
+
+       write_aux_reg(ARC_AUX_SLC_CTRL, ctrl);
+
+       if (op & OP_INV)        /* Inv or flush-n-inv use same cmd reg */
+               write_aux_reg(ARC_AUX_SLC_INVALIDATE, 0x1);
        else
-               return 0;
+               write_aux_reg(ARC_AUX_SLC_FLUSH, 0x1);
+
+       /* Make sure "busy" bit reports correct stataus, see STAR 9001165532 */
+       read_aux_reg(ARC_AUX_SLC_CTRL);
+
+       /* Important to wait for flush to complete */
+       while (read_aux_reg(ARC_AUX_SLC_CTRL) & SLC_CTRL_BUSY);
+}
+
+static void slc_upper_region_init(void)
+{
+       /*
+        * ARC_AUX_SLC_RGN_START1 and ARC_AUX_SLC_RGN_END1 register exist
+        * only if PAE exists in current HW. So we had to check pae_exist
+        * before using them.
+        */
+       if (!pae_exists())
+               return;
+
+       /*
+        * ARC_AUX_SLC_RGN_END1 and ARC_AUX_SLC_RGN_START1 are always == 0
+        * as we don't use PAE40.
+        */
+       write_aux_reg(ARC_AUX_SLC_RGN_END1, 0);
+       write_aux_reg(ARC_AUX_SLC_RGN_START1, 0);
 }
 
-static inline int dcache_exists(void)
+static void __slc_rgn_op(unsigned long paddr, unsigned long sz, const int op)
 {
-       /* Check if Data Cache is available */
-       if (read_aux_reg(ARC_BCR_DC_BUILD) & CACHE_VER_NUM_MASK)
-               return 1;
+#ifdef CONFIG_ISA_ARCV2
+
+       unsigned int ctrl;
+       unsigned long end;
+
+       if (!slc_enabled())
+               return;
+
+       /*
+        * The Region Flush operation is specified by CTRL.RGN_OP[11..9]
+        *  - b'000 (default) is Flush,
+        *  - b'001 is Invalidate if CTRL.IM == 0
+        *  - b'001 is Flush-n-Invalidate if CTRL.IM == 1
+        */
+       ctrl = read_aux_reg(ARC_AUX_SLC_CTRL);
+
+       /* Don't rely on default value of IM bit */
+       if (!(op & OP_FLUSH))           /* i.e. OP_INV */
+               ctrl &= ~SLC_CTRL_IM;   /* clear IM: Disable flush before Inv */
+       else
+               ctrl |= SLC_CTRL_IM;
+
+       if (op & OP_INV)
+               ctrl |= SLC_CTRL_RGN_OP_INV;    /* Inv or flush-n-inv */
        else
-               return 0;
+               ctrl &= ~SLC_CTRL_RGN_OP_INV;
+
+       write_aux_reg(ARC_AUX_SLC_CTRL, ctrl);
+
+       /*
+        * Lower bits are ignored, no need to clip
+        * END needs to be setup before START (latter triggers the operation)
+        * END can't be same as START, so add (l2_line_sz - 1) to sz
+        */
+       end = paddr + sz + gd->arch.slc_line_sz - 1;
+
+       /*
+        * Upper addresses (ARC_AUX_SLC_RGN_END1 and ARC_AUX_SLC_RGN_START1)
+        * are always == 0 as we don't use PAE40, so we only setup lower ones
+        * (ARC_AUX_SLC_RGN_END and ARC_AUX_SLC_RGN_START)
+        */
+       write_aux_reg(ARC_AUX_SLC_RGN_END, end);
+       write_aux_reg(ARC_AUX_SLC_RGN_START, paddr);
+
+       /* Make sure "busy" bit reports correct stataus, see STAR 9001165532 */
+       read_aux_reg(ARC_AUX_SLC_CTRL);
+
+       while (read_aux_reg(ARC_AUX_SLC_CTRL) & SLC_CTRL_BUSY);
+
+#endif /* CONFIG_ISA_ARCV2 */
 }
 
-void cache_init(void)
+static void arc_ioc_setup(void)
+{
+       /* IOC Aperture start is equal to DDR start */
+       unsigned int ap_base = CONFIG_SYS_SDRAM_BASE;
+       /* IOC Aperture size is equal to DDR size */
+       long ap_size = CONFIG_SYS_SDRAM_SIZE;
+
+       /* Unsupported configuration. See [ NOTE 2 ] for more details. */
+       if (!slc_exists())
+               panic("Try to enable IOC but SLC is not present");
+
+       if (!slc_enabled())
+               panic("Try to enable IOC but SLC is disabled");
+
+       /* Unsupported configuration. See [ NOTE 2 ] for more details. */
+       if (!dcache_enabled())
+               panic("Try to enable IOC but L1 D$ is disabled");
+
+       if (!is_power_of_2(ap_size) || ap_size < 4096)
+               panic("IOC Aperture size must be power of 2 and bigger 4Kib");
+
+       /* IOC Aperture start must be aligned to the size of the aperture */
+       if (ap_base % ap_size != 0)
+               panic("IOC Aperture start must be aligned to the size of the aperture");
+
+       flush_n_invalidate_dcache_all();
+
+       /*
+        * IOC Aperture size decoded as 2 ^ (SIZE + 2) KB,
+        * so setting 0x11 implies 512M, 0x12 implies 1G...
+        */
+       write_aux_reg(ARC_AUX_IO_COH_AP0_SIZE,
+                     order_base_2(ap_size / 1024) - 2);
+
+       write_aux_reg(ARC_AUX_IO_COH_AP0_BASE, ap_base >> 12);
+       write_aux_reg(ARC_AUX_IO_COH_PARTIAL, 1);
+       write_aux_reg(ARC_AUX_IO_COH_ENABLE, 1);
+}
+
+static void read_decode_cache_bcr_arcv2(void)
 {
 #ifdef CONFIG_ISA_ARCV2
-       /* Check if System-Level Cache (SLC) is available */
-       if (read_aux_reg(ARC_BCR_SLC) & CACHE_VER_NUM_MASK) {
-#define LSIZE_OFFSET   4
-#define LSIZE_MASK     3
-               if (read_aux_reg(ARC_AUX_SLC_CONFIG) &
-                   (LSIZE_MASK << LSIZE_OFFSET))
-                       slc_line_sz = 64;
-               else
-                       slc_line_sz = 128;
-               slc_exists = 1;
-       } else {
-               slc_exists = 0;
+
+       union bcr_slc_cfg slc_cfg;
+
+       if (slc_exists()) {
+               slc_cfg.word = read_aux_reg(ARC_AUX_SLC_CONFIG);
+               gd->arch.slc_line_sz = (slc_cfg.fields.lsz == 0) ? 128 : 64;
+
+               /*
+                * We don't support configuration where L1 I$ or L1 D$ is
+                * absent but SL$ exists. See [ NOTE 2 ] for more details.
+                */
+               if (!icache_exists() || !dcache_exists())
+                       panic("Unsupported cache configuration: SLC exists but one of L1 caches is absent");
        }
-#endif
+
+#endif /* CONFIG_ISA_ARCV2 */
 }
 
-int icache_status(void)
+void read_decode_cache_bcr(void)
 {
-       if (!icache_exists())
-               return 0;
+       int dc_line_sz = 0, ic_line_sz = 0;
+       union bcr_di_cache ibcr, dbcr;
+
+       /*
+        * We don't care much about I$ line length really as there're
+        * no per-line ops on I$ instead we only do full invalidation of it
+        * on occasion of relocation and right before jumping to the OS.
+        * Still we check insane config with zero-encoded line length in
+        * presense of version field in I$ BCR. Just in case.
+        */
+       ibcr.word = read_aux_reg(ARC_BCR_IC_BUILD);
+       if (ibcr.fields.ver) {
+               ic_line_sz = 8 << ibcr.fields.line_len;
+               if (!ic_line_sz)
+                       panic("Instruction exists but line length is 0\n");
+       }
 
-       if (read_aux_reg(ARC_AUX_IC_CTRL) & IC_CTRL_CACHE_DISABLE)
-               return 0;
-       else
-               return 1;
+       dbcr.word = read_aux_reg(ARC_BCR_DC_BUILD);
+       if (dbcr.fields.ver) {
+               gd->arch.l1_line_sz = dc_line_sz = 16 << dbcr.fields.line_len;
+               if (!dc_line_sz)
+                       panic("Data cache exists but line length is 0\n");
+       }
+}
+
+void cache_init(void)
+{
+       read_decode_cache_bcr();
+
+       if (is_isa_arcv2())
+               read_decode_cache_bcr_arcv2();
+
+       if (is_isa_arcv2() && ioc_enabled())
+               arc_ioc_setup();
+
+       if (is_isa_arcv2() && slc_exists())
+               slc_upper_region_init();
+}
+
+int icache_status(void)
+{
+       return icache_enabled();
 }
 
 void icache_enable(void)
@@ -168,35 +586,48 @@ void icache_enable(void)
 
 void icache_disable(void)
 {
-       if (icache_exists())
-               write_aux_reg(ARC_AUX_IC_CTRL, read_aux_reg(ARC_AUX_IC_CTRL) |
-                             IC_CTRL_CACHE_DISABLE);
+       if (!icache_exists())
+               return;
+
+       __ic_entire_invalidate();
+
+       write_aux_reg(ARC_AUX_IC_CTRL, read_aux_reg(ARC_AUX_IC_CTRL) |
+                     IC_CTRL_CACHE_DISABLE);
 }
 
-#ifndef CONFIG_SYS_DCACHE_OFF
-void invalidate_icache_all(void)
+/* IC supports only invalidation */
+static inlined_cachefunc void __ic_entire_invalidate(void)
 {
+       if (!icache_enabled())
+               return;
+
        /* Any write to IC_IVIC register triggers invalidation of entire I$ */
-       if (icache_status()) {
-               write_aux_reg(ARC_AUX_IC_IVIC, 1);
-               read_aux_reg(ARC_AUX_IC_CTRL);  /* blocks */
-       }
+       write_aux_reg(ARC_AUX_IC_IVIC, 1);
+       /*
+        * As per ARC HS databook (see chapter 5.3.3.2)
+        * it is required to add 3 NOPs after each write to IC_IVIC.
+        */
+       __builtin_arc_nop();
+       __builtin_arc_nop();
+       __builtin_arc_nop();
+       read_aux_reg(ARC_AUX_IC_CTRL);  /* blocks */
 }
-#else
+
 void invalidate_icache_all(void)
 {
+       __ic_entire_invalidate();
+
+       /*
+        * If SL$ is bypassed for data it is used only for instructions,
+        * so we need to invalidate it too.
+        */
+       if (is_isa_arcv2() && slc_data_bypass())
+               __slc_entire_op(OP_INV);
 }
-#endif
 
 int dcache_status(void)
 {
-       if (!dcache_exists())
-               return 0;
-
-       if (read_aux_reg(ARC_AUX_DC_CTRL) & DC_CTRL_CACHE_DISABLE)
-               return 0;
-       else
-               return 1;
+       return dcache_enabled();
 }
 
 void dcache_enable(void)
@@ -213,81 +644,74 @@ void dcache_disable(void)
        if (!dcache_exists())
                return;
 
+       __dc_entire_op(OP_FLUSH_N_INV);
+
+       /*
+        * As SLC will be bypassed for data after L1 D$ disable we need to
+        * flush it first before L1 D$ disable. Also we invalidate SLC to
+        * avoid any inconsistent data problems after enabling L1 D$ again with
+        * dcache_enable function.
+        */
+       if (is_isa_arcv2())
+               __slc_entire_op(OP_FLUSH_N_INV);
+
        write_aux_reg(ARC_AUX_DC_CTRL, read_aux_reg(ARC_AUX_DC_CTRL) |
                      DC_CTRL_CACHE_DISABLE);
 }
 
-#ifndef CONFIG_SYS_DCACHE_OFF
-/*
- * Common Helper for Line Operations on {I,D}-Cache
- */
-static inline void __cache_line_loop(unsigned long paddr, unsigned long sz,
-                                    const int cacheop)
+/* Common Helper for Line Operations on D-cache */
+static inline void __dcache_line_loop(unsigned long paddr, unsigned long sz,
+                                     const int cacheop)
 {
        unsigned int aux_cmd;
-#if (CONFIG_ARC_MMU_VER == 3)
-       unsigned int aux_tag;
-#endif
        int num_lines;
 
-       if (cacheop == OP_INV_IC) {
-               aux_cmd = ARC_AUX_IC_IVIL;
-#if (CONFIG_ARC_MMU_VER == 3)
-               aux_tag = ARC_AUX_IC_PTAG;
-#endif
-       } else {
-               /* d$ cmd: INV (discard or wback-n-discard) OR FLUSH (wback) */
-               aux_cmd = cacheop & OP_INV ? ARC_AUX_DC_IVDL : ARC_AUX_DC_FLDL;
-#if (CONFIG_ARC_MMU_VER == 3)
-               aux_tag = ARC_AUX_DC_PTAG;
-#endif
-       }
+       /* d$ cmd: INV (discard or wback-n-discard) OR FLUSH (wback) */
+       aux_cmd = cacheop & OP_INV ? ARC_AUX_DC_IVDL : ARC_AUX_DC_FLDL;
 
        sz += paddr & ~CACHE_LINE_MASK;
        paddr &= CACHE_LINE_MASK;
 
-       num_lines = DIV_ROUND_UP(sz, CONFIG_SYS_CACHELINE_SIZE);
+       num_lines = DIV_ROUND_UP(sz, gd->arch.l1_line_sz);
 
        while (num_lines-- > 0) {
 #if (CONFIG_ARC_MMU_VER == 3)
-               write_aux_reg(aux_tag, paddr);
+               write_aux_reg(ARC_AUX_DC_PTAG, paddr);
 #endif
                write_aux_reg(aux_cmd, paddr);
-               paddr += CONFIG_SYS_CACHELINE_SIZE;
+               paddr += gd->arch.l1_line_sz;
        }
 }
 
-static unsigned int __before_dc_op(const int op)
+static inlined_cachefunc void __before_dc_op(const int op)
 {
-       unsigned int reg;
+       unsigned int ctrl;
 
-       if (op == OP_INV) {
-               /*
-                * IM is set by default and implies Flush-n-inv
-                * Clear it here for vanilla inv
-                */
-               reg = read_aux_reg(ARC_AUX_DC_CTRL);
-               write_aux_reg(ARC_AUX_DC_CTRL, reg & ~DC_CTRL_INV_MODE_FLUSH);
-       }
+       ctrl = read_aux_reg(ARC_AUX_DC_CTRL);
 
-       return reg;
+       /* IM bit implies flush-n-inv, instead of vanilla inv */
+       if (op == OP_INV)
+               ctrl &= ~DC_CTRL_INV_MODE_FLUSH;
+       else
+               ctrl |= DC_CTRL_INV_MODE_FLUSH;
+
+       write_aux_reg(ARC_AUX_DC_CTRL, ctrl);
 }
 
-static void __after_dc_op(const int op, unsigned int reg)
+static inlined_cachefunc void __after_dc_op(const int op)
 {
        if (op & OP_FLUSH)      /* flush / flush-n-inv both wait */
-               while (read_aux_reg(ARC_AUX_DC_CTRL) & DC_CTRL_FLUSH_STATUS)
-                       ;
-
-       /* Switch back to default Invalidate mode */
-       if (op == OP_INV)
-               write_aux_reg(ARC_AUX_DC_CTRL, reg | DC_CTRL_INV_MODE_FLUSH);
+               while (read_aux_reg(ARC_AUX_DC_CTRL) & DC_CTRL_FLUSH_STATUS);
 }
 
-static inline void __dc_entire_op(const int cacheop)
+static inlined_cachefunc void __dc_entire_op(const int cacheop)
 {
        int aux;
-       unsigned int ctrl_reg = __before_dc_op(cacheop);
+
+       if (!dcache_enabled())
+               return;
+
+       __before_dc_op(cacheop);
 
        if (cacheop & OP_INV)   /* Inv or flush-n-inv use same cmd reg */
                aux = ARC_AUX_DC_IVDC;
@@ -296,37 +720,54 @@ static inline void __dc_entire_op(const int cacheop)
 
        write_aux_reg(aux, 0x1);
 
-       __after_dc_op(cacheop, ctrl_reg);
+       __after_dc_op(cacheop);
 }
 
 static inline void __dc_line_op(unsigned long paddr, unsigned long sz,
                                const int cacheop)
 {
-       unsigned int ctrl_reg = __before_dc_op(cacheop);
-       __cache_line_loop(paddr, sz, cacheop);
-       __after_dc_op(cacheop, ctrl_reg);
+       if (!dcache_enabled())
+               return;
+
+       __before_dc_op(cacheop);
+       __dcache_line_loop(paddr, sz, cacheop);
+       __after_dc_op(cacheop);
 }
-#else
-#define __dc_entire_op(cacheop)
-#define __dc_line_op(paddr, sz, cacheop)
-#endif /* !CONFIG_SYS_DCACHE_OFF */
 
 void invalidate_dcache_range(unsigned long start, unsigned long end)
 {
-       __dc_line_op(start, end - start, OP_INV);
-#ifdef CONFIG_ISA_ARCV2
-       if (slc_exists)
-               __slc_line_op(start, end - start, OP_INV);
-#endif
+       if (start >= end)
+               return;
+
+       /*
+        * ARCv1                                 -> call __dc_line_op
+        * ARCv2 && L1 D$ disabled               -> nothing
+        * ARCv2 && L1 D$ enabled && IOC enabled -> nothing
+        * ARCv2 && L1 D$ enabled && no IOC      -> call __dc_line_op; call __slc_rgn_op
+        */
+       if (!is_isa_arcv2() || !ioc_enabled())
+               __dc_line_op(start, end - start, OP_INV);
+
+       if (is_isa_arcv2() && !ioc_enabled() && !slc_data_bypass())
+               __slc_rgn_op(start, end - start, OP_INV);
 }
 
 void flush_dcache_range(unsigned long start, unsigned long end)
 {
-       __dc_line_op(start, end - start, OP_FLUSH);
-#ifdef CONFIG_ISA_ARCV2
-       if (slc_exists)
-               __slc_line_op(start, end - start, OP_FLUSH);
-#endif
+       if (start >= end)
+               return;
+
+       /*
+        * ARCv1                                 -> call __dc_line_op
+        * ARCv2 && L1 D$ disabled               -> nothing
+        * ARCv2 && L1 D$ enabled && IOC enabled -> nothing
+        * ARCv2 && L1 D$ enabled && no IOC      -> call __dc_line_op; call __slc_rgn_op
+        */
+       if (!is_isa_arcv2() || !ioc_enabled())
+               __dc_line_op(start, end - start, OP_FLUSH);
+
+       if (is_isa_arcv2() && !ioc_enabled() && !slc_data_bypass())
+               __slc_rgn_op(start, end - start, OP_FLUSH);
 }
 
 void flush_cache(unsigned long start, unsigned long size)
@@ -334,20 +775,47 @@ void flush_cache(unsigned long start, unsigned long size)
        flush_dcache_range(start, start + size);
 }
 
-void invalidate_dcache_all(void)
+/*
+ * As invalidate_dcache_all() is not used in generic U-Boot code and as we
+ * don't need it in arch/arc code alone (invalidate without flush) we implement
+ * flush_n_invalidate_dcache_all (flush and invalidate in 1 operation) because
+ * it's much safer. See [ NOTE 1 ] for more details.
+ */
+void flush_n_invalidate_dcache_all(void)
 {
-       __dc_entire_op(OP_INV);
-#ifdef CONFIG_ISA_ARCV2
-       if (slc_exists)
-               __slc_entire_op(OP_INV);
-#endif
+       __dc_entire_op(OP_FLUSH_N_INV);
+
+       if (is_isa_arcv2() && !slc_data_bypass())
+               __slc_entire_op(OP_FLUSH_N_INV);
 }
 
 void flush_dcache_all(void)
 {
        __dc_entire_op(OP_FLUSH);
-#ifdef CONFIG_ISA_ARCV2
-       if (slc_exists)
+
+       if (is_isa_arcv2() && !slc_data_bypass())
                __slc_entire_op(OP_FLUSH);
-#endif
+}
+
+/*
+ * This is function to cleanup all caches (and therefore sync I/D caches) which
+ * can be used for cleanup before linux launch or to sync caches during
+ * relocation.
+ */
+void sync_n_cleanup_cache_all(void)
+{
+       __dc_entire_op(OP_FLUSH_N_INV);
+
+       /*
+        * If SL$ is bypassed for data it is used only for instructions,
+        * and we shouldn't flush it. So invalidate it instead of flush_n_inv.
+        */
+       if (is_isa_arcv2()) {
+               if (slc_data_bypass())
+                       __slc_entire_op(OP_INV);
+               else
+                       __slc_entire_op(OP_FLUSH_N_INV);
+       }
+
+       __ic_entire_invalidate();
 }