blob: 490503ee611c30a685fa98b01d14284644c4a70a [file] [log] [blame]
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Andes Technology Corporation
*/
#include <sbi/riscv_io.h>
#include <sbi/sbi_error.h>
#include <sbi/sbi_heap.h>
#include <sbi_utils/cache/fdt_cache.h>
#include <sbi_utils/fdt/fdt_driver.h>
#include <sbi_utils/hsm/fdt_hsm_andes_atcsmu.h>
#define LLCACHE_REG_CFG_OFFSET 0x0
#define LLCACHE_REG_CTRL_OFFSET 0x8
#define LLCACHE_REG_CCTL_CMD_OFFSET 0x40
#define LLCACHE_REG_CCTL_STATUS_OFFSET 0x80
#define LLCACHE_REG_CFG_MAP_MASK BIT(20)
#define LLCACHE_REG_CTRL_EN_MASK BIT(0)
#define LLCACHE_REG_CTRL_INIT_MASK BIT(14)
#define LLCACHE_REG_CCTL_STATUS_MASK GENMASK(3, 0)
#define LLCACHE_WBINVAL_ALL 0x12
struct andes_llcache {
struct cache_device dev;
void *base;
uint32_t cmd_stride;
uint32_t status_stride;
uint32_t status_core_stride;
};
#define to_llcache(_dev) container_of(_dev, struct andes_llcache, dev)
static bool andes_llcache_init_done(struct andes_llcache *llcache)
{
uint32_t llcache_ctrl;
void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
llcache_ctrl = readl_relaxed(ctrl_addr);
return !EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_INIT_MASK);
}
static bool andes_llcache_cctl_done(struct andes_llcache *llcache, uint32_t hartid)
{
uint32_t llcache_cctl_status;
void *cctl_status_addr = (char *)llcache->base + LLCACHE_REG_CCTL_STATUS_OFFSET +
hartid * llcache->status_stride;
llcache_cctl_status = readl_relaxed(cctl_status_addr);
return !EXTRACT_FIELD(llcache_cctl_status,
LLCACHE_REG_CCTL_STATUS_MASK <<
hartid * llcache->status_core_stride);
}
static int andes_llcache_flush_all(struct cache_device *dev)
{
uint32_t hartid = current_hartid();
struct andes_llcache *llcache = to_llcache(dev);
void *cctl_cmd_addr = (char *)llcache->base + LLCACHE_REG_CCTL_CMD_OFFSET +
hartid * llcache->cmd_stride;
/*
* Each command register corresponds to one CPU core, so each CPU core
* should only use its command registers to do the cache operation.
*/
writel(LLCACHE_WBINVAL_ALL, cctl_cmd_addr);
/* Wait for the command completion */
while (!andes_llcache_cctl_done(llcache, hartid))
;
return 0;
}
static int andes_llcache_enable(struct cache_device *dev, bool enable)
{
struct andes_llcache *llcache = to_llcache(dev);
u32 llcache_ctrl;
void *ctrl_addr = (char *)llcache->base + LLCACHE_REG_CTRL_OFFSET;
/*
* To properly enable the last level cache to cache both instructions
* and data, apply the following sequence:
*
* - Write the control register with the desired value, except the
* CEN field should be set to zero. Thus, store the control register
* value with the CEN field being 0 when disabling the last level
* cache.
* - Write the control register again using the same value of step 1
* with the CEN field being 1.
*/
if (enable) {
llcache_ctrl = atcsmu_read_scratch();
writel(llcache_ctrl, ctrl_addr);
writel(llcache_ctrl | LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
} else {
llcache_ctrl = readl(ctrl_addr);
atcsmu_write_scratch(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK);
writel(llcache_ctrl & ~LLCACHE_REG_CTRL_EN_MASK, ctrl_addr);
}
llcache_ctrl = readl(ctrl_addr);
return enable == EXTRACT_FIELD(llcache_ctrl, LLCACHE_REG_CTRL_EN_MASK);
}
static struct cache_ops andes_llcache_ops = {
.cache_flush_all = andes_llcache_flush_all,
.cache_enable = andes_llcache_enable,
};
static int andes_llcache_probe(const void *fdt, int nodeoff, const struct fdt_match *match)
{
int rc;
u64 llcache_base = 0;
struct andes_llcache *llcache;
struct cache_device *dev;
uint32_t llcache_cfg;
rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &llcache_base, NULL);
if (rc < 0 || !llcache_base)
return SBI_ENODEV;
llcache = sbi_zalloc(sizeof(*llcache));
if (!llcache)
return SBI_ENOMEM;
dev = &llcache->dev;
dev->ops = &andes_llcache_ops;
rc = fdt_cache_add(fdt, nodeoff, dev);
if (rc) {
sbi_free(llcache);
return rc;
}
llcache->base = (void *)(ulong)llcache_base;
llcache_cfg = readl_relaxed((char *)llcache->base + LLCACHE_REG_CFG_OFFSET);
/* Configurations for V1/V0 memory map */
if (EXTRACT_FIELD(llcache_cfg, LLCACHE_REG_CFG_MAP_MASK)) {
llcache->cmd_stride = 0x1000;
llcache->status_stride = 0x1000;
llcache->status_core_stride = 0;
} else {
llcache->cmd_stride = 0x10;
llcache->status_stride = 0x0;
llcache->status_core_stride = 4;
}
/* Wait for the hardware initialization done */
while (!andes_llcache_init_done(llcache))
;
return SBI_OK;
}
static const struct fdt_match andes_llcache_match[] = {
{ .compatible = "andestech,llcache" },
{},
};
const struct fdt_driver fdt_andes_llcache = {
.match_table = andes_llcache_match,
.init = andes_llcache_probe,
};