/******************************************************************************
 *
 * Copyright 2013 Altera Corporation. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 * 
 ******************************************************************************/

#include "alt_cache.h"
#include "alt_interrupt_common.h"
#include "alt_mmu.h"
#include "socal/alt_sysmgr.h"
#include "socal/hps.h"
#include "socal/socal.h"
#include <stdio.h>
#include <inttypes.h>

/////

// NOTE: To enable debugging output, delete the next line and uncomment the
//   line after.
#define dprintf(...)
// #define dprintf  printf

/////

#ifndef MIN
#define MIN(a, b) ((a) > (b) ? (b) : (a))
#endif

/////

// System Level API here

ALT_STATUS_CODE alt_cache_system_enable(void)
{
    alt_cache_l1_enable_all();

    alt_cache_l2_init();
    alt_cache_l2_prefetch_enable();
    alt_cache_l2_parity_enable();
    alt_cache_l2_enable();

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_system_disable(void)
{
    alt_cache_l2_disable();
    alt_cache_l2_parity_disable();
    alt_cache_l2_prefetch_disable();
    alt_cache_l2_uninit();

    alt_cache_l1_disable_all();

    return ALT_E_SUCCESS;
}

#define ALT_CPU_PAR_FS_VALUE_GET(par) ((par >> 1) & 0x3f)
#define ALT_CPU_PAR_SS_SET_MSK 0x00000002
#define ALT_CPU_PAR_F_SET_MSK  0x00000001

static inline uintptr_t alt_mmu_va_to_pa(void * va, uint32_t * seglength, uint32_t * dfsr)
{
#if ALT_CACHE_SUPPORT_NON_FLAT_VIRTUAL_MEMORY

    uintptr_t pa = 0;

    // ATS1CPR [Address Translate Stage 1 Current state PL1 Read]; See ARMv7-A,R, section B4.2.4.
    // ISB
    // Read the PAR
#ifdef __ARMCC_VERSION
    __asm("mcr p15, 0, va, c7, c8, 0");
    __asm("isb");
    __asm("mrc p15, 0, pa, c7, c4, 0");
#else
    __asm("mcr p15, 0, %0, c7, c8, 0" : : "r" (va));
    __asm("isb");
    __asm("mrc p15, 0, %0, c7, c4, 0" : "=r" (pa));
#endif

    if (pa & ALT_CPU_PAR_F_SET_MSK)
    {
        // If the F bit (fault) is set, then report the error.

        // Extract the PAR::FS field value.
        uint32_t fs = ALT_CPU_PAR_FS_VALUE_GET(pa);

        // Translate PAR::FS[5:0] (field value) or PAR[6:1] => DFSR[12,10,3:0] to report error.
        *dfsr = ((fs & 0x20) << 7) | // bit 12
                ((fs & 0x10) << 6) | // bit 10
                ((fs & 0x0f) << 0);  // bit 3:0

        dprintf("DEBUG[cache][va->pa]: Fault detected. DFSR = 0x%" PRIx32 ".\n", dfsr);
    }
    else if (pa & ALT_CPU_PAR_SS_SET_MSK)
    {
        // If the page is a supersection, PAR contains PA[31:24]. VA contains PA[23:0].

        uint32_t offset = (uintptr_t)va & (ALT_MMU_SUPERSECTION_SIZE - 1);
        pa &= ~(ALT_MMU_SUPERSECTION_SIZE - 1);
        pa |= offset;

        dprintf("DEBUG[cache][va->pa]: pa[SS] = 0x%x; offset = 0x%" PRIx32 ".\n",
                pa & ~(ALT_MMU_SUPERSECTION_SIZE - 1),
                offset);

        *seglength = ALT_MMU_SUPERSECTION_SIZE - offset;
        *dfsr      = 0;
    }
    else
    {
        // If the page is not a supersection, PAR contains PA[31:12]. VA contains PA[11:0].

        uint32_t offset = (uintptr_t)va & (ALT_MMU_SMALL_PAGE_SIZE - 1);
        pa &= ~(ALT_MMU_SMALL_PAGE_SIZE - 1);
        pa |= offset;

        dprintf("DEBUG[cache][va->pa]: pa[page] = 0x%x; offset = 0x%" PRIx32 ".\n",
                pa & ~(ALT_MMU_SMALL_PAGE_SIZE - 1),
                offset);

        *seglength = ALT_MMU_SMALL_PAGE_SIZE - offset;
        *dfsr      = 0;
    }

    return pa;

#else

    *seglength = 0xffffffff - (uintptr_t)va + 1;
    *dfsr      = 0;
    return (uintptr_t)va;

#endif
}

// Internal functions that expect all inputs to be aligned properly.

static void alt_cache_l1_data_invalidate_helper(void * vaddress, size_t length);
static void alt_cache_l1_data_clean_helper(void * vaddress, size_t length);
static void alt_cache_l1_data_purge_helper(void * vaddress, size_t length);

static ALT_STATUS_CODE alt_cache_l2_invalidate_helper(uintptr_t paddress, size_t length);
static ALT_STATUS_CODE alt_cache_l2_clean_helper(uintptr_t paddress, size_t length);
static ALT_STATUS_CODE alt_cache_l2_purge_helper(uintptr_t paddress, size_t length);

ALT_STATUS_CODE alt_cache_system_invalidate(void * vaddress, size_t length)
{
    // Verify preconditions:
    //  - address and length are on the cache boundaries
    if (((uintptr_t)vaddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    dprintf("DEBUG[cache][sys]: invalidate: vaddr = %p; length = 0x%x.\n", vaddress, length);

    char * va = vaddress;

    while (length)
    {
        uint32_t  dfsr     = 0;
        uint32_t  seg_size = 0;
        uintptr_t pa       = alt_mmu_va_to_pa(va, &seg_size, &dfsr);
        if (dfsr)
        {
            return ALT_E_ERROR;
        }

        seg_size = MIN(length, seg_size);

        dprintf("DEBUG[cache][sys]: (loop): va = %p; pa = 0x%x; size = 0x%" PRIx32 ".\n", va, pa, seg_size);

        alt_cache_l2_invalidate_helper(pa, seg_size);
        alt_cache_l2_sync();
        alt_cache_l1_data_invalidate_helper(va, seg_size);
        // __asm("dsb") handled by l1_data_invalidate().

        va     += seg_size;
        length -= seg_size;
    }

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_system_clean(void * vaddress, size_t length)
{
    // Verify preconditions:
    //  - address and length are on the cache boundaries
    if (((uintptr_t)vaddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    dprintf("DEBUG[cache][sys]: clean: vaddr = %p; length = 0x%x.\n", vaddress, length);

    char * va = vaddress;

    while (length)
    {
        uint32_t  dfsr     = 0;
        uint32_t  seg_size = 0;
        uintptr_t pa       = alt_mmu_va_to_pa(va, &seg_size, &dfsr);
        if (dfsr)
        {
            return ALT_E_ERROR;
        }

        seg_size = MIN(length, seg_size);

        dprintf("DEBUG[cache][sys]: (loop): va = %p; pa = 0x%x; size = 0x%" PRIx32 ".\n", va, pa, seg_size);

        alt_cache_l1_data_clean_helper(va, seg_size);
        // __asm("dsb") handled by l1_data_clean().
        alt_cache_l2_clean_helper(pa, seg_size);
        alt_cache_l2_sync();

        va     += seg_size;
        length -= seg_size;
    }

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_system_purge(void * vaddress, size_t length)
{
    // Verify preconditions:
    //  - address and length are on the cache boundaries
    if (((uintptr_t)vaddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    dprintf("DEBUG[cache][sys]: clean: vaddr = %p; length = 0x%x.\n", vaddress, length);

    char * va = vaddress;

    while (length)
    {
        uint32_t  dfsr     = 0;
        uint32_t  seg_size = 0;
        uintptr_t pa       = alt_mmu_va_to_pa(va, &seg_size, &dfsr);
        if (dfsr)
        {
            return ALT_E_ERROR;
        }

        seg_size = MIN(length, seg_size);

        dprintf("DEBUG[cache][sys]: (loop): va = %p; pa = 0x%x; size = 0x%" PRIx32 ".\n", va, pa, seg_size);

        alt_cache_l1_data_clean_helper(va, seg_size);
        // __asm("dsb") handled by l1_data_clean().
        alt_cache_l2_purge_helper(pa, seg_size);
        alt_cache_l2_sync();
        alt_cache_l1_data_invalidate_helper(va, seg_size);
        // __asm("dsb") handled by l1_data_invalidate().

        va     += seg_size;
        length -= seg_size;
    }

    return ALT_E_SUCCESS;
}

/////

//
// Stand-in SoCAL for ARM CPU registers needed for L1 cache functions.
//

// System Control Register. See Cortex-A9, section 4.3.9.
#define ALT_CPU_SCTLR_TE_SET_MSK   (1 << 30)
#define ALT_CPU_SCTLR_AFE_SET_MSK  (1 << 29)
#define ALT_CPU_SCTLR_TRE_SET_MSK  (1 << 28)
#define ALT_CPU_SCTLR_NMFI_SET_MSK (1 << 27)
#define ALT_CPU_SCTLR_EE_SET_MSK   (1 << 25)
#define ALT_CPU_SCTLR_HA_SET_MSK   (1 << 17)
#define ALT_CPU_SCTLR_RR_SET_MSK   (1 << 14)
#define ALT_CPU_SCTLR_V_SET_MSK    (1 << 13)
#define ALT_CPU_SCTLR_I_SET_MSK    (1 << 12)
#define ALT_CPU_SCTLR_Z_SET_MSK    (1 << 11)
#define ALT_CPU_SCTLR_SW_SET_MSK   (1 << 10)
#define ALT_CPU_SCTLR_C_SET_MSK    (1 <<  2)
#define ALT_CPU_SCTLR_A_SET_MSK    (1 <<  1)
#define ALT_CPU_SCTLR_M_SET_MSK    (1 <<  0)

// Auxiliary Control Register. See Cortex-A9, section 4.3.10.
#define ALT_CPU_ACTLR_PARITYON_SET_MSK           (1 << 9)
#define ALT_CPU_ACTLR_ALLOCINONEWAY_SET_MSK      (1 << 8)
#define ALT_CPU_ACTLR_EXCL_SET_MSK               (1 << 7)
#define ALT_CPU_ACTLR_SMP_SET_MSK                (1 << 6)
#define ALT_CPU_ACTLR_WRITEFULLLINEZEROS_SET_MSK (1 << 3)
#define ALT_CPU_ACTLR_L1PREFETCHEN_SET_MSK       (1 << 2)
#define ALT_CPU_ACTLR_L2PREFETCHEN_SET_MSK       (1 << 1)
#define ALT_CPU_ACTLR_FW_SET_MSK                 (1 << 0)

// Cache Size Selection Register. See Cortex-A9, section 4.3.8.
#define ALT_CPU_ACTLR_LEVEL_SET_MSK (7 << 1)
#define ALT_CPU_ACTLR_IND_SET_MSK   (1 << 0)

// Cache Size Identification Register. See Cortex-A9, section 4.3.5.
#define ALT_CPU_CCSIDR_WT_SET_MSK            (0x1    << 31)
#define ALT_CPU_CCSIDR_WB_SET_MSK            (0x1    << 30)
#define ALT_CPU_CCSIDR_RA_SET_MSK            (0x1    << 29)
#define ALT_CPU_CCSIDR_WA_SET_MSK            (0x1    << 28)
#define ALT_CPU_CCSIDR_NUMSETS_SET_MSK       (0x7fff << 13)
#define ALT_CPU_CCSIDR_ASSOCIATIVITY_SET_MSK (0x3ff  <<  3)
#define ALT_CPU_CCSIDR_LINESIZE_SET_MSK      (0x7    <<  0)

#define ALT_CPU_CCSIDR_NUMSETS_VALUE_GET(value)       (((value) & ALT_CPU_CCSIDR_NUMSETS_SET_MSK)       >> 13)
#define ALT_CPU_CCSIDR_ASSOCIATIVITY_VALUE_GET(value) (((value) & ALT_CPU_CCSIDR_ASSOCIATIVITY_SET_MSK) >>  3)
#define ALT_CPU_CCSIDR_LINESIZE_VALUE_GET(value)      (((value) & ALT_CPU_CCSIDR_LINESIZE_SET_MSK)      >>  0)

/////

static inline __attribute__((always_inline)) uint32_t sctlr_read_helper(void)
{
    uint32_t sctlr;

#ifdef __ARMCC_VERSION
    __asm("MRC p15, 0, sctlr, c1, c0, 0");
#else
    __asm("MRC p15, 0, %0,    c1, c0, 0" : "=r" (sctlr));
#endif

    return sctlr;
}

static inline __attribute__((always_inline)) void sctlr_write_helper(uint32_t sctlr)
{
#ifdef __ARMCC_VERSION
    __asm("MCR p15, 0, sctlr, c1, c0, 0");
#else
    __asm("MCR p15, 0, %0,    c1, c0, 0" : : "r" (sctlr));
#endif
}

static inline __attribute__((always_inline)) uint32_t actlr_read_helper(void)
{
    uint32_t actlr;

#ifdef __ARMCC_VERSION
    __asm("MRC p15, 0, actlr, c1, c0, 1");
#else
    __asm("MRC p15, 0, %0,    c1, c0, 1" : "=r" (actlr));
#endif

    return actlr;
}

static inline __attribute__((always_inline)) void actlr_write_helper(uint32_t actlr)
{
#ifdef __ARMCC_VERSION
    __asm("MCR p15, 0, actlr, c1, c0, 1");
#else
    __asm("MCR p15, 0, %0,    c1, c0, 1" : : "r" (actlr));
#endif
}

static void cssidr_decode_helper(bool query_i_cache, uint32_t * log2_L, uint32_t * log2_A, uint32_t * log2_S)
{
    // First query the D cache information using CSSELR to select the data cache
    // See ARM Cortex-A9 TRM, section 4.3.8.

    // Wait for the CSSELR to flush.

    // Then use CSSIDR to get the cache charateristics
    // See ARMv7-A,R, section B4.1.19

    uint32_t csselr;
    uint32_t cssidr;

    if (query_i_cache)
    {
        csselr = ALT_CPU_ACTLR_IND_SET_MSK;
    }
    else
    {
        csselr = 0;
    }

#ifdef __ARMCC_VERSION
    __asm("MCR p15, 2, csselr, c0, c0, 0");
    __asm("ISB");
    __asm("MRC p15, 1, cssidr, c0, c0, 0");
#else
    __asm("MCR p15, 2, %0, c0, c0, 0" : : "r" (csselr));
    __asm("ISB");
    __asm("MRC p15, 1, %0, c0, c0, 0" : "=r" (cssidr));
#endif

    // Extract the associativity, line length, and number of sets.
    int linesize      = ALT_CPU_CCSIDR_LINESIZE_VALUE_GET(cssidr) + 2 + 2; // {log2(line length in words) - 2} + 2 + 2 => (... in bytes)
    int associativity = ALT_CPU_CCSIDR_ASSOCIATIVITY_VALUE_GET(cssidr) + 1;
    int numsets       = ALT_CPU_CCSIDR_NUMSETS_VALUE_GET(cssidr) + 1;

    // Determine the log2 of the associativity and numsets, rounded up
    int L = linesize; // log2(line length in bytes)
    int A = 0;        // log2(associativity) rounded up
    int S = 0;        // log2(number of sets) rounded up

    while ((1 << A) < associativity)
    {
        ++A;
    }

    while ((1 << S) < numsets)
    {
        ++S;
    }

    // Output the parameters
    *log2_L = L;
    *log2_A = A;
    *log2_S = S;
}

/////

ALT_STATUS_CODE alt_cache_l1_enable_all(void)
{
    alt_cache_l1_disable_all();

    // Parity should be turned on before anything else.
    alt_cache_l1_parity_enable();
    alt_cache_l1_instruction_enable();
    alt_cache_l1_data_enable();
    alt_cache_l1_branch_enable();
    alt_cache_l1_prefetch_enable();

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_disable_all(void)
{
    alt_cache_l1_parity_disable();
    alt_cache_l1_instruction_disable();
    alt_cache_l1_data_disable();
    alt_cache_l1_branch_disable();
    alt_cache_l1_prefetch_disable();

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_instruction_enable(void)
{
    // Update SCTLR.I bit (bit 12)
    // See Cortex-A9 TRM, section 4.3.9

    uint32_t sctlr = sctlr_read_helper();
    if ((sctlr & ALT_CPU_SCTLR_I_SET_MSK) == 0)
    {
        alt_cache_l1_instruction_invalidate();

        sctlr |= ALT_CPU_SCTLR_I_SET_MSK;
        sctlr_write_helper(sctlr);     
    }

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_instruction_disable(void)
{
    // Update SCTLR.I bit (bit 12)
    // See Cortex-A9 TRM, section 4.3.9

    uint32_t sctlr = sctlr_read_helper();
    sctlr &= ~ALT_CPU_SCTLR_I_SET_MSK;
    sctlr_write_helper(sctlr);

    return ALT_E_SUCCESS;
}

bool alt_cache_l1_instruction_is_enabled(void)
{
    // Query SCTLR.I bit (bit 12)
    // See Cortex-A9 TRM, section 4.3.9

    uint32_t sctlr = sctlr_read_helper();
    if ( (sctlr & ALT_CPU_SCTLR_I_SET_MSK) != 0 )
    {
        return true;
    }
    else
    {
        return false;
    }
}

ALT_STATUS_CODE alt_cache_l1_instruction_invalidate(void)
{
    // Issue the ICIALLUIS (Instruction Cache Invalidate ALL to point of
    // Unification Inner Shareable) cache maintenance operation
    // See ARMv7-A,R, section B4.2.1.

    uint32_t dummy = 0;

#ifdef __ARMCC_VERSION
    __asm("MCR p15, 0, dummy, c7, c1, 0");
#else
    __asm("MCR p15, 0, %0,    c7, c1, 0" : : "r" (dummy));
#endif

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_data_enable(void)
{
    // Update SCTLR.C bit (bit 2)
    // See Cortex-A9 TRM, section 4.3.9

    uint32_t sctlr = sctlr_read_helper();
    if ((sctlr & ALT_CPU_SCTLR_C_SET_MSK) == 0)
    {
        alt_cache_l1_data_invalidate_all();
        
        sctlr |= ALT_CPU_SCTLR_C_SET_MSK;
        sctlr_write_helper(sctlr);
    }

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_data_disable(void)
{
    // Update SCTLR.C bit (bit 2)
    // See Cortex-A9 TRM, section 4.3.9

    uint32_t sctlr = sctlr_read_helper();
    if ((sctlr & ALT_CPU_SCTLR_C_SET_MSK) != 0)
    {
        alt_cache_l1_data_clean_all();

        sctlr &= ~ALT_CPU_SCTLR_C_SET_MSK;

        sctlr_write_helper(sctlr);
    }

    return ALT_E_SUCCESS;
}

bool alt_cache_l1_data_is_enabled(void)
{
    // Query SCTLR.C bit (bit 2)
    // See Cortex-A9 TRM, section 4.3.9

    uint32_t sctlr = sctlr_read_helper();
    if ( (sctlr & ALT_CPU_SCTLR_C_SET_MSK) != 0 )
    {
        return true;
    }
    else
    {
        return false;
    }
}

static void alt_cache_l1_data_invalidate_helper(void * vaddress, size_t length)
{
    // Repeatedly call DCIMVAC (Data Cache Invalidate by Modified Virtual
    // Address to the point of Coherency) and loop for the length of the
    // segment.

    // The DCIMVAC uses the MVA format for the register. This is simply the
    // virtual address of the line to be invalidated.
    // See ARMv7-A,R, section B4.2.1.

    for (uintptr_t va = (uintptr_t)vaddress; va < (uintptr_t)vaddress + length; va += ALT_CACHE_LINE_SIZE)
    {
#ifdef __ARMCC_VERSION
        __asm("MCR p15, 0, va, c7, c6, 1");
#else
        __asm("MCR p15, 0, %0, c7, c6, 1" : : "r" (va));
#endif
    }

    // Ensure all cache maintenance operations complete before returning.
    __asm("dsb");
}

ALT_STATUS_CODE alt_cache_l1_data_invalidate(void * vaddress, size_t length)
{
    // Verify preconditions:
    //  - length is non-zero
    //  - address and length are on the cache boundaries
    if (length == 0)
    {
        return ALT_E_BAD_ARG;
    }
    if (((uintptr_t)vaddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    alt_cache_l1_data_invalidate_helper(vaddress, length);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_data_invalidate_all(void)
{
    // Gather parameters for DCISW (Data Cache Invalidate by Set / Way) data format.
    // See ARMv7-A,R, section B4.2.1

    // Query the log2(line size in bytes), log2(associativity), log2(set count) for the data cache.

    uint32_t L = 0;
    uint32_t A = 0;
    uint32_t S = 0;

    cssidr_decode_helper(false, &L, &A, &S);

    // Repeatedly call DCISW and loop for every cache way and set.

    for (int way = 0; way < (1 << A); ++way)
    {
        for (int set = 0; set < (1 << S); ++set)
        {
            uint32_t way_set_info = 0;
            way_set_info |= way << (32 - A);
            way_set_info |= set << (L);
            // Level is 0 because we're invalidating the L1.

#ifdef __ARMCC_VERSION
            __asm("MCR p15, 0, way_set_info, c7, c6, 2");
#else
            __asm("MCR p15, 0, %0,           c7, c6, 2" : : "r" (way_set_info));
#endif
        }
    }

    // Ensure all cache maintenance operations complete before returning.
    __asm("dsb");

    return ALT_E_SUCCESS;
}

static void alt_cache_l1_data_clean_helper(void * vaddress, size_t length)
{
    // Repeatedly call DCCMVAC (Data Cache Clean by Modified Virtual Address to
    // point of Coherency) and loop for the length of the segment.

    // The DCCMVAC uses the MVA format for the register. This is simply the
    // virtual address of the line to be invalidated.
    // See ARMv7-A,R, section B4.2.1.

    for (uintptr_t va = (uintptr_t)vaddress; va < (uintptr_t)vaddress + length; va += ALT_CACHE_LINE_SIZE)
    {
#ifdef __ARMCC_VERSION
        __asm("MCR p15, 0, va, c7, c10, 1");
#else
        __asm("MCR p15, 0, %0, c7, c10, 1" : : "r" (va));
#endif
    }

    // Ensure all cache maintenance operations complete before returning.
    __asm("dsb");
}

ALT_STATUS_CODE alt_cache_l1_data_clean(void * vaddress, size_t length)
{
    // Verify preconditions:
    //  - length is non-zero
    //  - address and length are on the cache boundaries
    if (length == 0)
    {
        return ALT_E_BAD_ARG;
    }
    if (((uintptr_t)vaddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    alt_cache_l1_data_clean_helper(vaddress, length);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_data_clean_all(void)
{
    // Gather parameters for DCCSW (Data Cache Clean by Set / Way) data format
    // See ARMv7-A,R, section B4.2.1

    // Query the log2(line size in bytes), log2(associativity), log2(set count) for the data cache.

    uint32_t L = 0;
    uint32_t A = 0;
    uint32_t S = 0;

    cssidr_decode_helper(false, &L, &A, &S);

    // Repeatedly call DCCSW and loop for every cache way and set.

    for (int way = 0; way < (1 << A); ++way)
    {
        for (int set = 0; set < (1 << S); ++set)
        {
            uint32_t way_set_info = 0;
            way_set_info |= way << (32 - A);
            way_set_info |= set << (L);
            // Level is 0 because we're invalidating the L1.

#ifdef __ARMCC_VERSION
            __asm("MCR p15, 0, way_set_info, c7, c10, 2");
#else
            __asm("MCR p15, 0, %0,           c7, c10, 2" : : "r" (way_set_info));
#endif
        }
    }

    // Ensure all cache maintenance operations complete before returning.
    __asm("dsb");

    return ALT_E_SUCCESS;
}

static void alt_cache_l1_data_purge_helper(void * vaddress, size_t length)
{
    // Repeatedly call DCCIMVAC (Data Cache Clean and Invalidate by Modified
    // Virtual Address to point of Coherency) and loop for the length of the
    // segment.

    // The DCCIMVAC uses the MVA format for the register. This is simply the
    // virtual address of the line to be invalidated.
    // See ARMv7-A,R, section B4.2.1.

    for (uintptr_t va = (uintptr_t)vaddress; va < (uintptr_t)vaddress + length; va += ALT_CACHE_LINE_SIZE)
    {
#ifdef __ARMCC_VERSION
        __asm("MCR p15, 0, va, c7, c14, 1");
#else
        __asm("MCR p15, 0, %0, c7, c14, 1" : : "r" (va));
#endif
    }

    // Ensure all cache maintenance operations complete before returning.
    __asm("dsb");
}

ALT_STATUS_CODE alt_cache_l1_data_purge(void * vaddress, size_t length)
{
    // Verify preconditions:
    //  - length is non-zero
    //  - address and length are on the cache boundaries
    if (length == 0)
    {
        return ALT_E_BAD_ARG;
    }
    if (((uintptr_t)vaddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    alt_cache_l1_data_purge_helper(vaddress, length);

    return ALT_E_SUCCESS;

}

ALT_STATUS_CODE alt_cache_l1_data_purge_all(void)
{
    // Gather parameters for DCCISW (Data Cache Clean and Invalidate by Set / Way) data format
    // See ARMv7-A,R, section B4.2.1

    // Query the log2(line size in bytes), log2(associativity), log2(set count) for the data cache.

    uint32_t L = 0;
    uint32_t A = 0;
    uint32_t S = 0;

    cssidr_decode_helper(false, &L, &A, &S);

    // Repeatedly call DCCISW and loop for every cache way and set.

    for (int way = 0; way < (1 << A); ++way)
    {
        for (int set = 0; set < (1 << S); ++set)
        {
            uint32_t way_set_info = 0;
            way_set_info |= way << (32 - A);
            way_set_info |= set << (L);
            // Level is 0 because we're invalidating the L1.

#ifdef __ARMCC_VERSION
            __asm("MCR p15, 0, way_set_info, c7, c14, 2");
#else
            __asm("MCR p15, 0, %0,           c7, c14, 2" : : "r" (way_set_info));
#endif
        }
    }

    // Ensure all cache maintenance operations complete before returning.
    __asm("dsb");

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_parity_enable(void)
{
    uint32_t actlr = actlr_read_helper();
    if ((actlr & ALT_CPU_ACTLR_PARITYON_SET_MSK) == 0)
    {
        // Determine which caches which will be affected by parity being enabled
        // are currently enabled.
        bool dcache_en = alt_cache_l1_data_is_enabled();
        bool icache_en = alt_cache_l1_instruction_is_enabled();
        bool branch_en = alt_cache_l1_branch_is_enabled();

        // For those caches, disable them temporarily
        if (icache_en == true)
        {
            alt_cache_l1_instruction_disable();
        }
        if (dcache_en == true)
        {
            alt_cache_l1_data_disable();
        }
        if (branch_en == true)
        {
            alt_cache_l1_branch_disable();
        }

        // Turn on parity in the L1.
        actlr |= ALT_CPU_ACTLR_PARITYON_SET_MSK;
        actlr_write_helper(actlr);

        // Now enable them again.
        if (icache_en == true)
        {
            alt_cache_l1_instruction_enable();
        }
        if (dcache_en == true)
        {
            alt_cache_l1_data_enable();
        }
        if (branch_en == true)
        {
            alt_cache_l1_branch_enable();
        }
    }

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_parity_disable(void)
{
    uint32_t actlr = actlr_read_helper();
    actlr &= ~ALT_CPU_ACTLR_PARITYON_SET_MSK;
    actlr_write_helper(actlr);

    return ALT_E_SUCCESS;
}

bool alt_cache_l1_parity_is_enabled(void)
{
    uint32_t actlr = actlr_read_helper();
    if ((actlr & ALT_CPU_ACTLR_PARITYON_SET_MSK) != 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

ALT_STATUS_CODE alt_cache_l1_branch_enable(void)
{
    alt_cache_l1_branch_invalidate();

    uint32_t sctlr = sctlr_read_helper();
    sctlr |= ALT_CPU_SCTLR_Z_SET_MSK;
    sctlr_write_helper(sctlr);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_branch_disable(void)
{
    uint32_t sctlr = sctlr_read_helper();
    sctlr &= ~ALT_CPU_SCTLR_Z_SET_MSK;
    sctlr_write_helper(sctlr);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_branch_invalidate(void)
{
    // Issue BPIALLIS (Branch Predictor Invalidate ALL, Inner Shareable).

    uint32_t dummy = 0;

#ifdef __ARMCC_VERSION
    __asm("MCR p15, 0, dummy, c7, c1, 6");
#else
    __asm("MCR p15, 0, %0,    c7, c1, 6" : : "r" (dummy));
#endif

    // Ensure all branch predictor maintenance operations complete before returning.
    __asm("dsb");

    return ALT_E_SUCCESS;
}

bool alt_cache_l1_branch_is_enabled(void)
{
    uint32_t sctlr = sctlr_read_helper();
    if ((sctlr & ALT_CPU_SCTLR_Z_SET_MSK) != 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

ALT_STATUS_CODE alt_cache_l1_prefetch_enable(void)
{
    uint32_t actlr = actlr_read_helper();
    actlr |= ALT_CPU_ACTLR_L1PREFETCHEN_SET_MSK;
    actlr_write_helper(actlr);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l1_prefetch_disable(void)
{
    uint32_t actlr = actlr_read_helper();
    actlr &= ~ALT_CPU_ACTLR_L1PREFETCHEN_SET_MSK;
    actlr_write_helper(actlr);

    return ALT_E_SUCCESS;
}

bool alt_cache_l1_prefetch_is_enabled(void)
{
    uint32_t actlr = actlr_read_helper();
    if ((actlr & ALT_CPU_ACTLR_L1PREFETCHEN_SET_MSK) != 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

/////

//
// Stand-in SoCAL for MPU L2 registers.
//
// All offsets are from ALT_MPUL2_OFST.
//

#define ALT_MPUL2_CACHE_ID_OFST             0x000
#define ALT_MPUL2_CACHE_TYPE_OFST           0x004
#define ALT_MPUL2_CONTROL_OFST              0x100
#define ALT_MPUL2_AUX_CONTROL_OFST          0x104
#define ALT_MPUL2_TAG_RAM_CONTROL_OFST      0x108
#define ALT_MPUL2_DATA_RAM_CONTROL_OFST     0x10c
#define ALT_MPUL2_EV_COUNTER_CTRL_OFST      0x200
#define ALT_MPUL2_EV_COUNTER1_CFG_OFST      0x204
#define ALT_MPUL2_EV_COUNTER0_CFG_OFST      0x208
#define ALT_MPUL2_EV_COUNTER1_OFST          0x20c
#define ALT_MPUL2_EV_COUNTER0_OFST          0x210
#define ALT_MPUL2_INT_MASK_OFST             0x214
#define ALT_MPUL2_INT_MASK_STATUS_OFST      0x218
#define ALT_MPUL2_INT_RAW_STATUS_OFST       0x21c
#define ALT_MPUL2_INT_CLEAR_OFST            0x220
#define ALT_MPUL2_CACHE_SYNC_OFST           0x730
#define ALT_MPUL2_INV_PA_OFST               0x770
#define ALT_MPUL2_INV_WAY_OFST              0x77c
#define ALT_MPUL2_CLEAN_PA_OFST             0x7b0
#define ALT_MPUL2_CLEAN_INDEX_OFST          0x7b8
#define ALT_MPUL2_CLEAN_WAY_OFST            0x7bc
#define ALT_MPUL2_CLEAN_INV_PA_OFST         0x7f0
#define ALT_MPUL2_CLEAN_INV_INDEX_OFST      0x7f8
#define ALT_MPUL2_CLEAN_INV_WAY_OFST        0x7fc
#define ALT_MPUL2_D_LOCKDOWN0_OFST          0x900
#define ALT_MPUL2_I_LOCKDOWN0_OFST          0x904
#define ALT_MPUL2_D_LOCKDOWN1_OFST          0x908
#define ALT_MPUL2_I_LOCKDOWN1_OFST          0x90c
#define ALT_MPUL2_D_LOCKDOWN2_OFST          0x910
#define ALT_MPUL2_I_LOCKDOWN2_OFST          0x914
#define ALT_MPUL2_D_LOCKDOWN3_OFST          0x918
#define ALT_MPUL2_I_LOCKDOWN3_OFST          0x91c
#define ALT_MPUL2_D_LOCKDOWN4_OFST          0x920
#define ALT_MPUL2_I_LOCKDOWN4_OFST          0x924
#define ALT_MPUL2_D_LOCKDOWN5_OFST          0x928
#define ALT_MPUL2_I_LOCKDOWN5_OFST          0x92c
#define ALT_MPUL2_D_LOCKDOWN6_OFST          0x930
#define ALT_MPUL2_I_LOCKDOWN6_OFST          0x934
#define ALT_MPUL2_D_LOCKDOWN7_OFST          0x938
#define ALT_MPUL2_I_LOCKDOWN7_OFST          0x93c
#define ALT_MPUL2_D_LOCKDOWNx_OFST(x)       (0x900 + ((x) * 0x8))
#define ALT_MPUL2_I_LOCKDOWNx_OFST(x)       (0x904 + ((x) * 0x8))
#define ALT_MPUL2_LOCK_LINE_EN_OFST         0x950
#define ALT_MPUL2_UNLOCK_WAY_OFST           0x954
#define ALT_MPUL2_ADDR_FILTERING_START_OFST 0xc00
#define ALT_MPUL2_ADDR_FILTERING_END_OFST   0xc04
#define ALT_MPUL2_DEBUG_CTRL_OFST           0xf40
#define ALT_MPUL2_PREFETCH_CTRL_OFST        0xf60
#define ALT_MPUL2_POWER_CTRL_OFST           0xf80

#define ALT_MPUL2_CACHE_ID_ADDR             ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CACHE_ID_OFST))
#define ALT_MPUL2_CACHE_TYPE_ADDR           ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CACHE_TYPE_OFST))
#define ALT_MPUL2_CONTROL_ADDR              ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CONTROL_OFST))
#define ALT_MPUL2_AUX_CONTROL_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_AUX_CONTROL_OFST))
#define ALT_MPUL2_TAG_RAM_CONTROL_ADDR      ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_TAG_RAM_CONTROL_OFST))
#define ALT_MPUL2_DATA_RAM_CONTROL_ADDR     ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_DATA_RAM_CONTROL_OFST))
#define ALT_MPUL2_EV_COUNTER_CTRL_ADDR      ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_EV_COUNTER_CTRL_OFST))
#define ALT_MPUL2_EV_COUNTER1_CFG_ADDR      ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_EV_COUNTER1_CFG_OFST))
#define ALT_MPUL2_EV_COUNTER0_CFG_ADDR      ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_EV_COUNTER0_CFG_OFST))
#define ALT_MPUL2_EV_COUNTER1_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_EV_COUNTER1_OFST))
#define ALT_MPUL2_EV_COUNTER0_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_EV_COUNTER0_OFST))
#define ALT_MPUL2_INT_MASK_ADDR             ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_INT_MASK_OFST))
#define ALT_MPUL2_INT_MASK_STATUS_ADDR      ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_INT_MASK_STATUS_OFST))
#define ALT_MPUL2_INT_RAW_STATUS_ADDR       ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_INT_RAW_STATUS_OFST))
#define ALT_MPUL2_INT_CLEAR_ADDR            ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_INT_CLEAR_OFST))
#define ALT_MPUL2_CACHE_SYNC_ADDR           ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CACHE_SYNC_OFST))
#define ALT_MPUL2_INV_PA_ADDR               ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_INV_PA_OFST))
#define ALT_MPUL2_INV_WAY_ADDR              ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_INV_WAY_OFST))
#define ALT_MPUL2_CLEAN_PA_ADDR             ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CLEAN_PA_OFST))
#define ALT_MPUL2_CLEAN_INDEX_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CLEAN_INDEX_OFST))
#define ALT_MPUL2_CLEAN_WAY_ADDR            ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CLEAN_WAY_OFST))
#define ALT_MPUL2_CLEAN_INV_PA_ADDR         ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CLEAN_INV_PA_OFST))
#define ALT_MPUL2_CLEAN_INV_INDEX_ADDR      ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CLEAN_INV_INDEX_OFST))
#define ALT_MPUL2_CLEAN_INV_WAY_ADDR        ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_CLEAN_INV_WAY_OFST))
#define ALT_MPUL2_D_LOCKDOWN0_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN0_OFST))
#define ALT_MPUL2_I_LOCKDOWN0_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN0_OFST))
#define ALT_MPUL2_D_LOCKDOWN1_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN1_OFST))
#define ALT_MPUL2_I_LOCKDOWN1_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN1_OFST))
#define ALT_MPUL2_D_LOCKDOWN2_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN2_OFST))
#define ALT_MPUL2_I_LOCKDOWN2_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN2_OFST))
#define ALT_MPUL2_D_LOCKDOWN3_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN3_OFST))
#define ALT_MPUL2_I_LOCKDOWN3_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN3_OFST))
#define ALT_MPUL2_D_LOCKDOWN4_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN4_OFST))
#define ALT_MPUL2_I_LOCKDOWN4_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN4_OFST))
#define ALT_MPUL2_D_LOCKDOWN5_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN5_OFST))
#define ALT_MPUL2_I_LOCKDOWN5_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN5_OFST))
#define ALT_MPUL2_D_LOCKDOWN6_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN6_OFST))
#define ALT_MPUL2_I_LOCKDOWN6_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN6_OFST))
#define ALT_MPUL2_D_LOCKDOWN7_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWN7_OFST))
#define ALT_MPUL2_I_LOCKDOWN7_ADDR          ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWN7_OFST))
#define ALT_MPUL2_D_LOCKDOWNx_ADDR(x)       ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_D_LOCKDOWNx_OFST(x)))
#define ALT_MPUL2_I_LOCKDOWNx_ADDR(x)       ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_I_LOCKDOWNx_OFST(x)))
#define ALT_MPUL2_LOCK_LINE_EN_ADDR         ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_LOCK_LINE_EN_OFST))
#define ALT_MPUL2_UNLOCK_WAY_ADDR           ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_UNLOCK_WAY_OFST))
#define ALT_MPUL2_ADDR_FILTERING_START_ADDR ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_ADDR_FILTERING_START_OFST))
#define ALT_MPUL2_ADDR_FILTERING_END_ADDR   ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_ADDR_FILTERING_END_OFST))
#define ALT_MPUL2_DEBUG_CTRL_ADDR           ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_DEBUG_CTRL_OFST))
#define ALT_MPUL2_PREFETCH_CTRL_ADDR        ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_PREFETCH_CTRL_OFST))
#define ALT_MPUL2_POWER_CTRL_ADDR           ALT_CAST(void *, (ALT_CAST(char *, ALT_MPUL2_ADDR) + ALT_MPUL2_POWER_CTRL_OFST))

#define ALT_MPUL2_CONTROL_EN_SET_MSK (1 << 0)

#define ALT_MPUL2_AUX_CONTROL_PARITY_EN_SET_MSK         (1 << 21)
#define ALT_MPUL2_AUX_CONTROL_WAYSIZE_SET_MSK           (7 << 17)
#define ALT_MPUL2_AUX_CONTROL_ASSOCIATIVITY_SET_MSK     (1 << 16)
#define ALT_MPUL2_AUX_CONTROL_WAYSIZE_VALUE_GET(value)  (((value) >> 17) & 0x7)
#define ALT_MPUL2_AUX_CONTROL_FULLLINEOFZERO_EN_SET_MSK (1 << 0)

#define ALT_MPUL2_TAG_RAM_CONTROL_WRITE_LATENCY_VALUE_GET(value) (((value) >> 8) & 0x7)
#define ALT_MPUL2_TAG_RAM_CONTROL_WRITE_LATENCY_VALUE_SET(value) (((value) & 0x7) << 8)
#define ALT_MPUL2_TAG_RAM_CONTROL_READ_LATENCY_VALUE_GET(value)  (((value) >> 4) & 0x7)
#define ALT_MPUL2_TAG_RAM_CONTROL_READ_LATENCY_VALUE_SET(value)  (((value) & 0x7) << 4)
#define ALT_MPUL2_TAG_RAM_CONTROL_SETUP_LATENCY_VALUE_GET(value) (((value) >> 0) & 0x7)
#define ALT_MPUL2_TAG_RAM_CONTROL_SETUP_LATENCY_VALUE_SET(value) (((value) & 0x7) << 0)

#define ALT_MPUL2_DATA_RAM_CONTROL_WRITE_LATENCY_VALUE_GET(value) (((value) >> 8) & 0x7)
#define ALT_MPUL2_DATA_RAM_CONTROL_WRITE_LATENCY_VALUE_SET(value) (((value) & 0x7) << 8)
#define ALT_MPUL2_DATA_RAM_CONTROL_READ_LATENCY_VALUE_GET(value)  (((value) >> 4) & 0x7)
#define ALT_MPUL2_DATA_RAM_CONTROL_READ_LATENCY_VALUE_SET(value)  (((value) & 0x7) << 4)
#define ALT_MPUL2_DATA_RAM_CONTROL_SETUP_LATENCY_VALUE_GET(value) (((value) >> 0) & 0x7)
#define ALT_MPUL2_DATA_RAM_CONTROL_SETUP_LATENCY_VALUE_SET(value) (((value) & 0x7) << 0)

#define ALT_MPUL2_PREFETCH_CTRL_I_PF_EN_SET_MSK (1 << 29)
#define ALT_MPUL2_PREFETCH_CTRL_D_PF_EN_SET_MSK (1 << 28)

/////

#define ALT_CACHE_L2_INTERRUPT_ALL (ALT_CACHE_L2_INTERRUPT_DECERR | \
                                    ALT_CACHE_L2_INTERRUPT_SLVERR | \
                                    ALT_CACHE_L2_INTERRUPT_ERRRD |  \
                                    ALT_CACHE_L2_INTERRUPT_ERRRT |  \
                                    ALT_CACHE_L2_INTERRUPT_ERRWD |  \
                                    ALT_CACHE_L2_INTERRUPT_ERRWT |  \
                                    ALT_CACHE_L2_INTERRUPT_PARRD |  \
                                    ALT_CACHE_L2_INTERRUPT_PARRT |  \
                                    ALT_CACHE_L2_INTERRUPT_ECNTR)

/////

static uint32_t alt_cache_l2_waymask = 0x0000ffff;

ALT_STATUS_CODE alt_cache_l2_init(void)
{
    // Query the cache characteristics

    uint32_t auxctrl = alt_read_word(ALT_MPUL2_AUX_CONTROL_ADDR);

    if (auxctrl & ALT_MPUL2_AUX_CONTROL_ASSOCIATIVITY_SET_MSK)
    {
        alt_cache_l2_waymask = 0x0000ffff;
    }
    else
    {
        alt_cache_l2_waymask = 0x000000ff;
    }

    /////

    // Initialize the L2CC using instructions from L2C-310, section 3.1.1.

    // Write global configuration:
    //  - Associativity, way size
    //  - Latencies for RAM accesses
    //  - Allocation policy
    //  - Prefetch and power capability
    // (Not needed as the associated lines are by default set to the instantiation parameters)

    // Invalidate by way all cache entries
    // (Not needed as it will be invalidated when L2 is enabled)

    alt_write_word(ALT_MPUL2_TAG_RAM_CONTROL_ADDR,
                     ALT_MPUL2_TAG_RAM_CONTROL_WRITE_LATENCY_VALUE_SET(0)
                   | ALT_MPUL2_TAG_RAM_CONTROL_READ_LATENCY_VALUE_SET(0)
                   | ALT_MPUL2_TAG_RAM_CONTROL_SETUP_LATENCY_VALUE_SET(0));

    alt_write_word(ALT_MPUL2_DATA_RAM_CONTROL_ADDR,
                     ALT_MPUL2_DATA_RAM_CONTROL_WRITE_LATENCY_VALUE_SET(0)
                   | ALT_MPUL2_DATA_RAM_CONTROL_READ_LATENCY_VALUE_SET(1)
                   | ALT_MPUL2_DATA_RAM_CONTROL_SETUP_LATENCY_VALUE_SET(0));

    // Clear interrupts just in case.
    alt_cache_l2_int_status_clear(ALT_CACHE_L2_INTERRUPT_ALL);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_uninit(void)
{
    return ALT_E_SUCCESS;
}

#define ALT_MPUL2_PREFETCH_CTRL_PF_EN_SET_MSK \
    (ALT_MPUL2_PREFETCH_CTRL_I_PF_EN_SET_MSK | ALT_MPUL2_PREFETCH_CTRL_D_PF_EN_SET_MSK)

ALT_STATUS_CODE alt_cache_l2_prefetch_enable(void)
{
    // Use the Prefetch Control Register instead of Aux Control. This is
    // because the Prefetch Control can be changed while the L2 is enabled.
    // For more information, see L2C-310, section 3.3.14.

    alt_setbits_word(ALT_MPUL2_PREFETCH_CTRL_ADDR, ALT_MPUL2_PREFETCH_CTRL_PF_EN_SET_MSK);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_prefetch_disable(void)
{
    // Use the Prefetch Control Register instead of Aux Control. This is
    // because the Prefetch Control can be changed while the L2 is enabled.
    // For more information, see L2C-310, section 3.3.14.

    alt_clrbits_word(ALT_MPUL2_PREFETCH_CTRL_ADDR, ALT_MPUL2_PREFETCH_CTRL_PF_EN_SET_MSK);

    return ALT_E_SUCCESS;
}

bool alt_cache_l2_prefetch_is_enabled(void)
{
    // Query the Prefetch Control Register.
    // For more information, see L2C-310, section 3.3.14.

    uint32_t pfctrl = alt_read_word(ALT_MPUL2_PREFETCH_CTRL_ADDR);

    if ((pfctrl & ALT_MPUL2_PREFETCH_CTRL_PF_EN_SET_MSK) == ALT_MPUL2_PREFETCH_CTRL_PF_EN_SET_MSK)
    {
        return true;
    }
    else
    {
        return false;
    }
}

ALT_STATUS_CODE alt_cache_l2_parity_enable(void)
{
    bool l2_enabled = alt_cache_l2_is_enabled();

    if (l2_enabled)
    {
        alt_cache_l2_disable();
    }

    alt_setbits_word(ALT_MPUL2_AUX_CONTROL_ADDR, ALT_MPUL2_AUX_CONTROL_PARITY_EN_SET_MSK);

    if (l2_enabled)
    {
        alt_cache_l2_enable();
    }

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_parity_disable(void)
{
    alt_clrbits_word(ALT_MPUL2_AUX_CONTROL_ADDR, ALT_MPUL2_AUX_CONTROL_PARITY_EN_SET_MSK);
    return ALT_E_SUCCESS;
}

bool alt_cache_l2_parity_is_enabled(void)
{
    uint32_t auxctrl = alt_read_word(ALT_MPUL2_AUX_CONTROL_ADDR);
    if ((auxctrl & ALT_MPUL2_AUX_CONTROL_PARITY_EN_SET_MSK) == ALT_MPUL2_AUX_CONTROL_PARITY_EN_SET_MSK)
    {
        return true;
    }
    else
    {
        return false;
    }
}

ALT_STATUS_CODE alt_cache_l2_enable(void)
{
    if (!alt_cache_l2_is_enabled())
    {
        alt_cache_l2_invalidate_all();
        alt_write_word(ALT_MPUL2_CONTROL_ADDR, ALT_MPUL2_CONTROL_EN_SET_MSK);
    }

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_disable(void)
{
    if (alt_cache_l2_is_enabled())
    {
        alt_cache_l2_purge_all();
        alt_write_word(ALT_MPUL2_CONTROL_ADDR, 0);
    }

    return ALT_E_SUCCESS;
}

bool alt_cache_l2_is_enabled(void)
{
    uint32_t ctrl = alt_read_word(ALT_MPUL2_CONTROL_ADDR);
    if ((ctrl & ALT_MPUL2_CONTROL_EN_SET_MSK) != 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

//
// Common cache maintenance operation register formats
//

#define ALT_MPUL2_COMMON_PA_TAG_SET_MSK   (0xfffff << 12)
#define ALT_MPUL2_COMMON_PA_INDEX_SET_MSK (0x7f    << 5)
#define ALT_MPUL2_COMMON_PA_C_SET_MSK     (0x1     << 0)

#define ALT_MPUL2_COMMON_INDEXWAY_WAY_SET_MSK   (0xf  << 28)
#define ALT_MPUL2_COMMON_INDEXWAY_INDEX_SET_MSK (0x7f << 5)
#define ALT_MPUL2_COMMON_INDEXWAY_C_SET_MSK     (0x1  << 0)

#define ALT_MPUL2_COMMON_SYNC_C_SET_MSK (1 << 0)

#define ALT_MPUL2_COMMON_WAY_WAY_SET_MSK (0xffff << 0)

/////

// Timeouts for various L2 cache maintenance operations.
// The ranges of the operations can be determined by enabling debug printing.
#define ALT_CACHE_L2_SYNC_TIMEOUT             128

#define ALT_CACHE_L2_INVALIDATE_ALL_TIMEOUT   4096
#define ALT_CACHE_L2_CLEAN_ALL_TIMEOUT        65536
#define ALT_CACHE_L2_PURGE_ALL_TIMEOUT        65536

#define ALT_CACHE_L2_INVALIDATE_ADDR_TIMEOUT  128
#define ALT_CACHE_L2_CLEAN_ADDR_TIMEOUT       128
#define ALT_CACHE_L2_PURGE_ADDR_TIMEOUT       128

ALT_STATUS_CODE alt_cache_l2_sync(void)
{
    // Issue cache sync command, then wait for it to complete by polling the same register.
    // For more information, see L2C-310, section 3.3.10.

    alt_write_word(ALT_MPUL2_CACHE_SYNC_ADDR, 0);

    int i = 0;

    while (alt_read_word(ALT_MPUL2_CACHE_SYNC_ADDR))
    {
        // Atomic operation still in progress.

        if (i == ALT_CACHE_L2_SYNC_TIMEOUT)
        {
            return ALT_E_TMO;
        }
        ++i;
    }

    dprintf("DEBUG[cache][l2]: L2 Sync time = %d.\n", i);

    return ALT_E_SUCCESS;
}

static ALT_STATUS_CODE alt_cache_l2_invalidate_helper(uintptr_t paddress, size_t length)
{
    int i   = 0;
    int seg = 0;

    // For each stride: Issue invalidate line by PA command, then wait for it
    // to complete by polling the same register.
    // For more information, see L2C-310, section 3.3.10.

    for (uintptr_t pa = paddress; pa < paddress + length; pa += ALT_CACHE_LINE_SIZE)
    {
        alt_write_word(ALT_MPUL2_INV_PA_ADDR, pa);

        ++seg;
    }

    while (alt_read_word(ALT_MPUL2_INV_PA_ADDR))
    {
        // Atomic operation still in progress.

        if (i == ALT_CACHE_L2_INVALIDATE_ADDR_TIMEOUT)
        {
            return ALT_E_TMO;
        }
        ++i;
    }

    dprintf("DEBUG[cache][l2]: L2 Invalidate PA time = %d for %d segment(s)\n", i, seg);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_invalidate(void * paddress, size_t length)
{
    // Verify preconditions:
    //  - length is non-zero
    //  - address and length are on the cache boundaries
    if (length == 0)
    {
        return ALT_E_BAD_ARG;
    }
    if (((uintptr_t)paddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    return alt_cache_l2_invalidate_helper((uintptr_t)paddress, length);
}

ALT_STATUS_CODE alt_cache_l2_invalidate_all(void)
{
    // Invalidate by way, all ways.
    // For more information, see L2C-310, section 3.3.10.

    alt_write_word(ALT_MPUL2_INV_WAY_ADDR, alt_cache_l2_waymask);

    int i = 0;

    while (alt_read_word(ALT_MPUL2_INV_WAY_ADDR))
    {
        // Background operation still in progress.

        if (i == ALT_CACHE_L2_INVALIDATE_ALL_TIMEOUT)
        {
            return ALT_E_TMO;
        }
        ++i;
    }

    dprintf("DEBUG[cache][l2]: L2 Invalidate All time = %d.\n", i);

    return ALT_E_SUCCESS;
}

static ALT_STATUS_CODE alt_cache_l2_clean_helper(uintptr_t paddress, size_t length)
{
    int i   = 0;
    int seg = 0;

    // For each stride: Issue clean line by PA command, then wait for it to
    // complete by polling the same register.
    // For more information, see L2C-310, section 3.3.10.

    for (uintptr_t pa = paddress; pa < paddress + length; pa += ALT_CACHE_LINE_SIZE)
    {
        alt_write_word(ALT_MPUL2_CLEAN_PA_ADDR, pa);

        ++seg;
    }

    while (alt_read_word(ALT_MPUL2_CLEAN_PA_ADDR) & ALT_MPUL2_COMMON_PA_C_SET_MSK)
    {
        // Atomic operation still in progress.

        if (i == ALT_CACHE_L2_CLEAN_ADDR_TIMEOUT)
        {
            return ALT_E_TMO;
        }
        ++i;
    }

    dprintf("DEBUG[cache][l2]: L2 Clean PA time = %d for %d segment(s)\n", i, seg);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_clean(void * paddress, size_t length)
{
    // Verify preconditions:
    //  - length is non-zero
    //  - address and length are on the cache boundaries
    if (length == 0)
    {
        return ALT_E_BAD_ARG;
    }
    if (((uintptr_t)paddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    return alt_cache_l2_clean_helper((uintptr_t)paddress, length);
}

ALT_STATUS_CODE alt_cache_l2_clean_all(void)
{
    // Clean by way, all ways.
    // For more information, see L2C-310, section 3.3.10.

    alt_write_word(ALT_MPUL2_CLEAN_WAY_ADDR, alt_cache_l2_waymask);

    int i = 0;

    while (alt_read_word(ALT_MPUL2_CLEAN_WAY_ADDR))
    {
        // Background operation still in progress.

        if (i == ALT_CACHE_L2_CLEAN_ALL_TIMEOUT)
        {
            return ALT_E_TMO;
        }
        ++i;
    }

    dprintf("DEBUG[cache][l2]: L2 Invalidate All time = %d.\n", i);

    return ALT_E_SUCCESS;
}

static ALT_STATUS_CODE alt_cache_l2_purge_helper(uintptr_t paddress, size_t length)
{
    int i   = 0;
    int seg = 0;

    // For each stride: Issue clean and invalidate line by PA command, then
    // wait for it to complete by polling the same register.
    // For more information, see L2C-310, section 3.3.10.

    for (uintptr_t pa = paddress; pa < paddress + length; pa += ALT_CACHE_LINE_SIZE)
    {
        alt_write_word(ALT_MPUL2_CLEAN_INV_PA_ADDR, pa);

        ++seg;
    }

    while (alt_read_word(ALT_MPUL2_CLEAN_INV_PA_ADDR) & ALT_MPUL2_COMMON_PA_C_SET_MSK)
    {
        // Atomic operation still in progress.

        if (i == ALT_CACHE_L2_PURGE_ADDR_TIMEOUT)
        {
            return ALT_E_TMO;
        }
        ++i;
    }

    dprintf("DEBUG[cache][l2]: L2 Purge PA time = %d for %d segment(s)\n", i, seg);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_purge(void * paddress, size_t length)
{
    // Verify preconditions:
    //  - length is non-zero
    //  - address and length are on the cache boundaries
    if (length == 0)
    {
        return ALT_E_BAD_ARG;
    }
    if (((uintptr_t)paddress & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }
    if ((length & (ALT_CACHE_LINE_SIZE - 1)) != 0)
    {
        return ALT_E_BAD_ARG;
    }

    return alt_cache_l2_purge_helper((uintptr_t)paddress, length);
}

ALT_STATUS_CODE alt_cache_l2_purge_all(void)
{
    // Clean and invalidate by way, all ways.
    // For more information, see L2C-310, section 3.3.10.

    alt_write_word(ALT_MPUL2_CLEAN_INV_WAY_ADDR, alt_cache_l2_waymask);

    int i = 0;

    while (alt_read_word(ALT_MPUL2_CLEAN_INV_WAY_ADDR))
    {
        // Background operation still in progress.

        if (i == ALT_CACHE_L2_PURGE_ALL_TIMEOUT)
        {
            return ALT_E_TMO;
        }
        ++i;
    }

    dprintf("DEBUG[cache][l2]: L2 Purge All time = %d.\n", i);

    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_int_enable(uint32_t interrupt)
{
    // Validate the interrupt mask
    if ((interrupt & ALT_CACHE_L2_INTERRUPT_ALL) == 0)
    {
        return ALT_E_BAD_ARG;
    }

    alt_setbits_word(ALT_MPUL2_INT_MASK_ADDR, interrupt);
    return ALT_E_SUCCESS;
}

ALT_STATUS_CODE alt_cache_l2_int_disable(uint32_t interrupt)
{
    // Validate the interrupt mask
    if ((interrupt & ALT_CACHE_L2_INTERRUPT_ALL) == 0)
    {
        return ALT_E_BAD_ARG;
    }

    alt_clrbits_word(ALT_MPUL2_INT_MASK_ADDR, interrupt);
    return ALT_E_SUCCESS;
}

uint32_t alt_cache_l2_int_status_get(void)
{
    return alt_read_word(ALT_MPUL2_INT_MASK_STATUS_ADDR);
}

ALT_STATUS_CODE alt_cache_l2_int_status_clear(uint32_t interrupt)
{
    // Validate the interrupt mask
    if ((interrupt & ALT_CACHE_L2_INTERRUPT_ALL) == 0)
    {
        return ALT_E_BAD_ARG;
    }

    alt_write_word(ALT_MPUL2_INT_CLEAR_ADDR, interrupt);
    return ALT_E_SUCCESS;
}

/////

__attribute__((weak)) ALT_STATUS_CODE alt_int_dist_pending_clear(ALT_INT_INTERRUPT_t int_id)
{
    return ALT_E_SUCCESS;
}

inline static uint32_t get_current_cpu_num(void)
{
    uint32_t affinity;

    // Use the MPIDR. This is only available at PL1 or higher.
    // See ARMv7, section B4.1.106.

#ifdef __ARMCC_VERSION
    __asm("MRC p15, 0, affinity, c0, c0, 5");
#else
    __asm("MRC p15, 0,       %0, c0, c0, 5" : "=r" (affinity));
#endif

    return affinity & 0xFF;
}

ALT_STATUS_CODE alt_cache_l2_ecc_start(void * block, size_t size)
{
    uint32_t way_size = (8 * 1024) << ALT_MPUL2_AUX_CONTROL_WAYSIZE_VALUE_GET(alt_read_word(ALT_MPUL2_AUX_CONTROL_ADDR));

    // Add 32 KiB to the scrubbing size to account for effects of the L1 on scrubbing.
    uint32_t scrub_way_size = way_size + (32 * 1024);

    if (size < scrub_way_size)
    {
        return ALT_E_ERROR;
    }

    if (alt_cache_l2_is_enabled() == false)
    {
        return ALT_E_ERROR;
    }

    /////

    bool l1_icache   = false;
    bool l1_prefetch = false;
    bool l2_prefetch = false;

    if (alt_cache_l1_instruction_is_enabled() == true)
    {
        l1_icache = true;
        alt_cache_l1_instruction_disable();
    }

    if (alt_cache_l1_prefetch_is_enabled() == true)
    {
        l1_prefetch = true;
        alt_cache_l1_prefetch_disable();
    }

    if (alt_cache_l2_prefetch_is_enabled() == true)
    {
        l2_prefetch = true;
        alt_cache_l2_prefetch_disable();
    }

    // inline'ed alt_cache_l2_disable(); // This will invalidate all L2 entries
    alt_cache_l2_purge_all();
    alt_write_word(ALT_MPUL2_CONTROL_ADDR, 0);

    // Enable "Full line of zero" feature of the L2C.
    // See L2C-310, section 2.5.5, "Full line of zero write".
    alt_setbits_word(ALT_MPUL2_AUX_CONTROL_ADDR, ALT_MPUL2_AUX_CONTROL_FULLLINEOFZERO_EN_SET_MSK);

    // ECC should be enabled before L2 is enabled. (NPP MPU, section 4.3, item 1)
    alt_write_word(ALT_SYSMGR_ECC_L2_ADDR, ALT_SYSMGR_ECC_L2_EN_SET_MSK);

    // inline'ed alt_cache_l2_enable();
    // No need to invalidate all; the previous L2 disable should have purged everything.
    alt_write_word(ALT_MPUL2_CONTROL_ADDR, ALT_MPUL2_CONTROL_EN_SET_MSK);

    // Enable "Full line of zero" feature of the A9.
    // See Cortex-A9 TRM, section 4.3.10.
    actlr_write_helper(actlr_read_helper() | ALT_CPU_ACTLR_WRITEFULLLINEZEROS_SET_MSK);

    /////

    uint32_t cpu_affinity = get_current_cpu_num();

    // Loop through all ways for the lock by master configuration.
    int way_count = (alt_cache_l2_waymask == 0x0000ffff) ? 16 : 8;

    for (int way_lock = 0; way_lock < way_count; ++way_lock)
    {
        // Lock the current way for Data and Instruction.
        alt_write_word(ALT_MPUL2_D_LOCKDOWNx_ADDR(cpu_affinity), ~(1 << way_lock) & alt_cache_l2_waymask);
        alt_write_word(ALT_MPUL2_I_LOCKDOWNx_ADDR(cpu_affinity), ~(1 << way_lock) & alt_cache_l2_waymask);

        // Invalidate All. This will ensure that scrubbing RAM contents does not exist in a different way.
        alt_cache_l2_invalidate_all();

        // Loop through all words in the block
        register uint32_t * block2 = block;
        for (register int i = 0; i < (scrub_way_size / sizeof(*block2)); i++)
        {
            *block2 = 0;
            ++block2;
        }

        // Don't put the DSB inside the loop. It will disallow the [full line of zero] optimization.
        __asm("dsb");
    }

    // Unlock the way lock by master for the current CPU.
    alt_write_word(ALT_MPUL2_D_LOCKDOWNx_ADDR(cpu_affinity), 0);
    alt_write_word(ALT_MPUL2_I_LOCKDOWNx_ADDR(cpu_affinity), 0);

    if (l1_icache)
    {
        alt_cache_l1_instruction_enable();
    }

    if (l1_prefetch)
    {
        alt_cache_l1_prefetch_enable();
    }

    if (l2_prefetch)
    {
        alt_cache_l2_prefetch_enable();
    }

    alt_int_dist_pending_clear(ALT_INT_INTERRUPT_L2_ECC_BYTE_WR_IRQ);
    alt_int_dist_pending_clear(ALT_INT_INTERRUPT_L2_ECC_CORRECTED_IRQ);
    alt_int_dist_pending_clear(ALT_INT_INTERRUPT_L2_ECC_UNCORRECTED_IRQ);

    return ALT_E_SUCCESS;
}
