#include <string.h>
#include <soc/bpmp.h>
#include <soc/ccplex.h>
#include <soc/timer.h>
#include <soc/t210.h>
#include <mem/mc_t210.h>
#include <mem/smmu.h>
#include <memory_map.h>
#define SMMU_ASID(asid) (((asid) << 24u) | ((asid) << 16u) | ((asid) << 8u) | (asid))
#define SMMU_ENABLE BIT(31)
#define SMMU_TLB_ACTIVE_LINES(l) ((l) << 0u)
#define SMMU_TLB_RR_ARBITRATION BIT(28)
#define SMMU_TLB_HIT_UNDER_MISS BIT(29)
#define SMMU_TLB_STATS_ENABLE BIT(31)
#define SMUU_PTC_INDEX_MAP(m) ((m) << 0u)
#define SMUU_PTC_LINE_MASK(m) ((m) << 8u)
#define SMUU_PTC_REQ_LIMIT(l) ((l) << 24u)
#define SMUU_PTC_CACHE_ENABLE BIT(29)
#define SMUU_PTC_STATS_ENABLE BIT(31)
#define SMMU_4MB_REGION 0
#define SMMU_PAGE_TABLE 1
#define SMMU_PDIR_COUNT 1024
#define SMMU_PTBL_COUNT 1024
#define SMMU_PAGE_SHIFT 12u
#define SMMU_PTN_SHIFT SMMU_PAGE_SHIFT
#define SMMU_PDN_SHIFT 22u
#define SMMU_ADDR_TO_PFN(addr) ((addr) >> SMMU_PAGE_SHIFT)
#define SMMU_ADDR_TO_PTN(addr) ((addr) >> SMMU_PTN_SHIFT)
#define SMMU_ADDR_TO_PDN(addr) ((addr) >> SMMU_PDN_SHIFT)
#define SMMU_PTN_TO_ADDR(ptn) ((ptn) << SMMU_PTN_SHIFT)
#define SMMU_PDN_TO_ADDR(pdn) ((pdn) << SMMU_PDN_SHIFT)
#define SMMU_PTB(page, attr) (((attr) << 29u) | ((page) >> SMMU_PAGE_SHIFT))
static void *smmu_heap = (void *)SMMU_HEAP_ADDR;
static const u8 smmu_enable_payload[] = {
0xC1, 0x00, 0x00, 0x18,
0x20, 0x00, 0x80, 0xD2,
0x20, 0x00, 0x00, 0xB9,
0x1F, 0x71, 0x08, 0xD5,
0x9F, 0x3B, 0x03, 0xD5,
0xFE, 0xFF, 0xFF, 0x17,
0x10, 0x90, 0x01, 0x70,
};
void *smmu_page_zalloc(u32 num)
{
void *page = smmu_heap;
memset(page, 0, SZ_PAGE * num);
smmu_heap += SZ_PAGE * num;
return page;
}
static pde_t *_smmu_pdir_alloc()
{
pde_t *pdir = (pde_t *)smmu_page_zalloc(1);
for (u32 pdn = 0; pdn < SMMU_PDIR_COUNT; pdn++)
pdir[pdn].huge.page = pdn;
return pdir;
}
static void _smmu_flush_regs()
{
(void)MC(MC_SMMU_PTB_DATA);
}
void smmu_flush_all()
{
MC(MC_SMMU_PTC_FLUSH) = 0;
_smmu_flush_regs();
MC(MC_SMMU_TLB_FLUSH) = 0;
_smmu_flush_regs();
}
void smmu_init()
{
MC(MC_SMMU_PTB_ASID) = 0;
MC(MC_SMMU_PTB_DATA) = 0;
MC(MC_SMMU_TLB_CONFIG) = SMMU_TLB_HIT_UNDER_MISS | SMMU_TLB_RR_ARBITRATION | SMMU_TLB_ACTIVE_LINES(48);
MC(MC_SMMU_PTC_CONFIG) = SMUU_PTC_CACHE_ENABLE | SMUU_PTC_REQ_LIMIT(8) | SMUU_PTC_LINE_MASK(0xF) | SMUU_PTC_INDEX_MAP(0x3F);
MC(MC_SMMU_PTC_FLUSH) = 0;
MC(MC_SMMU_TLB_FLUSH) = 0;
}
void smmu_enable()
{
static bool enabled = false;
if (enabled)
return;
ccplex_boot_cpu0((u32)smmu_enable_payload, false);
msleep(100);
ccplex_powergate_cpu0();
smmu_flush_all();
enabled = true;
}
void smmu_reset_heap()
{
smmu_heap = (void *)SMMU_HEAP_ADDR;
}
void *smmu_init_domain(u32 dev_base, u32 asid)
{
void *ptb = _smmu_pdir_alloc();
MC(MC_SMMU_PTB_ASID) = asid;
MC(MC_SMMU_PTB_DATA) = SMMU_PTB((u32)ptb, SMMU_ATTR_ALL);
_smmu_flush_regs();
MC(dev_base) = SMMU_ENABLE | SMMU_ASID(asid);
_smmu_flush_regs();
return ptb;
}
void smmu_deinit_domain(u32 dev_base, u32 asid)
{
MC(MC_SMMU_PTB_ASID) = asid;
MC(MC_SMMU_PTB_DATA) = 0;
MC(dev_base) = 0;
_smmu_flush_regs();
}
void smmu_domain_bypass(u32 dev_base, bool bypass)
{
if (bypass)
{
smmu_flush_all();
bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false);
MC(dev_base) &= ~SMMU_ENABLE;
}
else
{
bpmp_mmu_maintenance(BPMP_MMU_MAINT_CLN_INV_WAY, false);
MC(dev_base) |= SMMU_ENABLE;
smmu_flush_all();
}
_smmu_flush_regs();
}
static pte_t *_smmu_get_pte(pde_t *pdir, u32 iova)
{
u32 pdn = SMMU_ADDR_TO_PDN(iova);
pte_t *ptbl;
if (pdir[pdn].tbl.attr)
ptbl = (pte_t *)(SMMU_PTN_TO_ADDR(pdir[pdn].tbl.table));
else
{
ptbl = (pte_t *)smmu_page_zalloc(1);
u32 addr = SMMU_PDN_TO_ADDR(pdn);
for (u32 pn = 0; pn < SMMU_PTBL_COUNT; pn++, addr += SZ_PAGE)
ptbl[pn].page = SMMU_ADDR_TO_PFN(addr);
pdir[pdn].tbl.table = SMMU_ADDR_TO_PTN((u32)ptbl);
pdir[pdn].tbl.next = SMMU_PAGE_TABLE;
pdir[pdn].tbl.attr = SMMU_ATTR_ALL;
smmu_flush_all();
}
return &ptbl[SMMU_ADDR_TO_PTN(iova) % SMMU_PTBL_COUNT];
}
void smmu_map(void *ptb, u32 iova, u64 iopa, u32 pages, u32 attr)
{
for (u32 i = 0; i < pages; i++)
{
pte_t *pte = _smmu_get_pte((pde_t *)ptb, iova);
pte->page = SMMU_ADDR_TO_PFN(iopa);
pte->attr = attr;
iova += SZ_PAGE;
iopa += SZ_PAGE;
}
smmu_flush_all();
}
void smmu_map_huge(void *ptb, u32 iova, u64 iopa, u32 regions, u32 attr)
{
pde_t *pdir = (pde_t *)ptb;
for (u32 i = 0; i < regions; i++)
{
u32 pdn = SMMU_ADDR_TO_PDN(iova);
pdir[pdn].huge.page = SMMU_ADDR_TO_PDN(iopa);
pdir[pdn].huge.next = SMMU_4MB_REGION;
pdir[pdn].huge.attr = attr;
iova += SZ_4M;
iopa += SZ_4M;
}
smmu_flush_all();
}