/* * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2025 Andes Technology Corporation */ #include #include #include #include #include #include #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, };