| /* linux/drivers/iommu/exynos_iommu-reg.h |
| * |
| * Copyright (c) 2017 Samsung Electronics Co., Ltd. |
| * http://www.samsung.com |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| #include "exynos-iommu.h" |
| #include <dt-bindings/sysmmu/sysmmu.h> |
| #ifdef CONFIG_SEC_DEBUG_AUTO_COMMENT |
| #include <linux/sec_debug.h> |
| #endif |
| |
| #define is_secure_info_fail(x) ((((x) >> 16) & 0xffff) == 0xdead) |
| static inline u32 __secure_info_read(unsigned int addr) |
| { |
| u32 ret; |
| |
| ret = exynos_smc(SMC_DRM_SEC_SMMU_INFO, (unsigned long)addr, |
| 0, SEC_SMMU_SFR_READ); |
| if (is_secure_info_fail(ret)) |
| pr_err("Invalid value returned, %#x\n", ret); |
| |
| return ret; |
| } |
| |
| static inline void __sysmmu_tlb_invalidate_all(void __iomem *sfrbase) |
| { |
| writel(0x1, sfrbase + REG_MMU_FLUSH); |
| } |
| |
| static inline void __sysmmu_tlb_invalidate(struct sysmmu_drvdata *drvdata, |
| dma_addr_t iova, size_t size) |
| { |
| void * __iomem sfrbase = drvdata->sfrbase; |
| |
| __raw_writel(iova, sfrbase + REG_FLUSH_RANGE_START); |
| __raw_writel(size - 1 + iova, sfrbase + REG_FLUSH_RANGE_END); |
| writel(0x1, sfrbase + REG_MMU_FLUSH_RANGE); |
| SYSMMU_EVENT_LOG_TLB_INV_RANGE(SYSMMU_DRVDATA_TO_LOG(drvdata), |
| iova, iova + size); |
| } |
| |
| static inline void __sysmmu_set_ptbase(struct sysmmu_drvdata *drvdata, |
| phys_addr_t pfn_pgtable) |
| { |
| void * __iomem sfrbase = drvdata->sfrbase; |
| |
| writel_relaxed(pfn_pgtable, sfrbase + REG_PT_BASE_PPN); |
| |
| __sysmmu_tlb_invalidate_all(sfrbase); |
| SYSMMU_EVENT_LOG_TLB_INV_ALL( |
| SYSMMU_DRVDATA_TO_LOG(drvdata)); |
| } |
| |
| static inline unsigned int dump_tlb_entry_way_type(void __iomem *sfrbase, |
| int idx_way, int idx_set) |
| { |
| if (MMU_TLB_ENTRY_VALID(__raw_readl(sfrbase + REG_TLB_ATTR))) { |
| pr_auto(ASL4, "[%02d][%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x\n", |
| idx_way, idx_set, |
| __raw_readl(sfrbase + REG_TLB_VPN), |
| __raw_readl(sfrbase + REG_TLB_PPN), |
| __raw_readl(sfrbase + REG_TLB_ATTR)); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static inline void sysmmu_tlb_compare(phys_addr_t pgtable, |
| int idx_sub, u32 vpn, u32 ppn) |
| { |
| sysmmu_pte_t *entry; |
| unsigned long vaddr = MMU_VADDR_FROM_TLB((unsigned long)vpn, idx_sub); |
| unsigned long paddr = MMU_PADDR_FROM_TLB((unsigned long)ppn); |
| unsigned long phys = 0; |
| |
| if (!pgtable) |
| return; |
| |
| entry = section_entry(phys_to_virt(pgtable), vaddr); |
| |
| if (lv1ent_section(entry)) { |
| phys = section_phys(entry); |
| } else if (lv1ent_page(entry)) { |
| entry = page_entry(entry, vaddr); |
| |
| if (lv2ent_large(entry)) |
| phys = lpage_phys(entry); |
| else if (lv2ent_small(entry)) |
| phys = spage_phys(entry); |
| } else { |
| pr_auto(ASL4, ">> Invalid address detected! entry: %#lx", |
| (unsigned long)*entry); |
| return; |
| } |
| |
| if (paddr != phys) { |
| pr_auto(ASL4, ">> TLB mismatch detected!\n"); |
| pr_auto(ASL4, " TLB: %#010lx, PT entry: %#010lx\n", paddr, phys); |
| } |
| } |
| |
| static inline unsigned int dump_tlb_entry_port_type(void __iomem *sfrbase, |
| phys_addr_t pgtable, int idx_way, int idx_set, int idx_sub) |
| { |
| if (MMU_TLB_ENTRY_VALID(__raw_readl(sfrbase + REG_CAPA1_TLB_ATTR))) { |
| u32 vpn, ppn, attr; |
| |
| vpn = __raw_readl(sfrbase + REG_CAPA1_TLB_VPN); |
| ppn = __raw_readl(sfrbase + REG_CAPA1_TLB_PPN); |
| attr = __raw_readl(sfrbase + REG_CAPA1_TLB_ATTR); |
| |
| pr_auto(ASL4, "[%02d][%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x\n", |
| idx_way, idx_set, vpn, ppn, attr); |
| sysmmu_tlb_compare(pgtable, idx_sub, vpn, ppn); |
| return 1; |
| } |
| return 0; |
| } |
| |
| #define MMU_NUM_TLB_SUBLINE 4 |
| static inline void dump_sysmmu_tlb_way(struct sysmmu_drvdata *drvdata) |
| { |
| int i, j, k; |
| u32 capa; |
| unsigned int cnt; |
| void __iomem *sfrbase = drvdata->sfrbase; |
| |
| capa = __raw_readl(sfrbase + REG_MMU_CAPA0_V7); |
| |
| pr_auto(ASL4, "TLB has %d way, %d set. SBB has %d entries.\n", |
| MMU_CAPA_NUM_TLB_WAY(capa), |
| (1 << MMU_CAPA_NUM_TLB_SET(capa)), |
| (1 << MMU_CAPA_NUM_SBB_ENTRY(capa))); |
| pr_auto(ASL4, "------------- TLB[WAY][SET][ENTRY] -------------\n"); |
| for (i = 0, cnt = 0; i < MMU_CAPA_NUM_TLB_WAY(capa); i++) { |
| for (j = 0; j < (1 << MMU_CAPA_NUM_TLB_SET(capa)); j++) { |
| for (k = 0; k < MMU_NUM_TLB_SUBLINE; k++) { |
| __raw_writel(MMU_SET_TLB_READ_ENTRY(j, i, k), |
| sfrbase + REG_TLB_READ); |
| cnt += dump_tlb_entry_way_type(sfrbase, i, j); |
| } |
| } |
| } |
| if (!cnt) |
| pr_auto(ASL4, ">> No Valid TLB Entries\n"); |
| |
| pr_auto(ASL4, "--- SBB(Second-Level Page Table Base Address Buffer ---\n"); |
| for (i = 0, cnt = 0; i < (1 << MMU_CAPA_NUM_SBB_ENTRY(capa)); i++) { |
| __raw_writel(i, sfrbase + REG_SBB_READ); |
| if (MMU_SBB_ENTRY_VALID(__raw_readl(sfrbase + REG_SBB_VPN))) { |
| pr_auto(ASL4, "[%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x\n", |
| i, __raw_readl(sfrbase + REG_SBB_VPN), |
| __raw_readl(sfrbase + REG_SBB_LINK), |
| __raw_readl(sfrbase + REG_SBB_ATTR)); |
| cnt++; |
| } |
| } |
| if (!cnt) |
| pr_auto(ASL4, ">> No Valid SBB Entries\n"); |
| } |
| |
| static inline void sysmmu_sbb_compare(u32 sbb_vpn, u32 sbb_link, |
| phys_addr_t pgtable) |
| { |
| sysmmu_pte_t *entry; |
| unsigned long vaddr = MMU_VADDR_FROM_SBB((unsigned long)sbb_vpn); |
| unsigned long paddr = MMU_PADDR_FROM_SBB((unsigned long)sbb_link); |
| unsigned long phys = 0; |
| |
| if (!pgtable) |
| return; |
| |
| entry = section_entry(phys_to_virt(pgtable), vaddr); |
| |
| if (lv1ent_page(entry)) { |
| phys = lv2table_base(entry); |
| |
| if (paddr != phys) { |
| pr_auto(ASL4, ">> SBB mismatch detected!\n"); |
| pr_auto(ASL4, " entry addr: %lx / SBB addr %lx\n", |
| paddr, phys); |
| } |
| } else { |
| pr_auto(ASL4, ">> Invalid address detected! entry: %#lx", |
| (unsigned long)*entry); |
| } |
| } |
| |
| static inline void dump_sysmmu_tlb_port(struct sysmmu_drvdata *drvdata, |
| phys_addr_t pgtable) |
| { |
| int t, i, j, k; |
| u32 capa0, capa1, info; |
| u32 sbb_vpn, sbb_link; |
| unsigned int cnt; |
| int num_tlb, num_port, num_sbb; |
| void __iomem *sfrbase = drvdata->sfrbase; |
| |
| capa0 = __raw_readl(sfrbase + REG_MMU_CAPA0_V7); |
| capa1 = __raw_readl(sfrbase + REG_MMU_CAPA1_V7); |
| |
| num_tlb = MMU_CAPA1_NUM_TLB(capa1); |
| num_port = MMU_CAPA1_NUM_PORT(capa1); |
| num_sbb = 1 << MMU_CAPA_NUM_SBB_ENTRY(capa0); |
| |
| pr_crit("SysMMU has %d TLBs, %d ports, %d sbb entries\n", |
| num_tlb, num_port, num_sbb); |
| |
| for (t = 0; t < num_tlb; t++) { |
| int num_set, num_way; |
| |
| info = __raw_readl(sfrbase + MMU_TLB_INFO(t)); |
| num_way = MMU_CAPA1_NUM_TLB_WAY(info); |
| num_set = MMU_CAPA1_NUM_TLB_SET(info); |
| |
| pr_crit("TLB.%d has %d way, %d set.\n", t, num_way, num_set); |
| pr_crit("------------- TLB[WAY][SET][ENTRY] -------------\n"); |
| for (i = 0, cnt = 0; i < num_way; i++) { |
| for (j = 0; j < num_set; j++) { |
| for (k = 0; k < MMU_NUM_TLB_SUBLINE; k++) { |
| __raw_writel(MMU_CAPA1_SET_TLB_READ_ENTRY(t, j, i, k), |
| sfrbase + REG_CAPA1_TLB_READ); |
| cnt += dump_tlb_entry_port_type( |
| sfrbase, pgtable, i, j, k); |
| } |
| } |
| } |
| } |
| if (!cnt) |
| pr_auto(ASL4, ">> No Valid TLB Entries\n"); |
| |
| pr_crit("--- SBB(Second-Level Page Table Base Address Buffer) ---\n"); |
| for (i = 0, cnt = 0; i < num_sbb; i++) { |
| __raw_writel(i, sfrbase + REG_CAPA1_SBB_READ); |
| if (MMU_SBB_ENTRY_VALID(__raw_readl(sfrbase + REG_CAPA1_SBB_VPN))) { |
| sbb_vpn = __raw_readl(sfrbase + REG_CAPA1_SBB_VPN); |
| sbb_link = __raw_readl(sfrbase + REG_CAPA1_SBB_LINK); |
| |
| pr_crit("[%02d] VPN: %#010x, PPN: %#010x, ATTR: %#010x\n", |
| i, sbb_vpn, sbb_link, |
| __raw_readl(sfrbase + REG_CAPA1_SBB_ATTR)); |
| sysmmu_sbb_compare(sbb_vpn, sbb_link, pgtable); |
| cnt++; |
| } |
| } |
| if (!cnt) |
| pr_auto(ASL4, ">> No Valid SBB Entries\n"); |
| } |
| |
| static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = { |
| "PTW ACCESS FAULT", |
| "PAGE FAULT", |
| "L1TLB MULTI-HIT FAULT", |
| "ACCESS FAULT", |
| "SECURITY FAULT", |
| "UNKNOWN FAULT" |
| }; |
| |
| static inline void dump_sysmmu_status(struct sysmmu_drvdata *drvdata, |
| phys_addr_t pgtable) |
| { |
| int info; |
| pgd_t *pgd; |
| pud_t *pud; |
| pmd_t *pmd; |
| pte_t *pte; |
| phys_addr_t phys; |
| void __iomem *sfrbase = drvdata->sfrbase; |
| |
| pgd = pgd_offset_k((unsigned long)sfrbase); |
| if (!pgd) { |
| pr_auto(ASL4, "Invalid virtual address %p\n", sfrbase); |
| return; |
| } |
| |
| pud = pud_offset(pgd, (unsigned long)sfrbase); |
| if (!pud) { |
| pr_auto(ASL4, "Invalid virtual address %p\n", sfrbase); |
| return; |
| } |
| |
| pmd = pmd_offset(pud, (unsigned long)sfrbase); |
| if (!pmd) { |
| pr_auto(ASL4, "Invalid virtual address %p\n", sfrbase); |
| return; |
| } |
| |
| pte = pte_offset_kernel(pmd, (unsigned long)sfrbase); |
| if (!pte) { |
| pr_auto(ASL4, "Invalid virtual address %p\n", sfrbase); |
| return; |
| } |
| |
| info = MMU_RAW_VER(__raw_readl(sfrbase + REG_MMU_VERSION)); |
| |
| phys = pte_pfn(*pte) << PAGE_SHIFT; |
| pr_auto(ASL4, "ADDR: %pa(VA: %p), MMU_CTRL: %#010x, PT_BASE: %#010x\n", |
| &phys, sfrbase, |
| __raw_readl(sfrbase + REG_MMU_CTRL), |
| __raw_readl(sfrbase + REG_PT_BASE_PPN)); |
| pr_auto(ASL4, "VERSION %d.%d.%d, MMU_CFG: %#010x, MMU_STATUS: %#010x\n", |
| MMU_MAJ_VER(info), MMU_MIN_VER(info), MMU_REV_VER(info), |
| __raw_readl(sfrbase + REG_MMU_CFG), |
| __raw_readl(sfrbase + REG_MMU_STATUS)); |
| |
| if (IS_TLB_WAY_TYPE(drvdata)) |
| dump_sysmmu_tlb_way(drvdata); |
| else if(IS_TLB_PORT_TYPE(drvdata)) |
| dump_sysmmu_tlb_port(drvdata, pgtable); |
| } |
| |
| static inline void show_secure_fault_information(struct sysmmu_drvdata *drvdata, |
| int flags, unsigned long fault_addr) |
| { |
| unsigned int info; |
| phys_addr_t pgtable; |
| int fault_id = SYSMMU_FAULT_ID(flags); |
| unsigned int sfrbase = drvdata->securebase; |
| const char *port_name = NULL; |
| |
| #ifdef CONFIG_SEC_DEBUG_EXTRA_INFO |
| char temp_buf[SZ_128]; |
| #endif |
| |
| pgtable = __secure_info_read(sfrbase + REG_PT_BASE_PPN); |
| pgtable <<= PAGE_SHIFT; |
| |
| info = __secure_info_read(sfrbase + REG_FAULT_TRANS_INFO); |
| |
| of_property_read_string(drvdata->sysmmu->of_node, |
| "port-name", &port_name); |
| |
| pr_auto_once(4); |
| pr_auto(ASL4, "----------------------------------------------------------\n"); |
| pr_auto(ASL4, "From [%s], SysMMU %s %s at %#010lx (page table @ %pa)\n", |
| port_name ? port_name : dev_name(drvdata->sysmmu), |
| (flags & IOMMU_FAULT_WRITE) ? "WRITE" : "READ", |
| sysmmu_fault_name[fault_id], fault_addr, &pgtable); |
| |
| #ifdef CONFIG_SEC_DEBUG_EXTRA_INFO |
| snprintf(temp_buf, SZ_128, "%s %s %s at %#010lx (%pa)", |
| port_name ? port_name : dev_name(drvdata->sysmmu), |
| (flags & IOMMU_FAULT_WRITE) ? "WRITE" : "READ", |
| sysmmu_fault_name[fault_id], fault_addr, &pgtable); |
| sec_debug_set_extra_info_sysmmu(temp_buf); |
| #endif |
| |
| if (fault_id == SYSMMU_FAULT_UNKNOWN) { |
| pr_auto(ASL4, "The fault is not caused by this System MMU.\n"); |
| pr_auto(ASL4, "Please check IRQ and SFR base address.\n"); |
| goto finish; |
| } |
| |
| pr_auto(ASL4, "AxID: %#x, AxLEN: %#x\n", info & 0xFFFF, (info >> 16) & 0xF); |
| |
| if (fault_id == SYSMMU_FAULT_PTW_ACCESS) |
| pr_auto(ASL4, "System MMU has failed to access page table\n"); |
| |
| if (!pfn_valid(pgtable >> PAGE_SHIFT)) { |
| pr_auto(ASL4, "Page table base is not in a valid memory region\n"); |
| } else { |
| sysmmu_pte_t *ent; |
| ent = section_entry(phys_to_virt(pgtable), fault_addr); |
| pr_auto(ASL4, "Lv1 entry: %#010x\n", *ent); |
| |
| if (lv1ent_page(ent)) { |
| ent = page_entry(ent, fault_addr); |
| pr_auto(ASL4, "Lv2 entry: %#010x\n", *ent); |
| } |
| } |
| |
| info = MMU_RAW_VER(__secure_info_read(sfrbase + REG_MMU_VERSION)); |
| |
| pr_auto(ASL4, "ADDR: %#x, MMU_CTRL: %#010x, PT_BASE: %#010x\n", |
| sfrbase, |
| __secure_info_read(sfrbase + REG_MMU_CTRL), |
| __secure_info_read(sfrbase + REG_PT_BASE_PPN)); |
| pr_auto(ASL4, "VERSION %d.%d.%d, MMU_CFG: %#010x, MMU_STATUS: %#010x\n", |
| MMU_MAJ_VER(info), MMU_MIN_VER(info), MMU_REV_VER(info), |
| __secure_info_read(sfrbase + REG_MMU_CFG), |
| __secure_info_read(sfrbase + REG_MMU_STATUS)); |
| |
| finish: |
| pr_auto(ASL4, "----------------------------------------------------------\n"); |
| pr_auto_disable(4); |
| } |
| |
| static inline int show_fault_information(struct sysmmu_drvdata *drvdata, |
| int flags, unsigned long fault_addr) |
| { |
| unsigned int info; |
| phys_addr_t pgtable; |
| int fault_id = SYSMMU_FAULT_ID(flags); |
| const char *port_name = NULL; |
| static int ptw_count = 0; |
| #ifdef CONFIG_SEC_DEBUG_EXTRA_INFO |
| char temp_buf[SZ_128]; |
| #endif |
| |
| pgtable = __raw_readl(drvdata->sfrbase + REG_PT_BASE_PPN); |
| pgtable <<= PAGE_SHIFT; |
| |
| info = __raw_readl(drvdata->sfrbase + REG_FAULT_TRANS_INFO); |
| |
| of_property_read_string(drvdata->sysmmu->of_node, |
| "port-name", &port_name); |
| |
| pr_auto_once(4); |
| pr_auto(ASL4, "----------------------------------------------------------\n"); |
| pr_auto(ASL4, "From [%s], SysMMU %s %s at %#010lx (page table @ %pa)\n", |
| port_name ? port_name : dev_name(drvdata->sysmmu), |
| (flags & IOMMU_FAULT_WRITE) ? "WRITE" : "READ", |
| sysmmu_fault_name[fault_id], fault_addr, &pgtable); |
| |
| #ifdef CONFIG_SEC_DEBUG_EXTRA_INFO |
| snprintf(temp_buf, SZ_128, "%s %s %s at %#010lx (%pa)", |
| port_name ? port_name : dev_name(drvdata->sysmmu), |
| (flags & IOMMU_FAULT_WRITE) ? "WRITE" : "READ", |
| sysmmu_fault_name[fault_id], fault_addr, &pgtable); |
| sec_debug_set_extra_info_sysmmu(temp_buf); |
| #endif |
| |
| if (fault_id == SYSMMU_FAULT_UNKNOWN) { |
| pr_auto(ASL4, "The fault is not caused by this System MMU.\n"); |
| pr_auto(ASL4, "Please check IRQ and SFR base address.\n"); |
| goto finish; |
| } |
| |
| pr_auto(ASL4, "AxID: %#x, AxLEN: %#x\n", info & 0xFFFF, (info >> 16) & 0xF); |
| |
| if (pgtable != drvdata->pgtable) |
| pr_auto(ASL4, "Page table base of driver: %pa\n", |
| &drvdata->pgtable); |
| |
| if (!pfn_valid(pgtable >> PAGE_SHIFT)) { |
| pr_auto(ASL4, "Page table base is not in a valid memory region\n"); |
| pgtable = 0; |
| } else { |
| sysmmu_pte_t *ent; |
| ent = section_entry(phys_to_virt(pgtable), fault_addr); |
| pr_auto(ASL4, "Lv1 entry: %#010x\n", *ent); |
| |
| if (lv1ent_page(ent)) { |
| ent = page_entry(ent, fault_addr); |
| pr_auto(ASL4, "Lv2 entry: %#010x\n", *ent); |
| } |
| } |
| |
| if (fault_id == SYSMMU_FAULT_PTW_ACCESS) { |
| ptw_count++; |
| pr_auto(ASL4, "System MMU has failed to access page table, %d\n", ptw_count); |
| pgtable = 0; |
| |
| if (ptw_count > 3) |
| panic("Unrecoverable System MMU PTW fault"); |
| |
| writel(0x1, drvdata->sfrbase + REG_INT_CLEAR); |
| |
| return -EAGAIN; |
| } |
| |
| dump_sysmmu_status(drvdata, pgtable); |
| |
| finish: |
| pr_auto(ASL4, "----------------------------------------------------------\n"); |
| pr_auto_disable(4); |
| |
| return 0; |
| } |
| |
| static inline void __sysmmu_disable_nocount(struct sysmmu_drvdata *drvdata) |
| { |
| writel_relaxed(0, drvdata->sfrbase + REG_MMU_CFG); |
| writel_relaxed(CTRL_BLOCK_DISABLE, drvdata->sfrbase + REG_MMU_CTRL); |
| BUG_ON(readl_relaxed(drvdata->sfrbase + REG_MMU_CTRL) != CTRL_BLOCK_DISABLE); |
| |
| clk_disable(drvdata->clk); |
| |
| SYSMMU_EVENT_LOG_DISABLE(SYSMMU_DRVDATA_TO_LOG(drvdata)); |
| } |
| |
| static inline void __sysmmu_set_public_way(struct sysmmu_drvdata *drvdata, |
| unsigned int public_cfg) |
| { |
| u32 cfg = __raw_readl(drvdata->sfrbase + REG_PUBLIC_WAY_CFG); |
| cfg &= ~MMU_PUBLIC_WAY_MASK; |
| cfg |= public_cfg; |
| |
| writel_relaxed(cfg, drvdata->sfrbase + REG_PUBLIC_WAY_CFG); |
| |
| dev_dbg(drvdata->sysmmu, "public_cfg : %#x\n", cfg); |
| } |
| |
| static inline void __sysmmu_set_private_way_id(struct sysmmu_drvdata *drvdata, |
| unsigned int way_idx) |
| { |
| struct tlb_priv_id *priv_cfg = drvdata->tlb_props.way_props.priv_id_cfg; |
| u32 cfg = __raw_readl(drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx)); |
| |
| cfg &= ~MMU_PRIVATE_WAY_MASK; |
| cfg |= MMU_WAY_CFG_ID_MATCHING | MMU_WAY_CFG_PRIVATE_ENABLE | |
| priv_cfg[way_idx].cfg; |
| |
| writel_relaxed(cfg, drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx)); |
| writel_relaxed(priv_cfg[way_idx].id, |
| drvdata->sfrbase + REG_PRIVATE_ID(way_idx)); |
| |
| dev_dbg(drvdata->sysmmu, "priv ID way[%d] cfg : %#x, id : %#x\n", |
| way_idx, cfg, priv_cfg[way_idx].id); |
| } |
| |
| static inline void __sysmmu_set_private_way_addr(struct sysmmu_drvdata *drvdata, |
| unsigned int priv_addr_idx) |
| { |
| struct tlb_priv_addr *priv_cfg = |
| drvdata->tlb_props.way_props.priv_addr_cfg; |
| unsigned int way_idx = |
| drvdata->tlb_props.way_props.priv_id_cnt + priv_addr_idx; |
| u32 cfg = __raw_readl(drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx)); |
| |
| cfg &= ~MMU_PRIVATE_WAY_MASK; |
| cfg |= MMU_WAY_CFG_ADDR_MATCHING | MMU_WAY_CFG_PRIVATE_ENABLE | |
| priv_cfg[priv_addr_idx].cfg; |
| |
| writel_relaxed(cfg, drvdata->sfrbase + REG_PRIVATE_WAY_CFG(way_idx)); |
| |
| dev_dbg(drvdata->sysmmu, "priv ADDR way[%d] cfg : %#x\n", way_idx, cfg); |
| } |
| |
| static inline void __sysmmu_set_tlb_way_type(struct sysmmu_drvdata *drvdata) |
| { |
| u32 cfg = __raw_readl(drvdata->sfrbase + REG_MMU_CAPA0_V7); |
| u32 tlb_way_num = MMU_CAPA_NUM_TLB_WAY(cfg); |
| u32 set_cnt = 0; |
| struct tlb_props *tlb_props = &drvdata->tlb_props; |
| unsigned int i; |
| int priv_id_cnt = tlb_props->way_props.priv_id_cnt; |
| int priv_addr_cnt = tlb_props->way_props.priv_addr_cnt; |
| |
| if (tlb_props->flags & TLB_WAY_PUBLIC) |
| __sysmmu_set_public_way(drvdata, |
| tlb_props->way_props.public_cfg); |
| |
| if (tlb_props->flags & TLB_WAY_PRIVATE_ID) { |
| for (i = 0; i < priv_id_cnt && |
| set_cnt < tlb_way_num; i++, set_cnt++) |
| __sysmmu_set_private_way_id(drvdata, i); |
| } |
| |
| if (tlb_props->flags & TLB_WAY_PRIVATE_ADDR) { |
| for (i = 0; i < priv_addr_cnt && |
| set_cnt < tlb_way_num; i++, set_cnt++) |
| __sysmmu_set_private_way_addr(drvdata, i); |
| } |
| |
| if (priv_id_cnt + priv_addr_cnt > tlb_way_num) { |
| dev_warn(drvdata->sysmmu, |
| "Too many values than TLB way count %d," |
| " so ignored!\n", tlb_way_num); |
| dev_warn(drvdata->sysmmu, |
| "Number of private way id/addr = %d/%d\n", |
| priv_id_cnt, priv_addr_cnt); |
| } |
| } |
| |
| static inline void __sysmmu_set_tlb_port(struct sysmmu_drvdata *drvdata, |
| unsigned int port_idx) |
| { |
| struct tlb_port_cfg *port_cfg = drvdata->tlb_props.port_props.port_cfg; |
| |
| writel_relaxed(MMU_TLB_CFG_MASK(port_cfg[port_idx].cfg), |
| drvdata->sfrbase + REG_MMU_TLB_CFG(port_idx)); |
| |
| /* port_idx 0 is default port. */ |
| if (port_idx == 0) { |
| dev_dbg(drvdata->sysmmu, "port[%d] cfg : %#x for common\n", |
| port_idx, |
| MMU_TLB_CFG_MASK(port_cfg[port_idx].cfg)); |
| return; |
| } |
| |
| writel_relaxed(MMU_TLB_MATCH_CFG_MASK(port_cfg[port_idx].cfg), |
| drvdata->sfrbase + REG_MMU_TLB_MATCH_CFG(port_idx)); |
| writel_relaxed(port_cfg[port_idx].id, |
| drvdata->sfrbase + REG_MMU_TLB_MATCH_ID(port_idx)); |
| |
| dev_dbg(drvdata->sysmmu, "port[%d] cfg : %#x, match : %#x, id : %#x\n", |
| port_idx, |
| MMU_TLB_CFG_MASK(port_cfg[port_idx].cfg), |
| MMU_TLB_MATCH_CFG_MASK(port_cfg[port_idx].cfg), |
| port_cfg[port_idx].id); |
| } |
| |
| static inline void __sysmmu_set_tlb_port_type(struct sysmmu_drvdata *drvdata) |
| { |
| u32 cfg = __raw_readl(drvdata->sfrbase + REG_MMU_CAPA1_V7); |
| u32 tlb_num = MMU_CAPA1_NUM_TLB(cfg); |
| struct tlb_props *tlb_props = &drvdata->tlb_props; |
| unsigned int i; |
| int port_id_cnt = tlb_props->port_props.port_id_cnt; |
| int slot_cnt = tlb_props->port_props.slot_cnt; |
| |
| if (port_id_cnt > tlb_num) { |
| dev_warn(drvdata->sysmmu, |
| "Too many values %d than TLB count %d," |
| " so ignored!\n", port_id_cnt, tlb_num); |
| port_id_cnt = tlb_num; |
| } |
| |
| for (i = 0; i < port_id_cnt; i++) |
| __sysmmu_set_tlb_port(drvdata, i); |
| |
| for (i = 0; i < slot_cnt; i++) |
| writel_relaxed(tlb_props->port_props.slot_cfg[i], |
| drvdata->sfrbase + REG_SLOT_RSV(i)); |
| } |
| |
| static inline void __sysmmu_init_config(struct sysmmu_drvdata *drvdata) |
| { |
| unsigned long cfg = 0; |
| |
| if (IS_TLB_WAY_TYPE(drvdata)) |
| __sysmmu_set_tlb_way_type(drvdata); |
| else if (IS_TLB_PORT_TYPE(drvdata)) |
| __sysmmu_set_tlb_port_type(drvdata); |
| |
| if (drvdata->qos != DEFAULT_QOS_VALUE) |
| cfg |= CFG_QOS_OVRRIDE | CFG_QOS(drvdata->qos); |
| |
| cfg |= __raw_readl(drvdata->sfrbase + REG_MMU_CFG) & ~CFG_MASK; |
| writel_relaxed(cfg, drvdata->sfrbase + REG_MMU_CFG); |
| } |
| |
| static inline void __sysmmu_enable_nocount(struct sysmmu_drvdata *drvdata) |
| { |
| clk_enable(drvdata->clk); |
| |
| __sysmmu_init_config(drvdata); |
| |
| __sysmmu_set_ptbase(drvdata, drvdata->pgtable / PAGE_SIZE); |
| |
| writel(CTRL_ENABLE, drvdata->sfrbase + REG_MMU_CTRL); |
| |
| SYSMMU_EVENT_LOG_ENABLE(SYSMMU_DRVDATA_TO_LOG(drvdata)); |
| } |
| |
| static inline u32 __sysmmu_get_intr_status(struct sysmmu_drvdata *drvdata, |
| bool is_secure) |
| { |
| if (is_secure) |
| return __secure_info_read(drvdata->securebase + REG_INT_STATUS); |
| else |
| return __raw_readl(drvdata->sfrbase + REG_INT_STATUS); |
| } |
| |
| static inline u32 __sysmmu_get_fault_address(struct sysmmu_drvdata *drvdata, |
| bool is_secure) |
| { |
| if (is_secure) |
| return __secure_info_read(drvdata->securebase + REG_FAULT_ADDR); |
| else |
| return __raw_readl(drvdata->sfrbase + REG_FAULT_ADDR); |
| } |
| |
| static inline u32 __sysmmu_get_fault_trans_info(struct sysmmu_drvdata *drvdata, |
| bool is_secure) |
| { |
| if (is_secure) |
| return __secure_info_read( |
| drvdata->securebase + REG_FAULT_TRANS_INFO); |
| else |
| return __raw_readl(drvdata->sfrbase + REG_FAULT_TRANS_INFO); |
| } |
| |
| static inline u32 __sysmmu_get_hw_version(struct sysmmu_drvdata *data) |
| { |
| return MMU_RAW_VER(__raw_readl(data->sfrbase + REG_MMU_VERSION)); |
| } |
| |
| static inline bool __sysmmu_has_capa1(struct sysmmu_drvdata *data) |
| { |
| return MMU_CAPA1_EXIST(__raw_readl(data->sfrbase + REG_MMU_CAPA0_V7)); |
| } |
| |
| static inline u32 __sysmmu_get_capa_type(struct sysmmu_drvdata *data) |
| { |
| return MMU_CAPA1_TYPE(__raw_readl(data->sfrbase + REG_MMU_CAPA1_V7)); |
| } |