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 6f52877643cbd167a33bc8f6a6e134818a012532..85651b219cf8eee8f5808f5d33a1f1d673282893 100644 (file)
@@ -5,6 +5,8 @@
 
 #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>
@@ -88,8 +90,7 @@
  *
  * [ NOTE 2 ]:
  * As of today we only support the following cache configurations on ARC.
- * Other configurations may exist in HW (for example, since version 3.0 HS
- * supports SL$ (L2 system level cache) disable) but we don't support it in SW.
+ * Other configurations may exist in HW but we don't support it in SW.
  * Configuration 1:
  *        ______________________
  *       |                      |
  *       |                      |
  *       |   L2 (SL$)           |
  *       |______________________|
- *          always must be on
+ *          always on (ARCv2, HS <  3.0)
+ *          on/off    (ARCv2, HS >= 3.0)
  *        ___|______________|____
  *       |                      |
  *       |   main memory        |
@@ -177,6 +179,8 @@ DECLARE_GLOBAL_DATA_PTR;
 
 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)
 {
@@ -237,6 +241,70 @@ static inlined_cachefunc bool slc_exists(void)
        return false;
 }
 
+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)
+{
+       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;
+       }
+
+       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
+               return __slc_enabled();
+}
+
 static inlined_cachefunc bool slc_data_bypass(void)
 {
        /*
@@ -246,7 +314,40 @@ static inlined_cachefunc bool slc_data_bypass(void)
        return !dcache_enabled();
 }
 
-static inline bool ioc_exists(void)
+void slc_enable(void)
+{
+       if (slc_disable_supported() != ST_SLC_DISABLE_CTRL)
+               return;
+
+       if (__slc_enabled())
+               return;
+
+       __slc_enable();
+}
+
+/* TODO: warn if we are not able to disable SLC */
+void slc_disable(void)
+{
+       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();
+}
+
+static inlined_cachefunc bool ioc_exists(void)
 {
        if (is_isa_arcv2()) {
                union bcr_clust_cfg cbcr;
@@ -258,7 +359,7 @@ static inline bool ioc_exists(void)
        return false;
 }
 
-static inline bool ioc_enabled(void)
+static inlined_cachefunc bool ioc_enabled(void)
 {
        /*
         * We check only CONFIG option instead of IOC HW state check as IOC
@@ -274,7 +375,7 @@ static inlined_cachefunc void __slc_entire_op(const int op)
 {
        unsigned int ctrl;
 
-       if (!slc_exists())
+       if (!slc_enabled())
                return;
 
        ctrl = read_aux_reg(ARC_AUX_SLC_CTRL);
@@ -323,7 +424,7 @@ static void __slc_rgn_op(unsigned long paddr, unsigned long sz, const int op)
        unsigned int ctrl;
        unsigned long end;
 
-       if (!slc_exists())
+       if (!slc_enabled())
                return;
 
        /*
@@ -381,6 +482,9 @@ static void arc_ioc_setup(void)
        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");
@@ -432,9 +536,16 @@ void read_decode_cache_bcr(void)
        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) {
-               gd->arch.l1_line_sz = ic_line_sz = 8 << ibcr.fields.line_len;
+               ic_line_sz = 8 << ibcr.fields.line_len;
                if (!ic_line_sz)
                        panic("Instruction exists but line length is 0\n");
        }
@@ -445,9 +556,6 @@ void read_decode_cache_bcr(void)
                if (!dc_line_sz)
                        panic("Data cache exists but line length is 0\n");
        }
-
-       if (ic_line_sz && dc_line_sz && (ic_line_sz != dc_line_sz))
-               panic("Instruction and data cache line lengths differ\n");
 }
 
 void cache_init(void)
@@ -512,8 +620,6 @@ void invalidate_icache_all(void)
        /*
         * If SL$ is bypassed for data it is used only for instructions,
         * so we need to invalidate it too.
-        * TODO: HS 3.0 supports SLC disable so we need to check slc
-        * enable/disable status here.
         */
        if (is_isa_arcv2() && slc_data_bypass())
                __slc_entire_op(OP_INV);