mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-21 19:53:59 +08:00
af040ffc9b
Ensure that platform maintainers check the CPU part number in the right manner: the CPU part number is meaningless without also checking the CPU implement(e|o)r (choose your preferred spelling!) Provide an interface which returns both the implementer and part number together, and update the definitions to include the implementer. Mark the old function as being deprecated... indeed, using the old function with the definitions will now always evaluate as false, so people must update their un-merged code to the new function. While this could be avoided by adding new definitions, we'd also have to create new names for them which would be awkward. Acked-by: Nicolas Pitre <nico@linaro.org> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
1548 lines
42 KiB
C
1548 lines
42 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/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_PART_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;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
*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 };
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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
|