// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2023 Renesas Electronics Corp. * Copyright (c) 2024 Andes Technology Corporation * * Authors: * Ben Zong-You Xie * Lad Prabhakar */ #include #include #include #include #include #include #include #include static unsigned long andes_pma_read_num(unsigned int csr_num) { #define switchcase_csr_read(__csr_num, __val) \ case __csr_num: \ __val = csr_read(__csr_num); \ break; #define switchcase_csr_read_2(__csr_num, __val) \ switchcase_csr_read(__csr_num + 0, __val) \ switchcase_csr_read(__csr_num + 1, __val) #define switchcase_csr_read_4(__csr_num, __val) \ switchcase_csr_read_2(__csr_num + 0, __val) \ switchcase_csr_read_2(__csr_num + 2, __val) #define switchcase_csr_read_8(__csr_num, __val) \ switchcase_csr_read_4(__csr_num + 0, __val) \ switchcase_csr_read_4(__csr_num + 4, __val) #define switchcase_csr_read_16(__csr_num, __val) \ switchcase_csr_read_8(__csr_num + 0, __val) \ switchcase_csr_read_8(__csr_num + 8, __val) unsigned long ret = 0; switch (csr_num) { switchcase_csr_read_4(CSR_PMACFG0, ret) switchcase_csr_read_16(CSR_PMAADDR0, ret) default: sbi_panic("%s: Unknown Andes PMA CSR %#x", __func__, csr_num); break; } return ret; #undef switchcase_csr_read_16 #undef switchcase_csr_read_8 #undef switchcase_csr_read_4 #undef switchcase_csr_read_2 #undef switchcase_csr_read } static void andes_pma_write_num(unsigned int csr_num, unsigned long val) { #define switchcase_csr_write(__csr_num, __val) \ case __csr_num: \ csr_write(__csr_num, __val); \ break; #define switchcase_csr_write_2(__csr_num, __val) \ switchcase_csr_write(__csr_num + 0, __val) \ switchcase_csr_write(__csr_num + 1, __val) #define switchcase_csr_write_4(__csr_num, __val) \ switchcase_csr_write_2(__csr_num + 0, __val) \ switchcase_csr_write_2(__csr_num + 2, __val) #define switchcase_csr_write_8(__csr_num, __val) \ switchcase_csr_write_4(__csr_num + 0, __val) \ switchcase_csr_write_4(__csr_num + 4, __val) #define switchcase_csr_write_16(__csr_num, __val) \ switchcase_csr_write_8(__csr_num + 0, __val) \ switchcase_csr_write_8(__csr_num + 8, __val) switch (csr_num) { switchcase_csr_write_4(CSR_PMACFG0, val) switchcase_csr_write_16(CSR_PMAADDR0, val) default: sbi_panic("%s: Unknown Andes PMA CSR %#x", __func__, csr_num); break; } #undef switchcase_csr_write_16 #undef switchcase_csr_write_8 #undef switchcase_csr_write_4 #undef switchcase_csr_write_2 #undef switchcase_csr_write } static inline bool not_napot(unsigned long addr, unsigned long size) { return ((size & (size - 1)) || (addr & (size - 1))); } static unsigned long andes_pma_setup(const struct andes_pma_region *pma_region, unsigned int entry_id) { unsigned long size = pma_region->size; unsigned long addr = pma_region->pa; unsigned int pma_cfg_addr; unsigned long pmacfg_val; unsigned long pmaaddr; char *pmaxcfg; /* Check for a 4KiB granularity NAPOT region*/ if (size < ANDES_PMA_GRANULARITY || not_napot(addr, size) || !(pma_region->flags & ANDES_PMACFG_ETYP_NAPOT)) return SBI_EINVAL; #if __riscv_xlen == 64 pma_cfg_addr = CSR_PMACFG0 + ((entry_id / 8) ? 2 : 0); pmacfg_val = andes_pma_read_num(pma_cfg_addr); pmaxcfg = (char *)&pmacfg_val + (entry_id % 8); #elif __riscv_xlen == 32 pma_cfg_addr = CSR_PMACFG0 + (entry_id / 4); pmacfg_val = andes_pma_read_num(pma_cfg_addr); pmaxcfg = (char *)&pmacfg_val + (entry_id % 4); #else #error "Unexpected __riscv_xlen" #endif *pmaxcfg = pma_region->flags; andes_pma_write_num(pma_cfg_addr, pmacfg_val); pmaaddr = (addr >> 2) + (size >> 3) - 1; andes_pma_write_num(CSR_PMAADDR0 + entry_id, pmaaddr); return andes_pma_read_num(CSR_PMAADDR0 + entry_id) == pmaaddr ? pmaaddr : SBI_EINVAL; } static int andes_fdt_pma_resv(void *fdt, const struct andes_pma_region *pma, unsigned int index, int parent) { int na = fdt_address_cells(fdt, 0); int ns = fdt_size_cells(fdt, 0); static bool dma_default = false; fdt32_t addr_high, addr_low; fdt32_t size_high, size_low; int subnode, err; fdt32_t reg[4]; fdt32_t *val; char name[32]; addr_high = (u64)pma->pa >> 32; addr_low = pma->pa; size_high = (u64)pma->size >> 32; size_low = pma->size; if (na > 1 && addr_high) { sbi_snprintf(name, sizeof(name), "pma_resv%d@%x,%x", index, addr_high, addr_low); } else { sbi_snprintf(name, sizeof(name), "pma_resv%d@%x", index, addr_low); } subnode = fdt_add_subnode(fdt, parent, name); if (subnode < 0) return subnode; if (pma->shared_dma) { err = fdt_setprop_string(fdt, subnode, "compatible", "shared-dma-pool"); if (err < 0) return err; } if (pma->no_map) { err = fdt_setprop_empty(fdt, subnode, "no-map"); if (err < 0) return err; } /* Linux allows single linux,dma-default region. */ if (pma->dma_default) { if (dma_default) return SBI_EINVAL; err = fdt_setprop_empty(fdt, subnode, "linux,dma-default"); if (err < 0) return err; dma_default = true; } /* Encode the property value */ val = reg; if (na > 1) *val++ = cpu_to_fdt32(addr_high); *val++ = cpu_to_fdt32(addr_low); if (ns > 1) *val++ = cpu_to_fdt32(size_high); *val++ = cpu_to_fdt32(size_low); err = fdt_setprop(fdt, subnode, "reg", reg, (na + ns) * sizeof(fdt32_t)); if (err < 0) return err; return 0; } static int andes_fdt_reserved_memory_fixup(void *fdt, const struct andes_pma_region *pma, unsigned int entry) { int parent; /* Try to locate the reserved memory node */ parent = fdt_path_offset(fdt, "/reserved-memory"); if (parent < 0) { int na = fdt_address_cells(fdt, 0); int ns = fdt_size_cells(fdt, 0); int err; /* If such node does not exist, create one */ parent = fdt_add_subnode(fdt, 0, "reserved-memory"); if (parent < 0) return parent; err = fdt_setprop_empty(fdt, parent, "ranges"); if (err < 0) return err; err = fdt_setprop_u32(fdt, parent, "#size-cells", ns); if (err < 0) return err; err = fdt_setprop_u32(fdt, parent, "#address-cells", na); if (err < 0) return err; } return andes_fdt_pma_resv(fdt, pma, entry, parent); } int andes_pma_setup_regions(const struct andes_pma_region *pma_regions, unsigned int pma_regions_count) { unsigned long mmsc = csr_read(CSR_MMSC_CFG); unsigned int dt_populate_cnt; unsigned int i, j; unsigned long pa; void *fdt; int ret; if (!pma_regions || !pma_regions_count) return 0; if (pma_regions_count > ANDES_MAX_PMA_REGIONS) return SBI_EINVAL; if ((mmsc & MMSC_CFG_PPMA_MASK) == 0) return SBI_ENOTSUPP; /* Configure the PMA regions */ dt_populate_cnt = 0; for (i = 0; i < pma_regions_count; i++) { pa = andes_pma_setup(&pma_regions[i], i); if (pa == SBI_EINVAL) return SBI_EINVAL; else if (pma_regions[i].dt_populate) dt_populate_cnt++; } if (!dt_populate_cnt) return 0; fdt = fdt_get_address(); ret = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + (64 * dt_populate_cnt)); if (ret < 0) return ret; for (i = 0, j = 0; i < pma_regions_count; i++) { if (!pma_regions[i].dt_populate) continue; ret = andes_fdt_reserved_memory_fixup(fdt, &pma_regions[i], j++); if (ret) return ret; } return 0; }