forked from Mirrors/opensbi

To make the framework suit all Andes CPUs, change all occurrences of andes45 to andes. In addition, we fix some coding style problems and remove an unused macro in andes.h. Signed-off-by: Ben Zong-You Xie <ben717@andestech.com> Reviewed-by: Anup Patel <anup@brainfault.org>
343 lines
8.5 KiB
C
343 lines
8.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2023 Renesas Electronics Corp.
|
|
*
|
|
* Copyright (c) 2020 Andes Technology Corporation
|
|
*
|
|
* Authors:
|
|
* Nick Hu <nickhu@andestech.com>
|
|
* Nylon Chen <nylon7@andestech.com>
|
|
* Lad Prabhakar <prabhakar.mahadev-lad.rj@bp.renesas.com>
|
|
*/
|
|
|
|
#include <andes/andes.h>
|
|
#include <andes/andes_pma.h>
|
|
#include <libfdt.h>
|
|
#include <sbi/riscv_asm.h>
|
|
#include <sbi/riscv_io.h>
|
|
#include <sbi/sbi_console.h>
|
|
#include <sbi/sbi_error.h>
|
|
#include <sbi_utils/fdt/fdt_helper.h>
|
|
|
|
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 <reg> 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;
|
|
}
|