/*
 * Cache Ops For Loongson GS232
 *
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2010-07-09     Bernard        first version
 * 2011-08-08     lgnq             modified for LS1B
 * 2015-07-08     chinesebear   modified for loongson 1c
 */

#include <rtthread.h>
#include <mips.h>

#define K0BASE          0x80000000
#define PRID_LS1C       0x4220

extern void Clear_TagLo (void);
extern void Invalidate_Icache_Ls1c(unsigned int);
extern void Invalidate_Dcache_ClearTag_Ls1c(unsigned int);
extern void Invalidate_Dcache_Fill_Ls1c(unsigned int);
extern void Writeback_Invalidate_Dcache(unsigned int);
extern void enable_cpu_cache(void);

typedef struct cacheinfo_t
{
    unsigned int    icache_size;
    unsigned int    dcache_size;
    unsigned int    icacheline_size;
    unsigned int    dcacheline_size;
} cacheinfo_t ;

typedef struct cacheop_t
{
    void (*Clear_TagLo) (void);
    void (*Invalidate_Icache) (unsigned int);
    void (*Invalidate_Dcache_Fill) (unsigned int);
    void (*Invalidate_Dcache_ClearTag) (unsigned int);
    void (*Init_Cache)(void);
} cacheop_t ;

static cacheop_t cacheop, *pcacheop;
static cacheinfo_t cacheinfo, *pcacheinfo;

int identify_cpu(void)
{
    unsigned int cpu_id;

    pcacheop = &cacheop;
    pcacheinfo = &cacheinfo;

    rt_kprintf("CPU configure: 0x%08x\n", read_c0_config());
    cpu_id = read_c0_prid();
    switch (cpu_id)
    {
    case PRID_LS1C:
        rt_kprintf("CPU:Loongson 1C\n");
        pcacheop->Clear_TagLo = Clear_TagLo;
        pcacheop->Invalidate_Icache = Invalidate_Icache_Ls1c;
        pcacheop->Invalidate_Dcache_Fill = Invalidate_Dcache_Fill_Ls1c;
        pcacheop->Invalidate_Dcache_ClearTag = Invalidate_Dcache_ClearTag_Ls1c;
        break;
    default:
        rt_kprintf("Unknown CPU type, system halted!\n");
        while (1)
        {
            ;
        }
        break;
    }

    return 0;
}

void probe_cache(void)
{
    unsigned int config1 = read_c0_config1();
    unsigned int icache_size, icache_line_size, icache_sets, icache_ways;
    unsigned int dcache_size, dcache_line_size, dcache_sets, dcache_ways;

    if ((icache_line_size = ((config1 >> 19) & 7)))
        icache_line_size = 2 << icache_line_size;
    else
        icache_line_size = icache_line_size;
    icache_sets = 64 << ((config1 >> 22) & 7);
    icache_ways = 1 + ((config1 >> 16) & 7);
    icache_size = icache_sets * icache_ways * icache_line_size;

    if ((dcache_line_size = ((config1 >> 10) & 7)))
        dcache_line_size = 2 << dcache_line_size;
    else
        dcache_line_size = dcache_line_size;
    dcache_sets = 64 << ((config1 >> 13) & 7);
    dcache_ways = 1 + ((config1 >> 7) & 7);
    dcache_size = dcache_sets * dcache_ways * dcache_line_size;

    rt_kprintf("DCache %2dkb, linesize %d bytes.\n", dcache_size >> 10, dcache_line_size);
    rt_kprintf("ICache %2dkb, linesize %d bytes.\n", icache_size >> 10, icache_line_size);

    pcacheinfo->icache_size = icache_size;
    pcacheinfo->dcache_size = dcache_size;
    pcacheinfo->icacheline_size = icache_line_size;
    pcacheinfo->dcacheline_size = dcache_line_size;

    return ;
}

void invalidate_writeback_dcache_all(void)
{
    unsigned int start = K0BASE;
    unsigned int end = (start + pcacheinfo->dcache_size);

    while (start < end)
    {
        Writeback_Invalidate_Dcache(start);  //hit writeback invalidate
        start += pcacheinfo->dcacheline_size;
    }
}

void invalidate_writeback_dcache(unsigned long addr, int size)
{
    unsigned long start, end;

    start = (addr + pcacheinfo->dcacheline_size -1) & (- pcacheinfo->dcacheline_size);
    end = (addr + size + pcacheinfo->dcacheline_size -1) & ( -pcacheinfo->dcacheline_size);

    while (start <end)
    {
        Writeback_Invalidate_Dcache(start);
        start += pcacheinfo->dcacheline_size;
    }
}

void invalidate_icache_all(void)
{
    unsigned int start = K0BASE;
    unsigned int end = (start + pcacheinfo->icache_size);

    while (start < end)
    {
        pcacheop->Invalidate_Icache(start);
        start += pcacheinfo->icacheline_size;
    }
}

void invalidate_dcache_all(void)
{
    unsigned int start = K0BASE;
    unsigned int end  = (start + pcacheinfo->dcache_size);
    while (start <end)
    {
        Invalidate_Dcache_Fill_Ls1c(start);
        start += pcacheinfo->icacheline_size;
    }
}

//with cache disabled
void init_dcache(void)
{
    unsigned int start = K0BASE;
    unsigned int end = (start + pcacheinfo->dcache_size);

    while (start < end)
    {
        pcacheop->Invalidate_Dcache_ClearTag(start);
        start += pcacheinfo->dcacheline_size;
    }

}

void rt_hw_cache_init(void)
{
    unsigned int start, end;

    /* 1. identify cpu and probe cache */
    identify_cpu();
    probe_cache();

    start = K0BASE;
    end = (start + pcacheinfo->icache_size);

    /*
     *  2. clear CP0 taglo/taghi register;
     */
    pcacheop->Clear_TagLo();

    /*
     *  3. invalidate instruction cache;
     */
    while (start < end)
    {
        pcacheop->Invalidate_Icache(start); //index invalidate icache
        start += pcacheinfo->icacheline_size;
    }

    /*
     *  4. invalidate data cache;
     */
    start = K0BASE;
    end = (start + pcacheinfo->dcache_size);
    while(start < end)
    {
        pcacheop->Invalidate_Dcache_ClearTag(start);
        start += pcacheinfo->dcacheline_size;
    }

    start = K0BASE;
    while(start < end)
    {
        pcacheop->Invalidate_Dcache_Fill(start);  //index invalidate dcache
        start += pcacheinfo->dcacheline_size;
    }

    start = K0BASE;
    while(start < end)
    {
        pcacheop->Invalidate_Dcache_ClearTag(start);
        start += pcacheinfo->dcacheline_size;
    }

    /* enable cache */
    enable_cpu_cache();
    rt_kprintf("enable cpu cache done\n");

    return ;
}


