root/arch/microblaze/kernel/cpu/cache.c
/*
 * Cache control for MicroBlaze cache memories
 *
 * Copyright (C) 2007-2009 Michal Simek <monstr@monstr.eu>
 * Copyright (C) 2007-2009 PetaLogix
 * Copyright (C) 2007-2009 John Williams <john.williams@petalogix.com>
 *
 * This file is subject to the terms and conditions of the GNU General
 * Public License. See the file COPYING in the main directory of this
 * archive for more details.
 */

#include <asm/cacheflush.h>
#include <linux/cache.h>
#include <asm/cpuinfo.h>
#include <asm/pvr.h>

static inline void __enable_icache_msr(void)
{
        __asm__ __volatile__ ("  msrset r0, %0;"        \
                                "nop;"                  \
                        : : "i" (MSR_ICE) : "memory");
}

static inline void __disable_icache_msr(void)
{
        __asm__ __volatile__ ("  msrclr r0, %0;"        \
                                "nop;"                  \
                        : : "i" (MSR_ICE) : "memory");
}

static inline void __enable_dcache_msr(void)
{
        __asm__ __volatile__ ("  msrset r0, %0;"        \
                                "nop;"                  \
                        : : "i" (MSR_DCE) : "memory");
}

static inline void __disable_dcache_msr(void)
{
        __asm__ __volatile__ ("  msrclr r0, %0;"        \
                                "nop; "                 \
                        : : "i" (MSR_DCE) : "memory");
}

static inline void __enable_icache_nomsr(void)
{
        __asm__ __volatile__ ("  mfs    r12, rmsr;"     \
                                "nop;"                  \
                                "ori    r12, r12, %0;"  \
                                "mts    rmsr, r12;"     \
                                "nop;"                  \
                        : : "i" (MSR_ICE) : "memory", "r12");
}

static inline void __disable_icache_nomsr(void)
{
        __asm__ __volatile__ ("  mfs    r12, rmsr;"     \
                                "nop;"                  \
                                "andi   r12, r12, ~%0;" \
                                "mts    rmsr, r12;"     \
                                "nop;"                  \
                        : : "i" (MSR_ICE) : "memory", "r12");
}

static inline void __enable_dcache_nomsr(void)
{
        __asm__ __volatile__ ("  mfs    r12, rmsr;"     \
                                "nop;"                  \
                                "ori    r12, r12, %0;"  \
                                "mts    rmsr, r12;"     \
                                "nop;"                  \
                        : : "i" (MSR_DCE) : "memory", "r12");
}

static inline void __disable_dcache_nomsr(void)
{
        __asm__ __volatile__ ("  mfs    r12, rmsr;"     \
                                "nop;"                  \
                                "andi   r12, r12, ~%0;" \
                                "mts    rmsr, r12;"     \
                                "nop;"                  \
                        : : "i" (MSR_DCE) : "memory", "r12");
}


/* Helper macro for computing the limits of cache range loops
 *
 * End address can be unaligned which is OK for C implementation.
 * ASM implementation align it in ASM macros
 */
#define CACHE_LOOP_LIMITS(start, end, cache_line_length, cache_size)    \
do {                                                                    \
        int align = ~(cache_line_length - 1);                           \
        if (start <  UINT_MAX - cache_size)                             \
                end = min(start + cache_size, end);                     \
        start &= align;                                                 \
} while (0)

/*
 * Helper macro to loop over the specified cache_size/line_length and
 * execute 'op' on that cacheline
 */
#define CACHE_ALL_LOOP(cache_size, line_length, op)                     \
do {                                                                    \
        unsigned int len = cache_size - line_length;                    \
        int step = -line_length;                                        \
        WARN_ON(step >= 0);                                             \
                                                                        \
        __asm__ __volatile__ (" 1:      " #op " %0, r0;"                \
                                        "bgtid   %0, 1b;"               \
                                        "addk    %0, %0, %1;"           \
                                        : : "r" (len), "r" (step)       \
                                        : "memory");                    \
} while (0)

/* Used for wdc.flush/clear which can use rB for offset which is not possible
 * to use for simple wdc or wic.
 *
 * start address is cache aligned
 * end address is not aligned, if end is aligned then I have to subtract
 * cacheline length because I can't flush/invalidate the next cacheline.
 * If is not, I align it because I will flush/invalidate whole line.
 */
#define CACHE_RANGE_LOOP_2(start, end, line_length, op)                 \
do {                                                                    \
        int step = -line_length;                                        \
        int align = ~(line_length - 1);                                 \
        int count;                                                      \
        end = ((end & align) == end) ? end - line_length : end & align; \
        count = end - start;                                            \
        WARN_ON(count < 0);                                             \
                                                                        \
        __asm__ __volatile__ (" 1:      " #op " %0, %1;"                \
                                        "bgtid  %1, 1b;"                \
                                        "addk   %1, %1, %2;"            \
                                        : : "r" (start), "r" (count),   \
                                        "r" (step) : "memory");         \
} while (0)

/* It is used only first parameter for OP - for wic, wdc */
#define CACHE_RANGE_LOOP_1(start, end, line_length, op)                 \
do {                                                                    \
        unsigned int volatile temp = 0;                                         \
        unsigned int align = ~(line_length - 1);                                        \
        end = ((end & align) == end) ? end - line_length : end & align; \
        WARN_ON(end < start);                                   \
                                                                        \
        __asm__ __volatile__ (" 1:      " #op " %1, r0;"                \
                                        "cmpu   %0, %1, %2;"            \
                                        "bgtid  %0, 1b;"                \
                                        "addk   %1, %1, %3;"            \
                                : : "r" (temp), "r" (start), "r" (end), \
                                        "r" (line_length) : "memory");  \
} while (0)

#define ASM_LOOP

static void __flush_icache_range_msr_irq(unsigned long start, unsigned long end)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);

        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.icache_line_length, cpuinfo.icache_size);

        local_irq_save(flags);
        __disable_icache_msr();

#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_1(start, end, cpuinfo.icache_line_length, wic);
#else
        for (i = start; i < end; i += cpuinfo.icache_line_length)
                __asm__ __volatile__ ("wic      %0, r0;"        \
                                : : "r" (i));
#endif
        __enable_icache_msr();
        local_irq_restore(flags);
}

static void __flush_icache_range_nomsr_irq(unsigned long start,
                                unsigned long end)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);

        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.icache_line_length, cpuinfo.icache_size);

        local_irq_save(flags);
        __disable_icache_nomsr();

#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_1(start, end, cpuinfo.icache_line_length, wic);
#else
        for (i = start; i < end; i += cpuinfo.icache_line_length)
                __asm__ __volatile__ ("wic      %0, r0;"        \
                                : : "r" (i));
#endif

        __enable_icache_nomsr();
        local_irq_restore(flags);
}

static void __flush_icache_range_noirq(unsigned long start,
                                unsigned long end)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);

        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.icache_line_length, cpuinfo.icache_size);
#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_1(start, end, cpuinfo.icache_line_length, wic);
#else
        for (i = start; i < end; i += cpuinfo.icache_line_length)
                __asm__ __volatile__ ("wic      %0, r0;"        \
                                : : "r" (i));
#endif
}

static void __flush_icache_all_msr_irq(void)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);

        local_irq_save(flags);
        __disable_icache_msr();
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.icache_size, cpuinfo.icache_line_length, wic);
#else
        for (i = 0; i < cpuinfo.icache_size;
                 i += cpuinfo.icache_line_length)
                        __asm__ __volatile__ ("wic      %0, r0;" \
                                        : : "r" (i));
#endif
        __enable_icache_msr();
        local_irq_restore(flags);
}

static void __flush_icache_all_nomsr_irq(void)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);

        local_irq_save(flags);
        __disable_icache_nomsr();
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.icache_size, cpuinfo.icache_line_length, wic);
#else
        for (i = 0; i < cpuinfo.icache_size;
                 i += cpuinfo.icache_line_length)
                        __asm__ __volatile__ ("wic      %0, r0;" \
                                        : : "r" (i));
#endif
        __enable_icache_nomsr();
        local_irq_restore(flags);
}

static void __flush_icache_all_noirq(void)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.icache_size, cpuinfo.icache_line_length, wic);
#else
        for (i = 0; i < cpuinfo.icache_size;
                 i += cpuinfo.icache_line_length)
                        __asm__ __volatile__ ("wic      %0, r0;" \
                                        : : "r" (i));
#endif
}

static void __invalidate_dcache_all_msr_irq(void)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);

        local_irq_save(flags);
        __disable_dcache_msr();
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc);
#else
        for (i = 0; i < cpuinfo.dcache_size;
                 i += cpuinfo.dcache_line_length)
                        __asm__ __volatile__ ("wdc      %0, r0;" \
                                        : : "r" (i));
#endif
        __enable_dcache_msr();
        local_irq_restore(flags);
}

static void __invalidate_dcache_all_nomsr_irq(void)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);

        local_irq_save(flags);
        __disable_dcache_nomsr();
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc);
#else
        for (i = 0; i < cpuinfo.dcache_size;
                 i += cpuinfo.dcache_line_length)
                        __asm__ __volatile__ ("wdc      %0, r0;" \
                                        : : "r" (i));
#endif
        __enable_dcache_nomsr();
        local_irq_restore(flags);
}

static void __invalidate_dcache_all_noirq_wt(void)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length, wdc);
#else
        for (i = 0; i < cpuinfo.dcache_size;
                 i += cpuinfo.dcache_line_length)
                        __asm__ __volatile__ ("wdc      %0, r0;" \
                                        : : "r" (i));
#endif
}

/*
 * FIXME It is blindly invalidation as is expected
 * but can't be called on noMMU in microblaze_cache_init below
 *
 * MS: noMMU kernel won't boot if simple wdc is used
 * The reason should be that there are discared data which kernel needs
 */
static void __invalidate_dcache_all_wb(void)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length,
                                        wdc);
#else
        for (i = 0; i < cpuinfo.dcache_size;
                 i += cpuinfo.dcache_line_length)
                        __asm__ __volatile__ ("wdc      %0, r0;" \
                                        : : "r" (i));
#endif
}

static void __invalidate_dcache_range_wb(unsigned long start,
                                                unsigned long end)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);

        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.dcache_line_length, cpuinfo.dcache_size);
#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.clear);
#else
        for (i = start; i < end; i += cpuinfo.dcache_line_length)
                __asm__ __volatile__ ("wdc.clear        %0, r0;"        \
                                : : "r" (i));
#endif
}

static void __invalidate_dcache_range_nomsr_wt(unsigned long start,
                                                        unsigned long end)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);
        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.dcache_line_length, cpuinfo.dcache_size);

#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc);
#else
        for (i = start; i < end; i += cpuinfo.dcache_line_length)
                __asm__ __volatile__ ("wdc      %0, r0;"        \
                                : : "r" (i));
#endif
}

static void __invalidate_dcache_range_msr_irq_wt(unsigned long start,
                                                        unsigned long end)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);
        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.dcache_line_length, cpuinfo.dcache_size);

        local_irq_save(flags);
        __disable_dcache_msr();

#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc);
#else
        for (i = start; i < end; i += cpuinfo.dcache_line_length)
                __asm__ __volatile__ ("wdc      %0, r0;"        \
                                : : "r" (i));
#endif

        __enable_dcache_msr();
        local_irq_restore(flags);
}

static void __invalidate_dcache_range_nomsr_irq(unsigned long start,
                                                        unsigned long end)
{
        unsigned long flags;
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);

        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.dcache_line_length, cpuinfo.dcache_size);

        local_irq_save(flags);
        __disable_dcache_nomsr();

#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_1(start, end, cpuinfo.dcache_line_length, wdc);
#else
        for (i = start; i < end; i += cpuinfo.dcache_line_length)
                __asm__ __volatile__ ("wdc      %0, r0;"        \
                                : : "r" (i));
#endif

        __enable_dcache_nomsr();
        local_irq_restore(flags);
}

static void __flush_dcache_all_wb(void)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s\n", __func__);
#ifdef ASM_LOOP
        CACHE_ALL_LOOP(cpuinfo.dcache_size, cpuinfo.dcache_line_length,
                                wdc.flush);
#else
        for (i = 0; i < cpuinfo.dcache_size;
                 i += cpuinfo.dcache_line_length)
                        __asm__ __volatile__ ("wdc.flush        %0, r0;" \
                                        : : "r" (i));
#endif
}

static void __flush_dcache_range_wb(unsigned long start, unsigned long end)
{
#ifndef ASM_LOOP
        int i;
#endif
        pr_debug("%s: start 0x%x, end 0x%x\n", __func__,
                                (unsigned int)start, (unsigned int) end);

        CACHE_LOOP_LIMITS(start, end,
                        cpuinfo.dcache_line_length, cpuinfo.dcache_size);
#ifdef ASM_LOOP
        CACHE_RANGE_LOOP_2(start, end, cpuinfo.dcache_line_length, wdc.flush);
#else
        for (i = start; i < end; i += cpuinfo.dcache_line_length)
                __asm__ __volatile__ ("wdc.flush        %0, r0;"        \
                                : : "r" (i));
#endif
}

/* struct for wb caches and for wt caches */
struct scache *mbc;

/* new wb cache model */
static const struct scache wb_msr = {
        .ie = __enable_icache_msr,
        .id = __disable_icache_msr,
        .ifl = __flush_icache_all_noirq,
        .iflr = __flush_icache_range_noirq,
        .iin = __flush_icache_all_noirq,
        .iinr = __flush_icache_range_noirq,
        .de = __enable_dcache_msr,
        .dd = __disable_dcache_msr,
        .dfl = __flush_dcache_all_wb,
        .dflr = __flush_dcache_range_wb,
        .din = __invalidate_dcache_all_wb,
        .dinr = __invalidate_dcache_range_wb,
};

/* There is only difference in ie, id, de, dd functions */
static const struct scache wb_nomsr = {
        .ie = __enable_icache_nomsr,
        .id = __disable_icache_nomsr,
        .ifl = __flush_icache_all_noirq,
        .iflr = __flush_icache_range_noirq,
        .iin = __flush_icache_all_noirq,
        .iinr = __flush_icache_range_noirq,
        .de = __enable_dcache_nomsr,
        .dd = __disable_dcache_nomsr,
        .dfl = __flush_dcache_all_wb,
        .dflr = __flush_dcache_range_wb,
        .din = __invalidate_dcache_all_wb,
        .dinr = __invalidate_dcache_range_wb,
};

/* Old wt cache model with disabling irq and turn off cache */
static const struct scache wt_msr = {
        .ie = __enable_icache_msr,
        .id = __disable_icache_msr,
        .ifl = __flush_icache_all_msr_irq,
        .iflr = __flush_icache_range_msr_irq,
        .iin = __flush_icache_all_msr_irq,
        .iinr = __flush_icache_range_msr_irq,
        .de = __enable_dcache_msr,
        .dd = __disable_dcache_msr,
        .dfl = __invalidate_dcache_all_msr_irq,
        .dflr = __invalidate_dcache_range_msr_irq_wt,
        .din = __invalidate_dcache_all_msr_irq,
        .dinr = __invalidate_dcache_range_msr_irq_wt,
};

static const struct scache wt_nomsr = {
        .ie = __enable_icache_nomsr,
        .id = __disable_icache_nomsr,
        .ifl = __flush_icache_all_nomsr_irq,
        .iflr = __flush_icache_range_nomsr_irq,
        .iin = __flush_icache_all_nomsr_irq,
        .iinr = __flush_icache_range_nomsr_irq,
        .de = __enable_dcache_nomsr,
        .dd = __disable_dcache_nomsr,
        .dfl = __invalidate_dcache_all_nomsr_irq,
        .dflr = __invalidate_dcache_range_nomsr_irq,
        .din = __invalidate_dcache_all_nomsr_irq,
        .dinr = __invalidate_dcache_range_nomsr_irq,
};

/* New wt cache model for newer Microblaze versions */
static const struct scache wt_msr_noirq = {
        .ie = __enable_icache_msr,
        .id = __disable_icache_msr,
        .ifl = __flush_icache_all_noirq,
        .iflr = __flush_icache_range_noirq,
        .iin = __flush_icache_all_noirq,
        .iinr = __flush_icache_range_noirq,
        .de = __enable_dcache_msr,
        .dd = __disable_dcache_msr,
        .dfl = __invalidate_dcache_all_noirq_wt,
        .dflr = __invalidate_dcache_range_nomsr_wt,
        .din = __invalidate_dcache_all_noirq_wt,
        .dinr = __invalidate_dcache_range_nomsr_wt,
};

static const struct scache wt_nomsr_noirq = {
        .ie = __enable_icache_nomsr,
        .id = __disable_icache_nomsr,
        .ifl = __flush_icache_all_noirq,
        .iflr = __flush_icache_range_noirq,
        .iin = __flush_icache_all_noirq,
        .iinr = __flush_icache_range_noirq,
        .de = __enable_dcache_nomsr,
        .dd = __disable_dcache_nomsr,
        .dfl = __invalidate_dcache_all_noirq_wt,
        .dflr = __invalidate_dcache_range_nomsr_wt,
        .din = __invalidate_dcache_all_noirq_wt,
        .dinr = __invalidate_dcache_range_nomsr_wt,
};

/* CPU version code for 7.20.c - see arch/microblaze/kernel/cpu/cpuinfo.c */
#define CPUVER_7_20_A   0x0c
#define CPUVER_7_20_D   0x0f

void microblaze_cache_init(void)
{
        if (cpuinfo.use_instr & PVR2_USE_MSR_INSTR) {
                if (cpuinfo.dcache_wb) {
                        pr_info("wb_msr\n");
                        mbc = (struct scache *)&wb_msr;
                        if (cpuinfo.ver_code <= CPUVER_7_20_D) {
                                /* MS: problem with signal handling - hw bug */
                                pr_info("WB won't work properly\n");
                        }
                } else {
                        if (cpuinfo.ver_code >= CPUVER_7_20_A) {
                                pr_info("wt_msr_noirq\n");
                                mbc = (struct scache *)&wt_msr_noirq;
                        } else {
                                pr_info("wt_msr\n");
                                mbc = (struct scache *)&wt_msr;
                        }
                }
        } else {
                if (cpuinfo.dcache_wb) {
                        pr_info("wb_nomsr\n");
                        mbc = (struct scache *)&wb_nomsr;
                        if (cpuinfo.ver_code <= CPUVER_7_20_D) {
                                /* MS: problem with signal handling - hw bug */
                                pr_info("WB won't work properly\n");
                        }
                } else {
                        if (cpuinfo.ver_code >= CPUVER_7_20_A) {
                                pr_info("wt_nomsr_noirq\n");
                                mbc = (struct scache *)&wt_nomsr_noirq;
                        } else {
                                pr_info("wt_nomsr\n");
                                mbc = (struct scache *)&wt_nomsr;
                        }
                }
        }
        /*
         * FIXME Invalidation is done in U-BOOT
         * WT cache: Data is already written to main memory
         * WB cache: Discard data on noMMU which caused that kernel doesn't boot
         */
        /* invalidate_dcache(); */
        enable_dcache();

        invalidate_icache();
        enable_icache();
}