/***********************************************************************
*
*             Copyright 2011 Mentor Graphics Corporation
*                         All Rights Reserved.
*
* THIS WORK CONTAINS TRADE SECRET AND PROPRIETARY INFORMATION WHICH IS
* THE PROPERTY OF MENTOR GRAPHICS CORPORATION OR ITS LICENSORS AND IS
* SUBJECT TO LICENSE TERMS.
*
************************************************************************

************************************************************************
*
*   FILE NAME
*
*       ppc_mmu_e500.c
*
*   DESCRIPTION
*
*       This file contains the PPC architecture MMU functions
*
*   FUNCTIONS
*
*       ESAL_Resolve_Size_To_TLB_Entries
*       ESAL_Get_First_Avail_TLB_Entry
*       ESAL_Invalidate_TLB_Entry
*       ESAL_CO_MEM_Cache_Enable
*       ESAL_CO_MEM_Region_Setup
*
*   DEPENDENCIES
*
*       nucleus.h

*
***********************************************************************/

/* Include required header files */
#include      "nucleus.h"
#include      "kernel/nu_kernel.h"

#if (CFG_NU_OS_ARCH_PPC_COM_MMU_VARIANT == PPC_MMU_E500_VARIANT)
#include      "arch/ppc/ppc_mmu_e500_defs.h"

/* External Function prototypes */
extern UINT32 ESAL_Get_Current_TLB(VOID);
extern VOID   ESAL_Create_Temp_TLB_Entry(UINT32 tlb_entry_num);
extern VOID   ESAL_Switch_Address_Space(INT address_space);

/* Local Function prototypes */
VOID    ESAL_CO_MEM_Region_Setup(INT region_num,
                                 UINT32 phy_addr,
                                 UINT32 vrt_addr,
                                 UINT32 size,
                                 ESAL_GE_CACHE_TYPE cache_type,
                                 ESAL_GE_MEMORY_TYPE mem_type,
                                 UINT32 access_type);

/***********************************************************************
*
*   FUNCTION
*
*       ESAL_Resolve_Size_To_TLB_Entries
*
*   DESCRIPTION
*
*       This function receives the requested size of memory region
*       as input and resolves it to supported TLB entry sizes. Note
*       The requested size can be resolved at most to minimum supported
*       granularity only, for PPC this is typically 4KB.
*
*   CALLED BY
*
*       ESAL_CO_MEM_Region_Setup
*
*   CALLS
*
*       None
*
*   INPUTS
*
*       size                                requested size of memory.
*       num_entries                         number of page table.
*                                           entries to be made.
*       page_sizes                          array of page sizes to
*                                           realize the requested
*                                           memory region.
*
*   OUTPUTS
*
*       None
*
***********************************************************************/
static VOID ESAL_Resolve_Size_To_TLB_Entries(UINT64 size, UINT32* num_entries, UINT8* page_sizes)
{
    enum    PAGE_SIZES p;
    UINT64  rem_size = size;
    UINT64  tempU32;
    UINT8*  pUint8 = page_sizes;

    if(size > (ESAL_CO_MEM_MAS1_TSIZE_TO_BYTES(ESAL_CO_MEM_PAGE_SIZE_4KB)))
    {
        /* Resolve the page size for the memory region requested */
        while (rem_size > (ESAL_CO_MEM_MAS1_TSIZE_TO_BYTES(ESAL_CO_MEM_PAGE_SIZE_4KB)))
        {
            for( p = (ESAL_PG_SZ_END_IDX - 1); p > ESAL_PG_SZ_START_IDX; p--)
            {
                tempU32 = ESAL_CO_MEM_MAS1_TSIZE_TO_BYTES(p);

                if(((rem_size % tempU32) >= 0) && ((rem_size % tempU32) < rem_size))
                {
                    /* Update remaining size */
                    rem_size -= tempU32;

                    /* Increment number of TLB entries */
                    *num_entries = *num_entries + 1;

                    /* Update TLB entry size */
                    *pUint8++ = p;

                    break;
                }
            }
        }
    }
}

/***********************************************************************
*
*   FUNCTION
*
*       ESAL_Get_First_Avail_TLB_Entry
*
*   DESCRIPTION
*
*       This function gets the first available/invalid tlb entry.
*
*   CALLED BY
*
*       ESAL_CO_MEM_Cache_Enable
*       ESAL_CO_MEM_Region_Setup
*
*   CALLS
*
*       none
*
*   INPUTS
*
*       none
*
*   OUTPUTS
*
*       INT                                ID number of available TLB
*
***********************************************************************/
static INT ESAL_Get_First_Avail_TLB_Entry(VOID)
{
    UINT32      spr_val;
    INT         i = 0;
    STATUS      status = ESAL_INVALID_INPUT;

    for(i = 0; i < E500_MAX_TLB1_ENTRIES; i++)
    {
        /* Set up MAS0 to read TLB entry i */
        spr_val = 0;
        spr_val = (ESAL_CO_MEM_MAS0_TLBSEL_TLB1 | ESAL_CO_MEM_MAS0_ESEL(i));
        ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS0, spr_val);
        ESAL_TS_RTE_ISYNC_EXECUTE();

        /* Read TLB entry i */
        ESAL_TS_RTE_TLBRE_EXECUTE();

        /* Read MAS1 to see if entry is valid */
        spr_val = 0;
        ESAL_TS_RTE_SPR_READ(ESAL_TS_RTE_MAS1, &spr_val);

        /* If we found an invalid TLB entry */
        if(!(spr_val & ESAL_CO_MEM_MAS1_V_BIT_VALID))
        {
            status = NU_SUCCESS;
            break;
        }
    }

    if(status != NU_SUCCESS)
    {
        i = -1;
    }

    return (i);
}

/***********************************************************************
*
*   FUNCTION
*
*       ESAL_Invalidate_TLB_Entry
*
*   DESCRIPTION
*
*       This function invalidates the tlb entry requested
*
*
*   CALLED BY
*
*       ESAL_CO_MEM_Cache_Enable
*
*   CALLS
*
*       none
*
*   INPUTS
*
*       tlb_entry_num                       TLB entry number
*
*
*   OUTPUTS
*
*       STATUS                              Status of function execution
*
***********************************************************************/
static VOID ESAL_Invalidate_TLB_Entry(UINT32 tlb_entry_num)
{
    UINT32      spr_val;

    /* Set up MAS0 - TLB entry number in TLB1 */
    spr_val = 0;
    spr_val = (ESAL_CO_MEM_MAS0_TLBSEL_TLB1 | ESAL_CO_MEM_MAS0_ESEL(tlb_entry_num));
    ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS0, spr_val);
    ESAL_TS_RTE_ISYNC_EXECUTE();

    /* Read TLB entry */
    ESAL_TS_RTE_TLBRE_EXECUTE();

    /* Read and invalidate tlb entry in MAS1 register */
    spr_val = 0;
    ESAL_TS_RTE_SPR_READ(ESAL_TS_RTE_MAS1, &spr_val);
    spr_val &= (~(ESAL_CO_MEM_MAS1_V_BIT_VALID | ESAL_CO_MEM_MAS1_IPROT_PROTECTED));
    ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS1, spr_val);
    ESAL_TS_RTE_ISYNC_EXECUTE();

    /* Write to invalidated TLB entry */
    ESAL_TS_RTE_TLBWE_EXECUTE();
}

/***********************************************************************
*
*   FUNCTION
*
*       ESAL_CO_MEM_Cache_Enable
*
*   DESCRIPTION
*
*       This function initializes the cache as required
*       for the given core.  The memory region data structure
*       (ESAL_DP_MEM_Region_Data) should be utilized to perform
*       this initialization and the cache attributes in this
*       table should be correctly reflected.
*
*   CALLED BY
*
*       ESAL_GE_MEM_Initialize
*
*   CALLS
*
*       ESAL_CO_MEM_Region_Setup
*
*   INPUTS
*
*       avail_mem                           Address of available memory
*
*   OUTPUTS
*
*       VOID *                              Updated available memory
*                                           address
*
***********************************************************************/
VOID *ESAL_CO_MEM_Cache_Enable(VOID *avail_mem)
{
    INT     count;
    UINT32  l1csr_val;
    UINT32  tlb_entry_num;
    UINT32  current_tlb_entry_num;
    UINT32  max_tlb_entries;

    /* Invalidate all cache */
    ESAL_CO_MEM_ICACHE_ALL_INVALIDATE();
    ESAL_CO_MEM_DCACHE_FLUSH_INVAL(TOOLSET_DATA_START_ADDR, TOOLSET_DATA_SIZE);
    ESAL_CO_MEM_DCACHE_FLUSH_INVAL(TOOLSET_SDATA2_START_ADDR, TOOLSET_SDATA2_SIZE);
    ESAL_CO_MEM_DCACHE_FLUSH_INVAL(TOOLSET_SDATA_START_ADDR, TOOLSET_SDATA_SIZE);
    ESAL_CO_MEM_DCACHE_FLUSH_INVAL(TOOLSET_BSS_START_ADDR, TOOLSET_BSS_SIZE);

    /* Disable instruction and data cache */
    ESAL_CO_MEM_CACHE_DISABLE();

    /* Query the hardware to find the maximum number of TLBs */
    ESAL_TS_RTE_SPR_READ(ESAL_CO_MEM_TLB1CFG_REG_ID, &max_tlb_entries);
    max_tlb_entries &= ESAL_CO_MEM_TLB1CFG_NENTRY;

    /* Get TLB entry currently being used by this code */
    current_tlb_entry_num = ESAL_Get_Current_TLB();

    /* Invalidate all other TLB entries */
    for (tlb_entry_num = 0; tlb_entry_num < max_tlb_entries; tlb_entry_num++)
    {
        if (tlb_entry_num != current_tlb_entry_num)
        {
            /* Invalidate the entry */
            ESAL_Invalidate_TLB_Entry(tlb_entry_num);
        }
    }

    /* Get first available TLB entry */
    tlb_entry_num = ESAL_Get_First_Avail_TLB_Entry();

    /* Create temporary TLB entry in address space 1 */
    ESAL_Create_Temp_TLB_Entry(tlb_entry_num);

    /* Switch to address space 1 */
    ESAL_Switch_Address_Space(ADDR_SPA_1);

    /* Invalidate the original TLB entry that this code was using */
    ESAL_Invalidate_TLB_Entry(current_tlb_entry_num);

    /* Loop through all sections in the memory description table */
    for (count = 0; count < ESAL_DP_MEM_NUM_REGIONS; count++)
    {
        /* Enable caches for this memory section */
        ESAL_CO_MEM_Region_Setup(count,
                                 (UINT32)ESAL_DP_MEM_Region_Data[count].physical_start_addr,
                                 (UINT32)ESAL_DP_MEM_Region_Data[count].virtual_start_addr,
                                 ESAL_DP_MEM_Region_Data[count].size,
                                 ESAL_DP_MEM_Region_Data[count].cache_type,
                                 ESAL_DP_MEM_Region_Data[count].mem_type,
                                 ESAL_DP_MEM_Region_Data[count].access_type);
    } /* for loop */

    /* Switch back to address space 0 */
    ESAL_Switch_Address_Space(ADDR_SPA_0);

    /* Invalidate the temporary TLB entry created in address space 1 */
    ESAL_Invalidate_TLB_Entry(tlb_entry_num);

    /* Enable both I & D cache. Read the DCache and ICache Control and Status registers
       and OR in the Cache Enable bit the write the updated register value back */
    ESAL_TS_RTE_SPR_READ(ESAL_CO_MEM_L1CSR0_REG_ID, &l1csr_val);
    l1csr_val |= ESAL_CO_MEM_L1CSR0_CE_BIT;
    ESAL_TS_RTE_MSYNC_EXECUTE();
    ESAL_TS_RTE_ISYNC_EXECUTE();
    ESAL_TS_RTE_SPR_WRITE(ESAL_CO_MEM_L1CSR0_REG_ID, l1csr_val);
    ESAL_TS_RTE_ISYNC_EXECUTE();

    ESAL_TS_RTE_SPR_READ(ESAL_CO_MEM_L1CSR1_REG_ID, &l1csr_val);
    l1csr_val |= ESAL_CO_MEM_L1CSR1_ICE_BIT;
    ESAL_TS_RTE_SPR_WRITE(ESAL_CO_MEM_L1CSR1_REG_ID, l1csr_val);
    ESAL_TS_RTE_ISYNC_EXECUTE();

    /* Return available memory. */
    return (avail_mem);
}

/***********************************************************************
*
*   FUNCTION
*
*       ESAL_CO_MEM_Region_Setup
*
*   DESCRIPTION
*
*       This function sets-up the region of memory based on the given
*       attributes
*
*   CALLED BY
*
*       ESAL_CO_MEM_Cache_Enable
*
*   CALLS
*
*       None
*
*   INPUTS
*
*       region_num                          Number of region begin setup
*       phy_addr                            Physical address of region
*       vrt_addr                            Virtual address of region
*       size                                Size of region
*       cache_type                          Cache type of region
*       mem_type                            Memory type of region
*       access_type                         Memory access type of region
*
*   OUTPUTS
*
*       None
*
***********************************************************************/
VOID ESAL_CO_MEM_Region_Setup(INT region_num,
                              UINT32 phy_addr,
                              UINT32 vrt_addr,
                              UINT32 size,
                              ESAL_GE_CACHE_TYPE cache_type,
                              ESAL_GE_MEMORY_TYPE mem_type,
                              UINT32 access_type)
{
    UINT32  pBase, vBase;
    UINT32  numEntries = 0;
    UINT8   pageSizes[MAX_PAGES_PER_MEM_REGION];
    INT i;
    UINT32  tlb_entry_num = 0;
    UINT32  spr_val;

    /* This is to prevent compiler warnings. */
    NU_UNUSED_PARAM(region_num);
    NU_UNUSED_PARAM(access_type);

    /* Initialize vBase, pBase and size */
    pBase = phy_addr;
    vBase = vrt_addr;

    /* Reset number of page entries */
    numEntries =0;

    /* Resolve the requested memory region to supported page sizes */
    ESAL_Resolve_Size_To_TLB_Entries((UINT64)size, &numEntries, &pageSizes[0]);

    /* Make TLB entries for the resolved page sizes */
    for(i = 0; i < numEntries; i++)
    {
        if(pageSizes[i] != 0)
        {
            /* Get first available TLB entry */
            tlb_entry_num = ESAL_Get_First_Avail_TLB_Entry();

            if(tlb_entry_num != -1)
            {
                /* Set up MAS0 - TLB entry number in TLB1 */
                spr_val = 0;
                spr_val = (ESAL_CO_MEM_MAS0_TLBSEL_TLB1 | ESAL_CO_MEM_MAS0_ESEL(tlb_entry_num));
                ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS0, spr_val);
                ESAL_TS_RTE_ISYNC_EXECUTE();

                /* Set up MAS1 - enable validity, not protected, TID is zero, TS is zero,
                 * and computed size for TSIZE.
                 */
                spr_val = 0;
                spr_val = (ESAL_CO_MEM_MAS1_V_BIT_VALID | ESAL_CO_MEM_MAS1_IPROT_PROTECTED |
                          ESAL_CO_MEM_MAS1_TSIZE(pageSizes[i]));
                ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS1, spr_val);
                ESAL_TS_RTE_ISYNC_EXECUTE();

                /* Set up MAS2 - load effective page number, and WIMGE cache attributes
                 * for this page. This logic assumes all access to this page would be
                 * in big endian mode and are no guarded.
                 */
                spr_val = 0;
                spr_val |= ESAL_CO_MEM_MAS2_EPN(ESAL_CO_MEM_GET_EPN(vBase));

                switch(cache_type)
                {
                    case ESAL_NOCACHE:
                        spr_val |= ESAL_CO_MEM_MAS2_I;
                        break;

                    case ESAL_WRITETHROUGH:
                        spr_val |= ESAL_CO_MEM_MAS2_W;
                        break;

                    case ESAL_WRITEBACK:
                        break;

                    default:
                        break;
                }

                switch(mem_type)
                {
                    case ESAL_SHARED_RAM:
                        spr_val |= ESAL_CO_MEM_MAS2_M;
                        break;

                    case ESAL_RAM:;
                        break;

                    case ESAL_MEM_MAPPED:
                        spr_val |= ESAL_CO_MEM_MAS2_G;
                        break;

                    case ESAL_ROM:;
                        break;

                    case ESAL_IO_MAPPED:
                        break;

                    case ESAL_TLB_MEM:
                        break;
                }

                ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS2, spr_val);
                ESAL_TS_RTE_ISYNC_EXECUTE();

                /* Set up MAS3 - load real page number. User attributes bits and permissions bits
                 * are let to be in default state.
                 */
                spr_val = 0;
                spr_val |= (ESAL_CO_MEM_MAS3_RPN(ESAL_CO_MEM_GET_RPN(pBase))|ESAL_KERNEL_SUP_PERMISSIONS);
                ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS3, spr_val);
                ESAL_TS_RTE_ISYNC_EXECUTE();

                /* We let MAS4 and MAS6 in its existing state */

                /* Set up MAS7 - load zero for the 4 higher order bits of RPN */
                spr_val = 0;
                ESAL_TS_RTE_SPR_WRITE(ESAL_TS_RTE_MAS7, spr_val);

                /* Write the TLB entry we just created */
                ESAL_TS_RTE_TLBWE_EXECUTE();
            }
        }

        /* Increment virtual and physical base address for next page entry */
        vBase += (ESAL_CO_MEM_MAS1_TSIZE_TO_BYTES(pageSizes[i]));
        pBase += (ESAL_CO_MEM_MAS1_TSIZE_TO_BYTES(pageSizes[i]));
    }
}

#endif /* (CFG_NU_OS_ARCH_PPC_COM_MMU_VARIANT == PPC_MMU_E500_VARIANT) */
