mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 19:31:53 +00:00
f3354ab674
When both 'cache-size' and 'cache-sets' are specified for a L2 cache controller node, parse those properties and set up the set size based on which type of L2 cache controller we are using. Update the L2 cache controller Device Tree binding with the optional 'cache-size', 'cache-sets', 'cache-block-size' and 'cache-line-size' properties. These come from the ePAPR specification. Using the cache size, number of sets and cache line size we can calculate desired associativity of the L2 cache. This is done by the calculation: set size = cache size / sets ways = set size / line size way size = cache size / ways = sets * line size associativity = cache size / way size Example output from the PB1176 DT that look like this: L2: l2-cache { compatible = "arm,l220-cache"; (...) arm,override-auxreg; cache-size = <131072>; // 128kB cache-sets = <512>; cache-line-size = <32>; }; Ends up like this: L2C OF: override cache size: 131072 bytes (128KB) L2C OF: override line size: 32 bytes L2C OF: override way size: 16384 bytes (16KB) L2C OF: override associativity: 8 L2C: DT/platform modifies aux control register: 0x02020fff -> 0x02030fff L2C-220 cache controller enabled, 8 ways, 128 kB L2C-220: CACHE_ID 0x41000486, AUX_CTRL 0x06030fff Which is consistent with the value earlier hardcoded for the PB1176 platform. This patch is an extended version based on the initial patch by Florian Fainelli. Reviewed-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Florian Fainelli <f.fainelli@gmail.com> Signed-off-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
1669 lines
45 KiB
C
1669 lines
45 KiB
C
/*
|
|
* arch/arm/mm/cache-l2x0.c - L210/L220 cache controller support
|
|
*
|
|
* Copyright (C) 2007 ARM Limited
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
#include <linux/cpu.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/log2.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/cp15.h>
|
|
#include <asm/cputype.h>
|
|
#include <asm/hardware/cache-l2x0.h>
|
|
#include "cache-tauros3.h"
|
|
#include "cache-aurora-l2.h"
|
|
|
|
struct l2c_init_data {
|
|
const char *type;
|
|
unsigned way_size_0;
|
|
unsigned num_lock;
|
|
void (*of_parse)(const struct device_node *, u32 *, u32 *);
|
|
void (*enable)(void __iomem *, u32, unsigned);
|
|
void (*fixup)(void __iomem *, u32, struct outer_cache_fns *);
|
|
void (*save)(void __iomem *);
|
|
struct outer_cache_fns outer_cache;
|
|
};
|
|
|
|
#define CACHE_LINE_SIZE 32
|
|
|
|
static void __iomem *l2x0_base;
|
|
static DEFINE_RAW_SPINLOCK(l2x0_lock);
|
|
static u32 l2x0_way_mask; /* Bitmask of active ways */
|
|
static u32 l2x0_size;
|
|
static unsigned long sync_reg_offset = L2X0_CACHE_SYNC;
|
|
|
|
struct l2x0_regs l2x0_saved_regs;
|
|
|
|
/*
|
|
* Common code for all cache controllers.
|
|
*/
|
|
static inline void l2c_wait_mask(void __iomem *reg, unsigned long mask)
|
|
{
|
|
/* wait for cache operation by line or way to complete */
|
|
while (readl_relaxed(reg) & mask)
|
|
cpu_relax();
|
|
}
|
|
|
|
/*
|
|
* By default, we write directly to secure registers. Platforms must
|
|
* override this if they are running non-secure.
|
|
*/
|
|
static void l2c_write_sec(unsigned long val, void __iomem *base, unsigned reg)
|
|
{
|
|
if (val == readl_relaxed(base + reg))
|
|
return;
|
|
if (outer_cache.write_sec)
|
|
outer_cache.write_sec(val, reg);
|
|
else
|
|
writel_relaxed(val, base + reg);
|
|
}
|
|
|
|
/*
|
|
* This should only be called when we have a requirement that the
|
|
* register be written due to a work-around, as platforms running
|
|
* in non-secure mode may not be able to access this register.
|
|
*/
|
|
static inline void l2c_set_debug(void __iomem *base, unsigned long val)
|
|
{
|
|
l2c_write_sec(val, base, L2X0_DEBUG_CTRL);
|
|
}
|
|
|
|
static void __l2c_op_way(void __iomem *reg)
|
|
{
|
|
writel_relaxed(l2x0_way_mask, reg);
|
|
l2c_wait_mask(reg, l2x0_way_mask);
|
|
}
|
|
|
|
static inline void l2c_unlock(void __iomem *base, unsigned num)
|
|
{
|
|
unsigned i;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
writel_relaxed(0, base + L2X0_LOCKDOWN_WAY_D_BASE +
|
|
i * L2X0_LOCKDOWN_STRIDE);
|
|
writel_relaxed(0, base + L2X0_LOCKDOWN_WAY_I_BASE +
|
|
i * L2X0_LOCKDOWN_STRIDE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Enable the L2 cache controller. This function must only be
|
|
* called when the cache controller is known to be disabled.
|
|
*/
|
|
static void l2c_enable(void __iomem *base, u32 aux, unsigned num_lock)
|
|
{
|
|
unsigned long flags;
|
|
|
|
l2c_write_sec(aux, base, L2X0_AUX_CTRL);
|
|
|
|
l2c_unlock(base, num_lock);
|
|
|
|
local_irq_save(flags);
|
|
__l2c_op_way(base + L2X0_INV_WAY);
|
|
writel_relaxed(0, base + sync_reg_offset);
|
|
l2c_wait_mask(base + sync_reg_offset, 1);
|
|
local_irq_restore(flags);
|
|
|
|
l2c_write_sec(L2X0_CTRL_EN, base, L2X0_CTRL);
|
|
}
|
|
|
|
static void l2c_disable(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
outer_cache.flush_all();
|
|
l2c_write_sec(0, base, L2X0_CTRL);
|
|
dsb(st);
|
|
}
|
|
|
|
#ifdef CONFIG_CACHE_PL310
|
|
static inline void cache_wait(void __iomem *reg, unsigned long mask)
|
|
{
|
|
/* cache operations by line are atomic on PL310 */
|
|
}
|
|
#else
|
|
#define cache_wait l2c_wait_mask
|
|
#endif
|
|
|
|
static inline void cache_sync(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
writel_relaxed(0, base + sync_reg_offset);
|
|
cache_wait(base + L2X0_CACHE_SYNC, 1);
|
|
}
|
|
|
|
#if defined(CONFIG_PL310_ERRATA_588369) || defined(CONFIG_PL310_ERRATA_727915)
|
|
static inline void debug_writel(unsigned long val)
|
|
{
|
|
l2c_set_debug(l2x0_base, val);
|
|
}
|
|
#else
|
|
/* Optimised out for non-errata case */
|
|
static inline void debug_writel(unsigned long val)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static void l2x0_cache_sync(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
cache_sync();
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void __l2x0_flush_all(void)
|
|
{
|
|
debug_writel(0x03);
|
|
__l2c_op_way(l2x0_base + L2X0_CLEAN_INV_WAY);
|
|
cache_sync();
|
|
debug_writel(0x00);
|
|
}
|
|
|
|
static void l2x0_flush_all(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
/* clean all ways */
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
__l2x0_flush_all();
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void l2x0_disable(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
__l2x0_flush_all();
|
|
l2c_write_sec(0, l2x0_base, L2X0_CTRL);
|
|
dsb(st);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void l2c_save(void __iomem *base)
|
|
{
|
|
l2x0_saved_regs.aux_ctrl = readl_relaxed(l2x0_base + L2X0_AUX_CTRL);
|
|
}
|
|
|
|
/*
|
|
* L2C-210 specific code.
|
|
*
|
|
* The L2C-2x0 PA, set/way and sync operations are atomic, but we must
|
|
* ensure that no background operation is running. The way operations
|
|
* are all background tasks.
|
|
*
|
|
* While a background operation is in progress, any new operation is
|
|
* ignored (unspecified whether this causes an error.) Thankfully, not
|
|
* used on SMP.
|
|
*
|
|
* Never has a different sync register other than L2X0_CACHE_SYNC, but
|
|
* we use sync_reg_offset here so we can share some of this with L2C-310.
|
|
*/
|
|
static void __l2c210_cache_sync(void __iomem *base)
|
|
{
|
|
writel_relaxed(0, base + sync_reg_offset);
|
|
}
|
|
|
|
static void __l2c210_op_pa_range(void __iomem *reg, unsigned long start,
|
|
unsigned long end)
|
|
{
|
|
while (start < end) {
|
|
writel_relaxed(start, reg);
|
|
start += CACHE_LINE_SIZE;
|
|
}
|
|
}
|
|
|
|
static void l2c210_inv_range(unsigned long start, unsigned long end)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
if (start & (CACHE_LINE_SIZE - 1)) {
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
writel_relaxed(start, base + L2X0_CLEAN_INV_LINE_PA);
|
|
start += CACHE_LINE_SIZE;
|
|
}
|
|
|
|
if (end & (CACHE_LINE_SIZE - 1)) {
|
|
end &= ~(CACHE_LINE_SIZE - 1);
|
|
writel_relaxed(end, base + L2X0_CLEAN_INV_LINE_PA);
|
|
}
|
|
|
|
__l2c210_op_pa_range(base + L2X0_INV_LINE_PA, start, end);
|
|
__l2c210_cache_sync(base);
|
|
}
|
|
|
|
static void l2c210_clean_range(unsigned long start, unsigned long end)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
__l2c210_op_pa_range(base + L2X0_CLEAN_LINE_PA, start, end);
|
|
__l2c210_cache_sync(base);
|
|
}
|
|
|
|
static void l2c210_flush_range(unsigned long start, unsigned long end)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
__l2c210_op_pa_range(base + L2X0_CLEAN_INV_LINE_PA, start, end);
|
|
__l2c210_cache_sync(base);
|
|
}
|
|
|
|
static void l2c210_flush_all(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
BUG_ON(!irqs_disabled());
|
|
|
|
__l2c_op_way(base + L2X0_CLEAN_INV_WAY);
|
|
__l2c210_cache_sync(base);
|
|
}
|
|
|
|
static void l2c210_sync(void)
|
|
{
|
|
__l2c210_cache_sync(l2x0_base);
|
|
}
|
|
|
|
static void l2c210_resume(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
if (!(readl_relaxed(base + L2X0_CTRL) & L2X0_CTRL_EN))
|
|
l2c_enable(base, l2x0_saved_regs.aux_ctrl, 1);
|
|
}
|
|
|
|
static const struct l2c_init_data l2c210_data __initconst = {
|
|
.type = "L2C-210",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 1,
|
|
.enable = l2c_enable,
|
|
.save = l2c_save,
|
|
.outer_cache = {
|
|
.inv_range = l2c210_inv_range,
|
|
.clean_range = l2c210_clean_range,
|
|
.flush_range = l2c210_flush_range,
|
|
.flush_all = l2c210_flush_all,
|
|
.disable = l2c_disable,
|
|
.sync = l2c210_sync,
|
|
.resume = l2c210_resume,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* L2C-220 specific code.
|
|
*
|
|
* All operations are background operations: they have to be waited for.
|
|
* Conflicting requests generate a slave error (which will cause an
|
|
* imprecise abort.) Never uses sync_reg_offset, so we hard-code the
|
|
* sync register here.
|
|
*
|
|
* However, we can re-use the l2c210_resume call.
|
|
*/
|
|
static inline void __l2c220_cache_sync(void __iomem *base)
|
|
{
|
|
writel_relaxed(0, base + L2X0_CACHE_SYNC);
|
|
l2c_wait_mask(base + L2X0_CACHE_SYNC, 1);
|
|
}
|
|
|
|
static void l2c220_op_way(void __iomem *base, unsigned reg)
|
|
{
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
__l2c_op_way(base + reg);
|
|
__l2c220_cache_sync(base);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static unsigned long l2c220_op_pa_range(void __iomem *reg, unsigned long start,
|
|
unsigned long end, unsigned long flags)
|
|
{
|
|
raw_spinlock_t *lock = &l2x0_lock;
|
|
|
|
while (start < end) {
|
|
unsigned long blk_end = start + min(end - start, 4096UL);
|
|
|
|
while (start < blk_end) {
|
|
l2c_wait_mask(reg, 1);
|
|
writel_relaxed(start, reg);
|
|
start += CACHE_LINE_SIZE;
|
|
}
|
|
|
|
if (blk_end < end) {
|
|
raw_spin_unlock_irqrestore(lock, flags);
|
|
raw_spin_lock_irqsave(lock, flags);
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
static void l2c220_inv_range(unsigned long start, unsigned long end)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
if ((start | end) & (CACHE_LINE_SIZE - 1)) {
|
|
if (start & (CACHE_LINE_SIZE - 1)) {
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
writel_relaxed(start, base + L2X0_CLEAN_INV_LINE_PA);
|
|
start += CACHE_LINE_SIZE;
|
|
}
|
|
|
|
if (end & (CACHE_LINE_SIZE - 1)) {
|
|
end &= ~(CACHE_LINE_SIZE - 1);
|
|
l2c_wait_mask(base + L2X0_CLEAN_INV_LINE_PA, 1);
|
|
writel_relaxed(end, base + L2X0_CLEAN_INV_LINE_PA);
|
|
}
|
|
}
|
|
|
|
flags = l2c220_op_pa_range(base + L2X0_INV_LINE_PA,
|
|
start, end, flags);
|
|
l2c_wait_mask(base + L2X0_INV_LINE_PA, 1);
|
|
__l2c220_cache_sync(base);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void l2c220_clean_range(unsigned long start, unsigned long end)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
unsigned long flags;
|
|
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
if ((end - start) >= l2x0_size) {
|
|
l2c220_op_way(base, L2X0_CLEAN_WAY);
|
|
return;
|
|
}
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
flags = l2c220_op_pa_range(base + L2X0_CLEAN_LINE_PA,
|
|
start, end, flags);
|
|
l2c_wait_mask(base + L2X0_CLEAN_INV_LINE_PA, 1);
|
|
__l2c220_cache_sync(base);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void l2c220_flush_range(unsigned long start, unsigned long end)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
unsigned long flags;
|
|
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
if ((end - start) >= l2x0_size) {
|
|
l2c220_op_way(base, L2X0_CLEAN_INV_WAY);
|
|
return;
|
|
}
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
flags = l2c220_op_pa_range(base + L2X0_CLEAN_INV_LINE_PA,
|
|
start, end, flags);
|
|
l2c_wait_mask(base + L2X0_CLEAN_INV_LINE_PA, 1);
|
|
__l2c220_cache_sync(base);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void l2c220_flush_all(void)
|
|
{
|
|
l2c220_op_way(l2x0_base, L2X0_CLEAN_INV_WAY);
|
|
}
|
|
|
|
static void l2c220_sync(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
__l2c220_cache_sync(l2x0_base);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void l2c220_enable(void __iomem *base, u32 aux, unsigned num_lock)
|
|
{
|
|
/*
|
|
* Always enable non-secure access to the lockdown registers -
|
|
* we write to them as part of the L2C enable sequence so they
|
|
* need to be accessible.
|
|
*/
|
|
aux |= L220_AUX_CTRL_NS_LOCKDOWN;
|
|
|
|
l2c_enable(base, aux, num_lock);
|
|
}
|
|
|
|
static const struct l2c_init_data l2c220_data = {
|
|
.type = "L2C-220",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 1,
|
|
.enable = l2c220_enable,
|
|
.save = l2c_save,
|
|
.outer_cache = {
|
|
.inv_range = l2c220_inv_range,
|
|
.clean_range = l2c220_clean_range,
|
|
.flush_range = l2c220_flush_range,
|
|
.flush_all = l2c220_flush_all,
|
|
.disable = l2c_disable,
|
|
.sync = l2c220_sync,
|
|
.resume = l2c210_resume,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* L2C-310 specific code.
|
|
*
|
|
* Very similar to L2C-210, the PA, set/way and sync operations are atomic,
|
|
* and the way operations are all background tasks. However, issuing an
|
|
* operation while a background operation is in progress results in a
|
|
* SLVERR response. We can reuse:
|
|
*
|
|
* __l2c210_cache_sync (using sync_reg_offset)
|
|
* l2c210_sync
|
|
* l2c210_inv_range (if 588369 is not applicable)
|
|
* l2c210_clean_range
|
|
* l2c210_flush_range (if 588369 is not applicable)
|
|
* l2c210_flush_all (if 727915 is not applicable)
|
|
*
|
|
* Errata:
|
|
* 588369: PL310 R0P0->R1P0, fixed R2P0.
|
|
* Affects: all clean+invalidate operations
|
|
* clean and invalidate skips the invalidate step, so we need to issue
|
|
* separate operations. We also require the above debug workaround
|
|
* enclosing this code fragment on affected parts. On unaffected parts,
|
|
* we must not use this workaround without the debug register writes
|
|
* to avoid exposing a problem similar to 727915.
|
|
*
|
|
* 727915: PL310 R2P0->R3P0, fixed R3P1.
|
|
* Affects: clean+invalidate by way
|
|
* clean and invalidate by way runs in the background, and a store can
|
|
* hit the line between the clean operation and invalidate operation,
|
|
* resulting in the store being lost.
|
|
*
|
|
* 752271: PL310 R3P0->R3P1-50REL0, fixed R3P2.
|
|
* Affects: 8x64-bit (double fill) line fetches
|
|
* double fill line fetches can fail to cause dirty data to be evicted
|
|
* from the cache before the new data overwrites the second line.
|
|
*
|
|
* 753970: PL310 R3P0, fixed R3P1.
|
|
* Affects: sync
|
|
* prevents merging writes after the sync operation, until another L2C
|
|
* operation is performed (or a number of other conditions.)
|
|
*
|
|
* 769419: PL310 R0P0->R3P1, fixed R3P2.
|
|
* Affects: store buffer
|
|
* store buffer is not automatically drained.
|
|
*/
|
|
static void l2c310_inv_range_erratum(unsigned long start, unsigned long end)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
if ((start | end) & (CACHE_LINE_SIZE - 1)) {
|
|
unsigned long flags;
|
|
|
|
/* Erratum 588369 for both clean+invalidate operations */
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
l2c_set_debug(base, 0x03);
|
|
|
|
if (start & (CACHE_LINE_SIZE - 1)) {
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
writel_relaxed(start, base + L2X0_CLEAN_LINE_PA);
|
|
writel_relaxed(start, base + L2X0_INV_LINE_PA);
|
|
start += CACHE_LINE_SIZE;
|
|
}
|
|
|
|
if (end & (CACHE_LINE_SIZE - 1)) {
|
|
end &= ~(CACHE_LINE_SIZE - 1);
|
|
writel_relaxed(end, base + L2X0_CLEAN_LINE_PA);
|
|
writel_relaxed(end, base + L2X0_INV_LINE_PA);
|
|
}
|
|
|
|
l2c_set_debug(base, 0x00);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
__l2c210_op_pa_range(base + L2X0_INV_LINE_PA, start, end);
|
|
__l2c210_cache_sync(base);
|
|
}
|
|
|
|
static void l2c310_flush_range_erratum(unsigned long start, unsigned long end)
|
|
{
|
|
raw_spinlock_t *lock = &l2x0_lock;
|
|
unsigned long flags;
|
|
void __iomem *base = l2x0_base;
|
|
|
|
raw_spin_lock_irqsave(lock, flags);
|
|
while (start < end) {
|
|
unsigned long blk_end = start + min(end - start, 4096UL);
|
|
|
|
l2c_set_debug(base, 0x03);
|
|
while (start < blk_end) {
|
|
writel_relaxed(start, base + L2X0_CLEAN_LINE_PA);
|
|
writel_relaxed(start, base + L2X0_INV_LINE_PA);
|
|
start += CACHE_LINE_SIZE;
|
|
}
|
|
l2c_set_debug(base, 0x00);
|
|
|
|
if (blk_end < end) {
|
|
raw_spin_unlock_irqrestore(lock, flags);
|
|
raw_spin_lock_irqsave(lock, flags);
|
|
}
|
|
}
|
|
raw_spin_unlock_irqrestore(lock, flags);
|
|
__l2c210_cache_sync(base);
|
|
}
|
|
|
|
static void l2c310_flush_all_erratum(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
l2c_set_debug(base, 0x03);
|
|
__l2c_op_way(base + L2X0_CLEAN_INV_WAY);
|
|
l2c_set_debug(base, 0x00);
|
|
__l2c210_cache_sync(base);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
}
|
|
|
|
static void __init l2c310_save(void __iomem *base)
|
|
{
|
|
unsigned revision;
|
|
|
|
l2c_save(base);
|
|
|
|
l2x0_saved_regs.tag_latency = readl_relaxed(base +
|
|
L310_TAG_LATENCY_CTRL);
|
|
l2x0_saved_regs.data_latency = readl_relaxed(base +
|
|
L310_DATA_LATENCY_CTRL);
|
|
l2x0_saved_regs.filter_end = readl_relaxed(base +
|
|
L310_ADDR_FILTER_END);
|
|
l2x0_saved_regs.filter_start = readl_relaxed(base +
|
|
L310_ADDR_FILTER_START);
|
|
|
|
revision = readl_relaxed(base + L2X0_CACHE_ID) &
|
|
L2X0_CACHE_ID_RTL_MASK;
|
|
|
|
/* From r2p0, there is Prefetch offset/control register */
|
|
if (revision >= L310_CACHE_ID_RTL_R2P0)
|
|
l2x0_saved_regs.prefetch_ctrl = readl_relaxed(base +
|
|
L310_PREFETCH_CTRL);
|
|
|
|
/* From r3p0, there is Power control register */
|
|
if (revision >= L310_CACHE_ID_RTL_R3P0)
|
|
l2x0_saved_regs.pwr_ctrl = readl_relaxed(base +
|
|
L310_POWER_CTRL);
|
|
}
|
|
|
|
static void l2c310_resume(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
if (!(readl_relaxed(base + L2X0_CTRL) & L2X0_CTRL_EN)) {
|
|
unsigned revision;
|
|
|
|
/* restore pl310 setup */
|
|
writel_relaxed(l2x0_saved_regs.tag_latency,
|
|
base + L310_TAG_LATENCY_CTRL);
|
|
writel_relaxed(l2x0_saved_regs.data_latency,
|
|
base + L310_DATA_LATENCY_CTRL);
|
|
writel_relaxed(l2x0_saved_regs.filter_end,
|
|
base + L310_ADDR_FILTER_END);
|
|
writel_relaxed(l2x0_saved_regs.filter_start,
|
|
base + L310_ADDR_FILTER_START);
|
|
|
|
revision = readl_relaxed(base + L2X0_CACHE_ID) &
|
|
L2X0_CACHE_ID_RTL_MASK;
|
|
|
|
if (revision >= L310_CACHE_ID_RTL_R2P0)
|
|
l2c_write_sec(l2x0_saved_regs.prefetch_ctrl, base,
|
|
L310_PREFETCH_CTRL);
|
|
if (revision >= L310_CACHE_ID_RTL_R3P0)
|
|
l2c_write_sec(l2x0_saved_regs.pwr_ctrl, base,
|
|
L310_POWER_CTRL);
|
|
|
|
l2c_enable(base, l2x0_saved_regs.aux_ctrl, 8);
|
|
|
|
/* Re-enable full-line-of-zeros for Cortex-A9 */
|
|
if (l2x0_saved_regs.aux_ctrl & L310_AUX_CTRL_FULL_LINE_ZERO)
|
|
set_auxcr(get_auxcr() | BIT(3) | BIT(2) | BIT(1));
|
|
}
|
|
}
|
|
|
|
static int l2c310_cpu_enable_flz(struct notifier_block *nb, unsigned long act, void *data)
|
|
{
|
|
switch (act & ~CPU_TASKS_FROZEN) {
|
|
case CPU_STARTING:
|
|
set_auxcr(get_auxcr() | BIT(3) | BIT(2) | BIT(1));
|
|
break;
|
|
case CPU_DYING:
|
|
set_auxcr(get_auxcr() & ~(BIT(3) | BIT(2) | BIT(1)));
|
|
break;
|
|
}
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static void __init l2c310_enable(void __iomem *base, u32 aux, unsigned num_lock)
|
|
{
|
|
unsigned rev = readl_relaxed(base + L2X0_CACHE_ID) & L2X0_CACHE_ID_RTL_MASK;
|
|
bool cortex_a9 = read_cpuid_part() == ARM_CPU_PART_CORTEX_A9;
|
|
|
|
if (rev >= L310_CACHE_ID_RTL_R2P0) {
|
|
if (cortex_a9) {
|
|
aux |= L310_AUX_CTRL_EARLY_BRESP;
|
|
pr_info("L2C-310 enabling early BRESP for Cortex-A9\n");
|
|
} else if (aux & L310_AUX_CTRL_EARLY_BRESP) {
|
|
pr_warn("L2C-310 early BRESP only supported with Cortex-A9\n");
|
|
aux &= ~L310_AUX_CTRL_EARLY_BRESP;
|
|
}
|
|
}
|
|
|
|
if (cortex_a9) {
|
|
u32 aux_cur = readl_relaxed(base + L2X0_AUX_CTRL);
|
|
u32 acr = get_auxcr();
|
|
|
|
pr_debug("Cortex-A9 ACR=0x%08x\n", acr);
|
|
|
|
if (acr & BIT(3) && !(aux_cur & L310_AUX_CTRL_FULL_LINE_ZERO))
|
|
pr_err("L2C-310: full line of zeros enabled in Cortex-A9 but not L2C-310 - invalid\n");
|
|
|
|
if (aux & L310_AUX_CTRL_FULL_LINE_ZERO && !(acr & BIT(3)))
|
|
pr_err("L2C-310: enabling full line of zeros but not enabled in Cortex-A9\n");
|
|
|
|
if (!(aux & L310_AUX_CTRL_FULL_LINE_ZERO) && !outer_cache.write_sec) {
|
|
aux |= L310_AUX_CTRL_FULL_LINE_ZERO;
|
|
pr_info("L2C-310 full line of zeros enabled for Cortex-A9\n");
|
|
}
|
|
} else if (aux & (L310_AUX_CTRL_FULL_LINE_ZERO | L310_AUX_CTRL_EARLY_BRESP)) {
|
|
pr_err("L2C-310: disabling Cortex-A9 specific feature bits\n");
|
|
aux &= ~(L310_AUX_CTRL_FULL_LINE_ZERO | L310_AUX_CTRL_EARLY_BRESP);
|
|
}
|
|
|
|
if (aux & (L310_AUX_CTRL_DATA_PREFETCH | L310_AUX_CTRL_INSTR_PREFETCH)) {
|
|
u32 prefetch = readl_relaxed(base + L310_PREFETCH_CTRL);
|
|
|
|
pr_info("L2C-310 %s%s prefetch enabled, offset %u lines\n",
|
|
aux & L310_AUX_CTRL_INSTR_PREFETCH ? "I" : "",
|
|
aux & L310_AUX_CTRL_DATA_PREFETCH ? "D" : "",
|
|
1 + (prefetch & L310_PREFETCH_CTRL_OFFSET_MASK));
|
|
}
|
|
|
|
/* r3p0 or later has power control register */
|
|
if (rev >= L310_CACHE_ID_RTL_R3P0) {
|
|
u32 power_ctrl;
|
|
|
|
l2c_write_sec(L310_DYNAMIC_CLK_GATING_EN | L310_STNDBY_MODE_EN,
|
|
base, L310_POWER_CTRL);
|
|
power_ctrl = readl_relaxed(base + L310_POWER_CTRL);
|
|
pr_info("L2C-310 dynamic clock gating %sabled, standby mode %sabled\n",
|
|
power_ctrl & L310_DYNAMIC_CLK_GATING_EN ? "en" : "dis",
|
|
power_ctrl & L310_STNDBY_MODE_EN ? "en" : "dis");
|
|
}
|
|
|
|
/*
|
|
* Always enable non-secure access to the lockdown registers -
|
|
* we write to them as part of the L2C enable sequence so they
|
|
* need to be accessible.
|
|
*/
|
|
aux |= L310_AUX_CTRL_NS_LOCKDOWN;
|
|
|
|
l2c_enable(base, aux, num_lock);
|
|
|
|
if (aux & L310_AUX_CTRL_FULL_LINE_ZERO) {
|
|
set_auxcr(get_auxcr() | BIT(3) | BIT(2) | BIT(1));
|
|
cpu_notifier(l2c310_cpu_enable_flz, 0);
|
|
}
|
|
}
|
|
|
|
static void __init l2c310_fixup(void __iomem *base, u32 cache_id,
|
|
struct outer_cache_fns *fns)
|
|
{
|
|
unsigned revision = cache_id & L2X0_CACHE_ID_RTL_MASK;
|
|
const char *errata[8];
|
|
unsigned n = 0;
|
|
|
|
if (IS_ENABLED(CONFIG_PL310_ERRATA_588369) &&
|
|
revision < L310_CACHE_ID_RTL_R2P0 &&
|
|
/* For bcm compatibility */
|
|
fns->inv_range == l2c210_inv_range) {
|
|
fns->inv_range = l2c310_inv_range_erratum;
|
|
fns->flush_range = l2c310_flush_range_erratum;
|
|
errata[n++] = "588369";
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PL310_ERRATA_727915) &&
|
|
revision >= L310_CACHE_ID_RTL_R2P0 &&
|
|
revision < L310_CACHE_ID_RTL_R3P1) {
|
|
fns->flush_all = l2c310_flush_all_erratum;
|
|
errata[n++] = "727915";
|
|
}
|
|
|
|
if (revision >= L310_CACHE_ID_RTL_R3P0 &&
|
|
revision < L310_CACHE_ID_RTL_R3P2) {
|
|
u32 val = readl_relaxed(base + L310_PREFETCH_CTRL);
|
|
/* I don't think bit23 is required here... but iMX6 does so */
|
|
if (val & (BIT(30) | BIT(23))) {
|
|
val &= ~(BIT(30) | BIT(23));
|
|
l2c_write_sec(val, base, L310_PREFETCH_CTRL);
|
|
errata[n++] = "752271";
|
|
}
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PL310_ERRATA_753970) &&
|
|
revision == L310_CACHE_ID_RTL_R3P0) {
|
|
sync_reg_offset = L2X0_DUMMY_REG;
|
|
errata[n++] = "753970";
|
|
}
|
|
|
|
if (IS_ENABLED(CONFIG_PL310_ERRATA_769419))
|
|
errata[n++] = "769419";
|
|
|
|
if (n) {
|
|
unsigned i;
|
|
|
|
pr_info("L2C-310 errat%s", n > 1 ? "a" : "um");
|
|
for (i = 0; i < n; i++)
|
|
pr_cont(" %s", errata[i]);
|
|
pr_cont(" enabled\n");
|
|
}
|
|
}
|
|
|
|
static void l2c310_disable(void)
|
|
{
|
|
/*
|
|
* If full-line-of-zeros is enabled, we must first disable it in the
|
|
* Cortex-A9 auxiliary control register before disabling the L2 cache.
|
|
*/
|
|
if (l2x0_saved_regs.aux_ctrl & L310_AUX_CTRL_FULL_LINE_ZERO)
|
|
set_auxcr(get_auxcr() & ~(BIT(3) | BIT(2) | BIT(1)));
|
|
|
|
l2c_disable();
|
|
}
|
|
|
|
static const struct l2c_init_data l2c310_init_fns __initconst = {
|
|
.type = "L2C-310",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 8,
|
|
.enable = l2c310_enable,
|
|
.fixup = l2c310_fixup,
|
|
.save = l2c310_save,
|
|
.outer_cache = {
|
|
.inv_range = l2c210_inv_range,
|
|
.clean_range = l2c210_clean_range,
|
|
.flush_range = l2c210_flush_range,
|
|
.flush_all = l2c210_flush_all,
|
|
.disable = l2c310_disable,
|
|
.sync = l2c210_sync,
|
|
.resume = l2c310_resume,
|
|
},
|
|
};
|
|
|
|
static void __init __l2c_init(const struct l2c_init_data *data,
|
|
u32 aux_val, u32 aux_mask, u32 cache_id)
|
|
{
|
|
struct outer_cache_fns fns;
|
|
unsigned way_size_bits, ways;
|
|
u32 aux, old_aux;
|
|
|
|
/*
|
|
* Sanity check the aux values. aux_mask is the bits we preserve
|
|
* from reading the hardware register, and aux_val is the bits we
|
|
* set.
|
|
*/
|
|
if (aux_val & aux_mask)
|
|
pr_alert("L2C: platform provided aux values permit register corruption.\n");
|
|
|
|
old_aux = aux = readl_relaxed(l2x0_base + L2X0_AUX_CTRL);
|
|
aux &= aux_mask;
|
|
aux |= aux_val;
|
|
|
|
if (old_aux != aux)
|
|
pr_warn("L2C: DT/platform modifies aux control register: 0x%08x -> 0x%08x\n",
|
|
old_aux, aux);
|
|
|
|
/* Determine the number of ways */
|
|
switch (cache_id & L2X0_CACHE_ID_PART_MASK) {
|
|
case L2X0_CACHE_ID_PART_L310:
|
|
if ((aux_val | ~aux_mask) & (L2C_AUX_CTRL_WAY_SIZE_MASK | L310_AUX_CTRL_ASSOCIATIVITY_16))
|
|
pr_warn("L2C: DT/platform tries to modify or specify cache size\n");
|
|
if (aux & (1 << 16))
|
|
ways = 16;
|
|
else
|
|
ways = 8;
|
|
break;
|
|
|
|
case L2X0_CACHE_ID_PART_L210:
|
|
case L2X0_CACHE_ID_PART_L220:
|
|
ways = (aux >> 13) & 0xf;
|
|
break;
|
|
|
|
case AURORA_CACHE_ID:
|
|
ways = (aux >> 13) & 0xf;
|
|
ways = 2 << ((ways + 1) >> 2);
|
|
break;
|
|
|
|
default:
|
|
/* Assume unknown chips have 8 ways */
|
|
ways = 8;
|
|
break;
|
|
}
|
|
|
|
l2x0_way_mask = (1 << ways) - 1;
|
|
|
|
/*
|
|
* way_size_0 is the size that a way_size value of zero would be
|
|
* given the calculation: way_size = way_size_0 << way_size_bits.
|
|
* So, if way_size_bits=0 is reserved, but way_size_bits=1 is 16k,
|
|
* then way_size_0 would be 8k.
|
|
*
|
|
* L2 cache size = number of ways * way size.
|
|
*/
|
|
way_size_bits = (aux & L2C_AUX_CTRL_WAY_SIZE_MASK) >>
|
|
L2C_AUX_CTRL_WAY_SIZE_SHIFT;
|
|
l2x0_size = ways * (data->way_size_0 << way_size_bits);
|
|
|
|
fns = data->outer_cache;
|
|
fns.write_sec = outer_cache.write_sec;
|
|
if (data->fixup)
|
|
data->fixup(l2x0_base, cache_id, &fns);
|
|
|
|
/*
|
|
* Check if l2x0 controller is already enabled. If we are booting
|
|
* in non-secure mode accessing the below registers will fault.
|
|
*/
|
|
if (!(readl_relaxed(l2x0_base + L2X0_CTRL) & L2X0_CTRL_EN))
|
|
data->enable(l2x0_base, aux, data->num_lock);
|
|
|
|
outer_cache = fns;
|
|
|
|
/*
|
|
* It is strange to save the register state before initialisation,
|
|
* but hey, this is what the DT implementations decided to do.
|
|
*/
|
|
if (data->save)
|
|
data->save(l2x0_base);
|
|
|
|
/* Re-read it in case some bits are reserved. */
|
|
aux = readl_relaxed(l2x0_base + L2X0_AUX_CTRL);
|
|
|
|
pr_info("%s cache controller enabled, %d ways, %d kB\n",
|
|
data->type, ways, l2x0_size >> 10);
|
|
pr_info("%s: CACHE_ID 0x%08x, AUX_CTRL 0x%08x\n",
|
|
data->type, cache_id, aux);
|
|
}
|
|
|
|
void __init l2x0_init(void __iomem *base, u32 aux_val, u32 aux_mask)
|
|
{
|
|
const struct l2c_init_data *data;
|
|
u32 cache_id;
|
|
|
|
l2x0_base = base;
|
|
|
|
cache_id = readl_relaxed(base + L2X0_CACHE_ID);
|
|
|
|
switch (cache_id & L2X0_CACHE_ID_PART_MASK) {
|
|
default:
|
|
case L2X0_CACHE_ID_PART_L210:
|
|
data = &l2c210_data;
|
|
break;
|
|
|
|
case L2X0_CACHE_ID_PART_L220:
|
|
data = &l2c220_data;
|
|
break;
|
|
|
|
case L2X0_CACHE_ID_PART_L310:
|
|
data = &l2c310_init_fns;
|
|
break;
|
|
}
|
|
|
|
__l2c_init(data, aux_val, aux_mask, cache_id);
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static int l2_wt_override;
|
|
|
|
/* Aurora don't have the cache ID register available, so we have to
|
|
* pass it though the device tree */
|
|
static u32 cache_id_part_number_from_dt;
|
|
|
|
/**
|
|
* l2x0_cache_size_of_parse() - read cache size parameters from DT
|
|
* @np: the device tree node for the l2 cache
|
|
* @aux_val: pointer to machine-supplied auxilary register value, to
|
|
* be augmented by the call (bits to be set to 1)
|
|
* @aux_mask: pointer to machine-supplied auxilary register mask, to
|
|
* be augmented by the call (bits to be set to 0)
|
|
* @associativity: variable to return the calculated associativity in
|
|
* @max_way_size: the maximum size in bytes for the cache ways
|
|
*/
|
|
static void __init l2x0_cache_size_of_parse(const struct device_node *np,
|
|
u32 *aux_val, u32 *aux_mask,
|
|
u32 *associativity,
|
|
u32 max_way_size)
|
|
{
|
|
u32 mask = 0, val = 0;
|
|
u32 cache_size = 0, sets = 0;
|
|
u32 way_size_bits = 1;
|
|
u32 way_size = 0;
|
|
u32 block_size = 0;
|
|
u32 line_size = 0;
|
|
|
|
of_property_read_u32(np, "cache-size", &cache_size);
|
|
of_property_read_u32(np, "cache-sets", &sets);
|
|
of_property_read_u32(np, "cache-block-size", &block_size);
|
|
of_property_read_u32(np, "cache-line-size", &line_size);
|
|
|
|
if (!cache_size || !sets)
|
|
return;
|
|
|
|
/* All these l2 caches have the same line = block size actually */
|
|
if (!line_size) {
|
|
if (block_size) {
|
|
/* If linesize if not given, it is equal to blocksize */
|
|
line_size = block_size;
|
|
} else {
|
|
/* Fall back to known size */
|
|
pr_warn("L2C OF: no cache block/line size given: "
|
|
"falling back to default size %d bytes\n",
|
|
CACHE_LINE_SIZE);
|
|
line_size = CACHE_LINE_SIZE;
|
|
}
|
|
}
|
|
|
|
if (line_size != CACHE_LINE_SIZE)
|
|
pr_warn("L2C OF: DT supplied line size %d bytes does "
|
|
"not match hardware line size of %d bytes\n",
|
|
line_size,
|
|
CACHE_LINE_SIZE);
|
|
|
|
/*
|
|
* Since:
|
|
* set size = cache size / sets
|
|
* ways = cache size / (sets * line size)
|
|
* way size = cache size / (cache size / (sets * line size))
|
|
* way size = sets * line size
|
|
* associativity = ways = cache size / way size
|
|
*/
|
|
way_size = sets * line_size;
|
|
*associativity = cache_size / way_size;
|
|
|
|
if (way_size > max_way_size) {
|
|
pr_err("L2C OF: set size %dKB is too large\n", way_size);
|
|
return;
|
|
}
|
|
|
|
pr_info("L2C OF: override cache size: %d bytes (%dKB)\n",
|
|
cache_size, cache_size >> 10);
|
|
pr_info("L2C OF: override line size: %d bytes\n", line_size);
|
|
pr_info("L2C OF: override way size: %d bytes (%dKB)\n",
|
|
way_size, way_size >> 10);
|
|
pr_info("L2C OF: override associativity: %d\n", *associativity);
|
|
|
|
/*
|
|
* Calculates the bits 17:19 to set for way size:
|
|
* 512KB -> 6, 256KB -> 5, ... 16KB -> 1
|
|
*/
|
|
way_size_bits = ilog2(way_size >> 10) - 3;
|
|
if (way_size_bits < 1 || way_size_bits > 6) {
|
|
pr_err("L2C OF: cache way size illegal: %dKB is not mapped\n",
|
|
way_size);
|
|
return;
|
|
}
|
|
|
|
mask |= L2C_AUX_CTRL_WAY_SIZE_MASK;
|
|
val |= (way_size_bits << L2C_AUX_CTRL_WAY_SIZE_SHIFT);
|
|
|
|
*aux_val &= ~mask;
|
|
*aux_val |= val;
|
|
*aux_mask &= ~mask;
|
|
}
|
|
|
|
static void __init l2x0_of_parse(const struct device_node *np,
|
|
u32 *aux_val, u32 *aux_mask)
|
|
{
|
|
u32 data[2] = { 0, 0 };
|
|
u32 tag = 0;
|
|
u32 dirty = 0;
|
|
u32 val = 0, mask = 0;
|
|
u32 assoc;
|
|
|
|
of_property_read_u32(np, "arm,tag-latency", &tag);
|
|
if (tag) {
|
|
mask |= L2X0_AUX_CTRL_TAG_LATENCY_MASK;
|
|
val |= (tag - 1) << L2X0_AUX_CTRL_TAG_LATENCY_SHIFT;
|
|
}
|
|
|
|
of_property_read_u32_array(np, "arm,data-latency",
|
|
data, ARRAY_SIZE(data));
|
|
if (data[0] && data[1]) {
|
|
mask |= L2X0_AUX_CTRL_DATA_RD_LATENCY_MASK |
|
|
L2X0_AUX_CTRL_DATA_WR_LATENCY_MASK;
|
|
val |= ((data[0] - 1) << L2X0_AUX_CTRL_DATA_RD_LATENCY_SHIFT) |
|
|
((data[1] - 1) << L2X0_AUX_CTRL_DATA_WR_LATENCY_SHIFT);
|
|
}
|
|
|
|
of_property_read_u32(np, "arm,dirty-latency", &dirty);
|
|
if (dirty) {
|
|
mask |= L2X0_AUX_CTRL_DIRTY_LATENCY_MASK;
|
|
val |= (dirty - 1) << L2X0_AUX_CTRL_DIRTY_LATENCY_SHIFT;
|
|
}
|
|
|
|
l2x0_cache_size_of_parse(np, aux_val, aux_mask, &assoc, SZ_256K);
|
|
if (assoc > 8) {
|
|
pr_err("l2x0 of: cache setting yield too high associativity\n");
|
|
pr_err("l2x0 of: %d calculated, max 8\n", assoc);
|
|
} else {
|
|
mask |= L2X0_AUX_CTRL_ASSOC_MASK;
|
|
val |= (assoc << L2X0_AUX_CTRL_ASSOC_SHIFT);
|
|
}
|
|
|
|
*aux_val &= ~mask;
|
|
*aux_val |= val;
|
|
*aux_mask &= ~mask;
|
|
}
|
|
|
|
static const struct l2c_init_data of_l2c210_data __initconst = {
|
|
.type = "L2C-210",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 1,
|
|
.of_parse = l2x0_of_parse,
|
|
.enable = l2c_enable,
|
|
.save = l2c_save,
|
|
.outer_cache = {
|
|
.inv_range = l2c210_inv_range,
|
|
.clean_range = l2c210_clean_range,
|
|
.flush_range = l2c210_flush_range,
|
|
.flush_all = l2c210_flush_all,
|
|
.disable = l2c_disable,
|
|
.sync = l2c210_sync,
|
|
.resume = l2c210_resume,
|
|
},
|
|
};
|
|
|
|
static const struct l2c_init_data of_l2c220_data __initconst = {
|
|
.type = "L2C-220",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 1,
|
|
.of_parse = l2x0_of_parse,
|
|
.enable = l2c220_enable,
|
|
.save = l2c_save,
|
|
.outer_cache = {
|
|
.inv_range = l2c220_inv_range,
|
|
.clean_range = l2c220_clean_range,
|
|
.flush_range = l2c220_flush_range,
|
|
.flush_all = l2c220_flush_all,
|
|
.disable = l2c_disable,
|
|
.sync = l2c220_sync,
|
|
.resume = l2c210_resume,
|
|
},
|
|
};
|
|
|
|
static void __init l2c310_of_parse(const struct device_node *np,
|
|
u32 *aux_val, u32 *aux_mask)
|
|
{
|
|
u32 data[3] = { 0, 0, 0 };
|
|
u32 tag[3] = { 0, 0, 0 };
|
|
u32 filter[2] = { 0, 0 };
|
|
u32 assoc;
|
|
|
|
of_property_read_u32_array(np, "arm,tag-latency", tag, ARRAY_SIZE(tag));
|
|
if (tag[0] && tag[1] && tag[2])
|
|
writel_relaxed(
|
|
L310_LATENCY_CTRL_RD(tag[0] - 1) |
|
|
L310_LATENCY_CTRL_WR(tag[1] - 1) |
|
|
L310_LATENCY_CTRL_SETUP(tag[2] - 1),
|
|
l2x0_base + L310_TAG_LATENCY_CTRL);
|
|
|
|
of_property_read_u32_array(np, "arm,data-latency",
|
|
data, ARRAY_SIZE(data));
|
|
if (data[0] && data[1] && data[2])
|
|
writel_relaxed(
|
|
L310_LATENCY_CTRL_RD(data[0] - 1) |
|
|
L310_LATENCY_CTRL_WR(data[1] - 1) |
|
|
L310_LATENCY_CTRL_SETUP(data[2] - 1),
|
|
l2x0_base + L310_DATA_LATENCY_CTRL);
|
|
|
|
of_property_read_u32_array(np, "arm,filter-ranges",
|
|
filter, ARRAY_SIZE(filter));
|
|
if (filter[1]) {
|
|
writel_relaxed(ALIGN(filter[0] + filter[1], SZ_1M),
|
|
l2x0_base + L310_ADDR_FILTER_END);
|
|
writel_relaxed((filter[0] & ~(SZ_1M - 1)) | L310_ADDR_FILTER_EN,
|
|
l2x0_base + L310_ADDR_FILTER_START);
|
|
}
|
|
|
|
l2x0_cache_size_of_parse(np, aux_val, aux_mask, &assoc, SZ_512K);
|
|
switch (assoc) {
|
|
case 16:
|
|
*aux_val &= ~L2X0_AUX_CTRL_ASSOC_MASK;
|
|
*aux_val |= L310_AUX_CTRL_ASSOCIATIVITY_16;
|
|
*aux_mask &= ~L2X0_AUX_CTRL_ASSOC_MASK;
|
|
break;
|
|
case 8:
|
|
*aux_val &= ~L2X0_AUX_CTRL_ASSOC_MASK;
|
|
*aux_mask &= ~L2X0_AUX_CTRL_ASSOC_MASK;
|
|
break;
|
|
default:
|
|
pr_err("PL310 OF: cache setting yield illegal associativity\n");
|
|
pr_err("PL310 OF: %d calculated, only 8 and 16 legal\n", assoc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const struct l2c_init_data of_l2c310_data __initconst = {
|
|
.type = "L2C-310",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 8,
|
|
.of_parse = l2c310_of_parse,
|
|
.enable = l2c310_enable,
|
|
.fixup = l2c310_fixup,
|
|
.save = l2c310_save,
|
|
.outer_cache = {
|
|
.inv_range = l2c210_inv_range,
|
|
.clean_range = l2c210_clean_range,
|
|
.flush_range = l2c210_flush_range,
|
|
.flush_all = l2c210_flush_all,
|
|
.disable = l2c310_disable,
|
|
.sync = l2c210_sync,
|
|
.resume = l2c310_resume,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* This is a variant of the of_l2c310_data with .sync set to
|
|
* NULL. Outer sync operations are not needed when the system is I/O
|
|
* coherent, and potentially harmful in certain situations (PCIe/PL310
|
|
* deadlock on Armada 375/38x due to hardware I/O coherency). The
|
|
* other operations are kept because they are infrequent (therefore do
|
|
* not cause the deadlock in practice) and needed for secondary CPU
|
|
* boot and other power management activities.
|
|
*/
|
|
static const struct l2c_init_data of_l2c310_coherent_data __initconst = {
|
|
.type = "L2C-310 Coherent",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 8,
|
|
.of_parse = l2c310_of_parse,
|
|
.enable = l2c310_enable,
|
|
.fixup = l2c310_fixup,
|
|
.save = l2c310_save,
|
|
.outer_cache = {
|
|
.inv_range = l2c210_inv_range,
|
|
.clean_range = l2c210_clean_range,
|
|
.flush_range = l2c210_flush_range,
|
|
.flush_all = l2c210_flush_all,
|
|
.disable = l2c310_disable,
|
|
.resume = l2c310_resume,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* Note that the end addresses passed to Linux primitives are
|
|
* noninclusive, while the hardware cache range operations use
|
|
* inclusive start and end addresses.
|
|
*/
|
|
static unsigned long calc_range_end(unsigned long start, unsigned long end)
|
|
{
|
|
/*
|
|
* Limit the number of cache lines processed at once,
|
|
* since cache range operations stall the CPU pipeline
|
|
* until completion.
|
|
*/
|
|
if (end > start + MAX_RANGE_SIZE)
|
|
end = start + MAX_RANGE_SIZE;
|
|
|
|
/*
|
|
* Cache range operations can't straddle a page boundary.
|
|
*/
|
|
if (end > PAGE_ALIGN(start+1))
|
|
end = PAGE_ALIGN(start+1);
|
|
|
|
return end;
|
|
}
|
|
|
|
/*
|
|
* Make sure 'start' and 'end' reference the same page, as L2 is PIPT
|
|
* and range operations only do a TLB lookup on the start address.
|
|
*/
|
|
static void aurora_pa_range(unsigned long start, unsigned long end,
|
|
unsigned long offset)
|
|
{
|
|
unsigned long flags;
|
|
|
|
raw_spin_lock_irqsave(&l2x0_lock, flags);
|
|
writel_relaxed(start, l2x0_base + AURORA_RANGE_BASE_ADDR_REG);
|
|
writel_relaxed(end, l2x0_base + offset);
|
|
raw_spin_unlock_irqrestore(&l2x0_lock, flags);
|
|
|
|
cache_sync();
|
|
}
|
|
|
|
static void aurora_inv_range(unsigned long start, unsigned long end)
|
|
{
|
|
/*
|
|
* round start and end adresses up to cache line size
|
|
*/
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
end = ALIGN(end, CACHE_LINE_SIZE);
|
|
|
|
/*
|
|
* Invalidate all full cache lines between 'start' and 'end'.
|
|
*/
|
|
while (start < end) {
|
|
unsigned long range_end = calc_range_end(start, end);
|
|
aurora_pa_range(start, range_end - CACHE_LINE_SIZE,
|
|
AURORA_INVAL_RANGE_REG);
|
|
start = range_end;
|
|
}
|
|
}
|
|
|
|
static void aurora_clean_range(unsigned long start, unsigned long end)
|
|
{
|
|
/*
|
|
* If L2 is forced to WT, the L2 will always be clean and we
|
|
* don't need to do anything here.
|
|
*/
|
|
if (!l2_wt_override) {
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
end = ALIGN(end, CACHE_LINE_SIZE);
|
|
while (start != end) {
|
|
unsigned long range_end = calc_range_end(start, end);
|
|
aurora_pa_range(start, range_end - CACHE_LINE_SIZE,
|
|
AURORA_CLEAN_RANGE_REG);
|
|
start = range_end;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void aurora_flush_range(unsigned long start, unsigned long end)
|
|
{
|
|
start &= ~(CACHE_LINE_SIZE - 1);
|
|
end = ALIGN(end, CACHE_LINE_SIZE);
|
|
while (start != end) {
|
|
unsigned long range_end = calc_range_end(start, end);
|
|
/*
|
|
* If L2 is forced to WT, the L2 will always be clean and we
|
|
* just need to invalidate.
|
|
*/
|
|
if (l2_wt_override)
|
|
aurora_pa_range(start, range_end - CACHE_LINE_SIZE,
|
|
AURORA_INVAL_RANGE_REG);
|
|
else
|
|
aurora_pa_range(start, range_end - CACHE_LINE_SIZE,
|
|
AURORA_FLUSH_RANGE_REG);
|
|
start = range_end;
|
|
}
|
|
}
|
|
|
|
static void aurora_save(void __iomem *base)
|
|
{
|
|
l2x0_saved_regs.ctrl = readl_relaxed(base + L2X0_CTRL);
|
|
l2x0_saved_regs.aux_ctrl = readl_relaxed(base + L2X0_AUX_CTRL);
|
|
}
|
|
|
|
static void aurora_resume(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
if (!(readl(base + L2X0_CTRL) & L2X0_CTRL_EN)) {
|
|
writel_relaxed(l2x0_saved_regs.aux_ctrl, base + L2X0_AUX_CTRL);
|
|
writel_relaxed(l2x0_saved_regs.ctrl, base + L2X0_CTRL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* For Aurora cache in no outer mode, enable via the CP15 coprocessor
|
|
* broadcasting of cache commands to L2.
|
|
*/
|
|
static void __init aurora_enable_no_outer(void __iomem *base, u32 aux,
|
|
unsigned num_lock)
|
|
{
|
|
u32 u;
|
|
|
|
asm volatile("mrc p15, 1, %0, c15, c2, 0" : "=r" (u));
|
|
u |= AURORA_CTRL_FW; /* Set the FW bit */
|
|
asm volatile("mcr p15, 1, %0, c15, c2, 0" : : "r" (u));
|
|
|
|
isb();
|
|
|
|
l2c_enable(base, aux, num_lock);
|
|
}
|
|
|
|
static void __init aurora_fixup(void __iomem *base, u32 cache_id,
|
|
struct outer_cache_fns *fns)
|
|
{
|
|
sync_reg_offset = AURORA_SYNC_REG;
|
|
}
|
|
|
|
static void __init aurora_of_parse(const struct device_node *np,
|
|
u32 *aux_val, u32 *aux_mask)
|
|
{
|
|
u32 val = AURORA_ACR_REPLACEMENT_TYPE_SEMIPLRU;
|
|
u32 mask = AURORA_ACR_REPLACEMENT_MASK;
|
|
|
|
of_property_read_u32(np, "cache-id-part",
|
|
&cache_id_part_number_from_dt);
|
|
|
|
/* Determine and save the write policy */
|
|
l2_wt_override = of_property_read_bool(np, "wt-override");
|
|
|
|
if (l2_wt_override) {
|
|
val |= AURORA_ACR_FORCE_WRITE_THRO_POLICY;
|
|
mask |= AURORA_ACR_FORCE_WRITE_POLICY_MASK;
|
|
}
|
|
|
|
*aux_val &= ~mask;
|
|
*aux_val |= val;
|
|
*aux_mask &= ~mask;
|
|
}
|
|
|
|
static const struct l2c_init_data of_aurora_with_outer_data __initconst = {
|
|
.type = "Aurora",
|
|
.way_size_0 = SZ_4K,
|
|
.num_lock = 4,
|
|
.of_parse = aurora_of_parse,
|
|
.enable = l2c_enable,
|
|
.fixup = aurora_fixup,
|
|
.save = aurora_save,
|
|
.outer_cache = {
|
|
.inv_range = aurora_inv_range,
|
|
.clean_range = aurora_clean_range,
|
|
.flush_range = aurora_flush_range,
|
|
.flush_all = l2x0_flush_all,
|
|
.disable = l2x0_disable,
|
|
.sync = l2x0_cache_sync,
|
|
.resume = aurora_resume,
|
|
},
|
|
};
|
|
|
|
static const struct l2c_init_data of_aurora_no_outer_data __initconst = {
|
|
.type = "Aurora",
|
|
.way_size_0 = SZ_4K,
|
|
.num_lock = 4,
|
|
.of_parse = aurora_of_parse,
|
|
.enable = aurora_enable_no_outer,
|
|
.fixup = aurora_fixup,
|
|
.save = aurora_save,
|
|
.outer_cache = {
|
|
.resume = aurora_resume,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* For certain Broadcom SoCs, depending on the address range, different offsets
|
|
* need to be added to the address before passing it to L2 for
|
|
* invalidation/clean/flush
|
|
*
|
|
* Section Address Range Offset EMI
|
|
* 1 0x00000000 - 0x3FFFFFFF 0x80000000 VC
|
|
* 2 0x40000000 - 0xBFFFFFFF 0x40000000 SYS
|
|
* 3 0xC0000000 - 0xFFFFFFFF 0x80000000 VC
|
|
*
|
|
* When the start and end addresses have crossed two different sections, we
|
|
* need to break the L2 operation into two, each within its own section.
|
|
* For example, if we need to invalidate addresses starts at 0xBFFF0000 and
|
|
* ends at 0xC0001000, we need do invalidate 1) 0xBFFF0000 - 0xBFFFFFFF and 2)
|
|
* 0xC0000000 - 0xC0001000
|
|
*
|
|
* Note 1:
|
|
* By breaking a single L2 operation into two, we may potentially suffer some
|
|
* performance hit, but keep in mind the cross section case is very rare
|
|
*
|
|
* Note 2:
|
|
* We do not need to handle the case when the start address is in
|
|
* Section 1 and the end address is in Section 3, since it is not a valid use
|
|
* case
|
|
*
|
|
* Note 3:
|
|
* Section 1 in practical terms can no longer be used on rev A2. Because of
|
|
* that the code does not need to handle section 1 at all.
|
|
*
|
|
*/
|
|
#define BCM_SYS_EMI_START_ADDR 0x40000000UL
|
|
#define BCM_VC_EMI_SEC3_START_ADDR 0xC0000000UL
|
|
|
|
#define BCM_SYS_EMI_OFFSET 0x40000000UL
|
|
#define BCM_VC_EMI_OFFSET 0x80000000UL
|
|
|
|
static inline int bcm_addr_is_sys_emi(unsigned long addr)
|
|
{
|
|
return (addr >= BCM_SYS_EMI_START_ADDR) &&
|
|
(addr < BCM_VC_EMI_SEC3_START_ADDR);
|
|
}
|
|
|
|
static inline unsigned long bcm_l2_phys_addr(unsigned long addr)
|
|
{
|
|
if (bcm_addr_is_sys_emi(addr))
|
|
return addr + BCM_SYS_EMI_OFFSET;
|
|
else
|
|
return addr + BCM_VC_EMI_OFFSET;
|
|
}
|
|
|
|
static void bcm_inv_range(unsigned long start, unsigned long end)
|
|
{
|
|
unsigned long new_start, new_end;
|
|
|
|
BUG_ON(start < BCM_SYS_EMI_START_ADDR);
|
|
|
|
if (unlikely(end <= start))
|
|
return;
|
|
|
|
new_start = bcm_l2_phys_addr(start);
|
|
new_end = bcm_l2_phys_addr(end);
|
|
|
|
/* normal case, no cross section between start and end */
|
|
if (likely(bcm_addr_is_sys_emi(end) || !bcm_addr_is_sys_emi(start))) {
|
|
l2c210_inv_range(new_start, new_end);
|
|
return;
|
|
}
|
|
|
|
/* They cross sections, so it can only be a cross from section
|
|
* 2 to section 3
|
|
*/
|
|
l2c210_inv_range(new_start,
|
|
bcm_l2_phys_addr(BCM_VC_EMI_SEC3_START_ADDR-1));
|
|
l2c210_inv_range(bcm_l2_phys_addr(BCM_VC_EMI_SEC3_START_ADDR),
|
|
new_end);
|
|
}
|
|
|
|
static void bcm_clean_range(unsigned long start, unsigned long end)
|
|
{
|
|
unsigned long new_start, new_end;
|
|
|
|
BUG_ON(start < BCM_SYS_EMI_START_ADDR);
|
|
|
|
if (unlikely(end <= start))
|
|
return;
|
|
|
|
new_start = bcm_l2_phys_addr(start);
|
|
new_end = bcm_l2_phys_addr(end);
|
|
|
|
/* normal case, no cross section between start and end */
|
|
if (likely(bcm_addr_is_sys_emi(end) || !bcm_addr_is_sys_emi(start))) {
|
|
l2c210_clean_range(new_start, new_end);
|
|
return;
|
|
}
|
|
|
|
/* They cross sections, so it can only be a cross from section
|
|
* 2 to section 3
|
|
*/
|
|
l2c210_clean_range(new_start,
|
|
bcm_l2_phys_addr(BCM_VC_EMI_SEC3_START_ADDR-1));
|
|
l2c210_clean_range(bcm_l2_phys_addr(BCM_VC_EMI_SEC3_START_ADDR),
|
|
new_end);
|
|
}
|
|
|
|
static void bcm_flush_range(unsigned long start, unsigned long end)
|
|
{
|
|
unsigned long new_start, new_end;
|
|
|
|
BUG_ON(start < BCM_SYS_EMI_START_ADDR);
|
|
|
|
if (unlikely(end <= start))
|
|
return;
|
|
|
|
if ((end - start) >= l2x0_size) {
|
|
outer_cache.flush_all();
|
|
return;
|
|
}
|
|
|
|
new_start = bcm_l2_phys_addr(start);
|
|
new_end = bcm_l2_phys_addr(end);
|
|
|
|
/* normal case, no cross section between start and end */
|
|
if (likely(bcm_addr_is_sys_emi(end) || !bcm_addr_is_sys_emi(start))) {
|
|
l2c210_flush_range(new_start, new_end);
|
|
return;
|
|
}
|
|
|
|
/* They cross sections, so it can only be a cross from section
|
|
* 2 to section 3
|
|
*/
|
|
l2c210_flush_range(new_start,
|
|
bcm_l2_phys_addr(BCM_VC_EMI_SEC3_START_ADDR-1));
|
|
l2c210_flush_range(bcm_l2_phys_addr(BCM_VC_EMI_SEC3_START_ADDR),
|
|
new_end);
|
|
}
|
|
|
|
/* Broadcom L2C-310 start from ARMs R3P2 or later, and require no fixups */
|
|
static const struct l2c_init_data of_bcm_l2x0_data __initconst = {
|
|
.type = "BCM-L2C-310",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 8,
|
|
.of_parse = l2c310_of_parse,
|
|
.enable = l2c310_enable,
|
|
.save = l2c310_save,
|
|
.outer_cache = {
|
|
.inv_range = bcm_inv_range,
|
|
.clean_range = bcm_clean_range,
|
|
.flush_range = bcm_flush_range,
|
|
.flush_all = l2c210_flush_all,
|
|
.disable = l2c310_disable,
|
|
.sync = l2c210_sync,
|
|
.resume = l2c310_resume,
|
|
},
|
|
};
|
|
|
|
static void __init tauros3_save(void __iomem *base)
|
|
{
|
|
l2c_save(base);
|
|
|
|
l2x0_saved_regs.aux2_ctrl =
|
|
readl_relaxed(base + TAUROS3_AUX2_CTRL);
|
|
l2x0_saved_regs.prefetch_ctrl =
|
|
readl_relaxed(base + L310_PREFETCH_CTRL);
|
|
}
|
|
|
|
static void tauros3_resume(void)
|
|
{
|
|
void __iomem *base = l2x0_base;
|
|
|
|
if (!(readl_relaxed(base + L2X0_CTRL) & L2X0_CTRL_EN)) {
|
|
writel_relaxed(l2x0_saved_regs.aux2_ctrl,
|
|
base + TAUROS3_AUX2_CTRL);
|
|
writel_relaxed(l2x0_saved_regs.prefetch_ctrl,
|
|
base + L310_PREFETCH_CTRL);
|
|
|
|
l2c_enable(base, l2x0_saved_regs.aux_ctrl, 8);
|
|
}
|
|
}
|
|
|
|
static const struct l2c_init_data of_tauros3_data __initconst = {
|
|
.type = "Tauros3",
|
|
.way_size_0 = SZ_8K,
|
|
.num_lock = 8,
|
|
.enable = l2c_enable,
|
|
.save = tauros3_save,
|
|
/* Tauros3 broadcasts L1 cache operations to L2 */
|
|
.outer_cache = {
|
|
.resume = tauros3_resume,
|
|
},
|
|
};
|
|
|
|
#define L2C_ID(name, fns) { .compatible = name, .data = (void *)&fns }
|
|
static const struct of_device_id l2x0_ids[] __initconst = {
|
|
L2C_ID("arm,l210-cache", of_l2c210_data),
|
|
L2C_ID("arm,l220-cache", of_l2c220_data),
|
|
L2C_ID("arm,pl310-cache", of_l2c310_data),
|
|
L2C_ID("brcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),
|
|
L2C_ID("marvell,aurora-outer-cache", of_aurora_with_outer_data),
|
|
L2C_ID("marvell,aurora-system-cache", of_aurora_no_outer_data),
|
|
L2C_ID("marvell,tauros3-cache", of_tauros3_data),
|
|
/* Deprecated IDs */
|
|
L2C_ID("bcm,bcm11351-a2-pl310-cache", of_bcm_l2x0_data),
|
|
{}
|
|
};
|
|
|
|
int __init l2x0_of_init(u32 aux_val, u32 aux_mask)
|
|
{
|
|
const struct l2c_init_data *data;
|
|
struct device_node *np;
|
|
struct resource res;
|
|
u32 cache_id, old_aux;
|
|
|
|
np = of_find_matching_node(NULL, l2x0_ids);
|
|
if (!np)
|
|
return -ENODEV;
|
|
|
|
if (of_address_to_resource(np, 0, &res))
|
|
return -ENODEV;
|
|
|
|
l2x0_base = ioremap(res.start, resource_size(&res));
|
|
if (!l2x0_base)
|
|
return -ENOMEM;
|
|
|
|
l2x0_saved_regs.phy_base = res.start;
|
|
|
|
data = of_match_node(l2x0_ids, np)->data;
|
|
|
|
if (of_device_is_compatible(np, "arm,pl310-cache") &&
|
|
of_property_read_bool(np, "arm,io-coherent"))
|
|
data = &of_l2c310_coherent_data;
|
|
|
|
old_aux = readl_relaxed(l2x0_base + L2X0_AUX_CTRL);
|
|
if (old_aux != ((old_aux & aux_mask) | aux_val)) {
|
|
pr_warn("L2C: platform modifies aux control register: 0x%08x -> 0x%08x\n",
|
|
old_aux, (old_aux & aux_mask) | aux_val);
|
|
} else if (aux_mask != ~0U && aux_val != 0) {
|
|
pr_alert("L2C: platform provided aux values match the hardware, so have no effect. Please remove them.\n");
|
|
}
|
|
|
|
/* All L2 caches are unified, so this property should be specified */
|
|
if (!of_property_read_bool(np, "cache-unified"))
|
|
pr_err("L2C: device tree omits to specify unified cache\n");
|
|
|
|
/* L2 configuration can only be changed if the cache is disabled */
|
|
if (!(readl_relaxed(l2x0_base + L2X0_CTRL) & L2X0_CTRL_EN))
|
|
if (data->of_parse)
|
|
data->of_parse(np, &aux_val, &aux_mask);
|
|
|
|
if (cache_id_part_number_from_dt)
|
|
cache_id = cache_id_part_number_from_dt;
|
|
else
|
|
cache_id = readl_relaxed(l2x0_base + L2X0_CACHE_ID);
|
|
|
|
__l2c_init(data, aux_val, aux_mask, cache_id);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|