// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2023 Renesas Electronics Corp. * * Copyright (c) 2020 Andes Technology Corporation * * Authors: * Nick Hu * Nylon Chen * Lad Prabhakar */ #include #include #include #include #include #include #include #include static inline unsigned long andes_pma_read_cfg(unsigned int pma_cfg_off) { #define switchcase_pma_cfg_read(__pma_cfg_off, __val) \ case __pma_cfg_off: \ __val = csr_read(__pma_cfg_off); \ break; #define switchcase_pma_cfg_read_2(__pma_cfg_off, __val) \ switchcase_pma_cfg_read(__pma_cfg_off + 0, __val) \ switchcase_pma_cfg_read(__pma_cfg_off + 2, __val) unsigned long ret = 0; switch (pma_cfg_off) { switchcase_pma_cfg_read_2(CSR_PMACFG0, ret) default: sbi_panic("%s: Unknown PMA CFG offset %#x", __func__, pma_cfg_off); break; } return ret; #undef switchcase_pma_cfg_read_2 #undef switchcase_pma_cfg_read } static inline void andes_pma_write_cfg(unsigned int pma_cfg_off, unsigned long val) { #define switchcase_pma_cfg_write(__pma_cfg_off, __val) \ case __pma_cfg_off: \ csr_write(__pma_cfg_off, __val); \ break; #define switchcase_pma_cfg_write_2(__pma_cfg_off, __val) \ switchcase_pma_cfg_write(__pma_cfg_off + 0, __val) \ switchcase_pma_cfg_write(__pma_cfg_off + 2, __val) switch (pma_cfg_off) { switchcase_pma_cfg_write_2(CSR_PMACFG0, val) default: sbi_panic("%s: Unknown PMA CFG offset %#x", __func__, pma_cfg_off); break; } #undef switchcase_pma_cfg_write_2 #undef switchcase_pma_cfg_write } static inline void andes_pma_write_addr(unsigned int pma_addr_off, unsigned long val) { #define switchcase_pma_write(__pma_addr_off, __val) \ case __pma_addr_off: \ csr_write(__pma_addr_off, __val); \ break; #define switchcase_pma_write_2(__pma_addr_off, __val) \ switchcase_pma_write(__pma_addr_off + 0, __val) \ switchcase_pma_write(__pma_addr_off + 1, __val) #define switchcase_pma_write_4(__pma_addr_off, __val) \ switchcase_pma_write_2(__pma_addr_off + 0, __val) \ switchcase_pma_write_2(__pma_addr_off + 2, __val) #define switchcase_pma_write_8(__pma_addr_off, __val) \ switchcase_pma_write_4(__pma_addr_off + 0, __val) \ switchcase_pma_write_4(__pma_addr_off + 4, __val) #define switchcase_pma_write_16(__pma_addr_off, __val) \ switchcase_pma_write_8(__pma_addr_off + 0, __val) \ switchcase_pma_write_8(__pma_addr_off + 8, __val) switch (pma_addr_off) { switchcase_pma_write_16(CSR_PMAADDR0, val) default: sbi_panic("%s: Unknown PMA ADDR offset %#x", __func__, pma_addr_off); break; } #undef switchcase_pma_write_16 #undef switchcase_pma_write_8 #undef switchcase_pma_write_4 #undef switchcase_pma_write_2 #undef switchcase_pma_write } static inline unsigned long andes_pma_read_addr(unsigned int pma_addr_off) { #define switchcase_pma_read(__pma_addr_off, __val) \ case __pma_addr_off: \ __val = csr_read(__pma_addr_off); \ break; #define switchcase_pma_read_2(__pma_addr_off, __val) \ switchcase_pma_read(__pma_addr_off + 0, __val) \ switchcase_pma_read(__pma_addr_off + 1, __val) #define switchcase_pma_read_4(__pma_addr_off, __val) \ switchcase_pma_read_2(__pma_addr_off + 0, __val) \ switchcase_pma_read_2(__pma_addr_off + 2, __val) #define switchcase_pma_read_8(__pma_addr_off, __val) \ switchcase_pma_read_4(__pma_addr_off + 0, __val) \ switchcase_pma_read_4(__pma_addr_off + 4, __val) #define switchcase_pma_read_16(__pma_addr_off, __val) \ switchcase_pma_read_8(__pma_addr_off + 0, __val) \ switchcase_pma_read_8(__pma_addr_off + 8, __val) unsigned long ret = 0; switch (pma_addr_off) { switchcase_pma_read_16(CSR_PMAADDR0, ret) default: sbi_panic("%s: Unknown PMA ADDR offset %#x", __func__, pma_addr_off); break; } return ret; #undef switchcase_pma_read_16 #undef switchcase_pma_read_8 #undef switchcase_pma_read_4 #undef switchcase_pma_read_2 #undef switchcase_pma_read } 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 4KiB granularity */ if (size < (1 << 12)) return SBI_EINVAL; /* Check size is power of 2 */ if (size & (size - 1)) return SBI_EINVAL; if (entry_id > 15) return SBI_EINVAL; if (!(pma_region->flags & ANDES_PMACFG_ETYP_NAPOT)) return SBI_EINVAL; if ((addr & (size - 1)) != 0) return SBI_EINVAL; pma_cfg_addr = entry_id / 8 ? CSR_PMACFG0 + 2 : CSR_PMACFG0; pmacfg_val = andes_pma_read_cfg(pma_cfg_addr); pmaxcfg = (char *)&pmacfg_val + (entry_id % 8); *pmaxcfg = 0; *pmaxcfg = pma_region->flags; andes_pma_write_cfg(pma_cfg_addr, pmacfg_val); pmaaddr = (addr >> 2) + (size >> 3) - 1; andes_pma_write_addr(CSR_PMAADDR0 + entry_id, pmaaddr); return andes_pma_read_addr(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 */ for (i = 0; i < pma_regions_count; i++) { pa = andes_pma_setup(&pma_regions[i], i); if (pa == SBI_EINVAL) return SBI_EINVAL; } dt_populate_cnt = 0; for (i = 0; i < pma_regions_count; i++) { if (!pma_regions[i].dt_populate) continue; 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; }