| /* | |
| * 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, | |
| }; |