18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (c) 2011,2016 Samsung Electronics Co., Ltd. 48c2ecf20Sopenharmony_ci * http://www.samsung.com 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#ifdef CONFIG_EXYNOS_IOMMU_DEBUG 88c2ecf20Sopenharmony_ci#define DEBUG 98c2ecf20Sopenharmony_ci#endif 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/clk.h> 128c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h> 138c2ecf20Sopenharmony_ci#include <linux/err.h> 148c2ecf20Sopenharmony_ci#include <linux/io.h> 158c2ecf20Sopenharmony_ci#include <linux/iommu.h> 168c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 178c2ecf20Sopenharmony_ci#include <linux/kmemleak.h> 188c2ecf20Sopenharmony_ci#include <linux/list.h> 198c2ecf20Sopenharmony_ci#include <linux/of.h> 208c2ecf20Sopenharmony_ci#include <linux/of_iommu.h> 218c2ecf20Sopenharmony_ci#include <linux/of_platform.h> 228c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 238c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 248c2ecf20Sopenharmony_ci#include <linux/slab.h> 258c2ecf20Sopenharmony_ci#include <linux/dma-iommu.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_citypedef u32 sysmmu_iova_t; 288c2ecf20Sopenharmony_citypedef u32 sysmmu_pte_t; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* We do not consider super section mapping (16MB) */ 318c2ecf20Sopenharmony_ci#define SECT_ORDER 20 328c2ecf20Sopenharmony_ci#define LPAGE_ORDER 16 338c2ecf20Sopenharmony_ci#define SPAGE_ORDER 12 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#define SECT_SIZE (1 << SECT_ORDER) 368c2ecf20Sopenharmony_ci#define LPAGE_SIZE (1 << LPAGE_ORDER) 378c2ecf20Sopenharmony_ci#define SPAGE_SIZE (1 << SPAGE_ORDER) 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci#define SECT_MASK (~(SECT_SIZE - 1)) 408c2ecf20Sopenharmony_ci#define LPAGE_MASK (~(LPAGE_SIZE - 1)) 418c2ecf20Sopenharmony_ci#define SPAGE_MASK (~(SPAGE_SIZE - 1)) 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci#define lv1ent_fault(sent) ((*(sent) == ZERO_LV2LINK) || \ 448c2ecf20Sopenharmony_ci ((*(sent) & 3) == 0) || ((*(sent) & 3) == 3)) 458c2ecf20Sopenharmony_ci#define lv1ent_zero(sent) (*(sent) == ZERO_LV2LINK) 468c2ecf20Sopenharmony_ci#define lv1ent_page_zero(sent) ((*(sent) & 3) == 1) 478c2ecf20Sopenharmony_ci#define lv1ent_page(sent) ((*(sent) != ZERO_LV2LINK) && \ 488c2ecf20Sopenharmony_ci ((*(sent) & 3) == 1)) 498c2ecf20Sopenharmony_ci#define lv1ent_section(sent) ((*(sent) & 3) == 2) 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#define lv2ent_fault(pent) ((*(pent) & 3) == 0) 528c2ecf20Sopenharmony_ci#define lv2ent_small(pent) ((*(pent) & 2) == 2) 538c2ecf20Sopenharmony_ci#define lv2ent_large(pent) ((*(pent) & 3) == 1) 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/* 568c2ecf20Sopenharmony_ci * v1.x - v3.x SYSMMU supports 32bit physical and 32bit virtual address spaces 578c2ecf20Sopenharmony_ci * v5.0 introduced support for 36bit physical address space by shifting 588c2ecf20Sopenharmony_ci * all page entry values by 4 bits. 598c2ecf20Sopenharmony_ci * All SYSMMU controllers in the system support the address spaces of the same 608c2ecf20Sopenharmony_ci * size, so PG_ENT_SHIFT can be initialized on first SYSMMU probe to proper 618c2ecf20Sopenharmony_ci * value (0 or 4). 628c2ecf20Sopenharmony_ci */ 638c2ecf20Sopenharmony_cistatic short PG_ENT_SHIFT = -1; 648c2ecf20Sopenharmony_ci#define SYSMMU_PG_ENT_SHIFT 0 658c2ecf20Sopenharmony_ci#define SYSMMU_V5_PG_ENT_SHIFT 4 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_cistatic const sysmmu_pte_t *LV1_PROT; 688c2ecf20Sopenharmony_cistatic const sysmmu_pte_t SYSMMU_LV1_PROT[] = { 698c2ecf20Sopenharmony_ci ((0 << 15) | (0 << 10)), /* no access */ 708c2ecf20Sopenharmony_ci ((1 << 15) | (1 << 10)), /* IOMMU_READ only */ 718c2ecf20Sopenharmony_ci ((0 << 15) | (1 << 10)), /* IOMMU_WRITE not supported, use read/write */ 728c2ecf20Sopenharmony_ci ((0 << 15) | (1 << 10)), /* IOMMU_READ | IOMMU_WRITE */ 738c2ecf20Sopenharmony_ci}; 748c2ecf20Sopenharmony_cistatic const sysmmu_pte_t SYSMMU_V5_LV1_PROT[] = { 758c2ecf20Sopenharmony_ci (0 << 4), /* no access */ 768c2ecf20Sopenharmony_ci (1 << 4), /* IOMMU_READ only */ 778c2ecf20Sopenharmony_ci (2 << 4), /* IOMMU_WRITE only */ 788c2ecf20Sopenharmony_ci (3 << 4), /* IOMMU_READ | IOMMU_WRITE */ 798c2ecf20Sopenharmony_ci}; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_cistatic const sysmmu_pte_t *LV2_PROT; 828c2ecf20Sopenharmony_cistatic const sysmmu_pte_t SYSMMU_LV2_PROT[] = { 838c2ecf20Sopenharmony_ci ((0 << 9) | (0 << 4)), /* no access */ 848c2ecf20Sopenharmony_ci ((1 << 9) | (1 << 4)), /* IOMMU_READ only */ 858c2ecf20Sopenharmony_ci ((0 << 9) | (1 << 4)), /* IOMMU_WRITE not supported, use read/write */ 868c2ecf20Sopenharmony_ci ((0 << 9) | (1 << 4)), /* IOMMU_READ | IOMMU_WRITE */ 878c2ecf20Sopenharmony_ci}; 888c2ecf20Sopenharmony_cistatic const sysmmu_pte_t SYSMMU_V5_LV2_PROT[] = { 898c2ecf20Sopenharmony_ci (0 << 2), /* no access */ 908c2ecf20Sopenharmony_ci (1 << 2), /* IOMMU_READ only */ 918c2ecf20Sopenharmony_ci (2 << 2), /* IOMMU_WRITE only */ 928c2ecf20Sopenharmony_ci (3 << 2), /* IOMMU_READ | IOMMU_WRITE */ 938c2ecf20Sopenharmony_ci}; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci#define SYSMMU_SUPPORTED_PROT_BITS (IOMMU_READ | IOMMU_WRITE) 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci#define sect_to_phys(ent) (((phys_addr_t) ent) << PG_ENT_SHIFT) 988c2ecf20Sopenharmony_ci#define section_phys(sent) (sect_to_phys(*(sent)) & SECT_MASK) 998c2ecf20Sopenharmony_ci#define section_offs(iova) (iova & (SECT_SIZE - 1)) 1008c2ecf20Sopenharmony_ci#define lpage_phys(pent) (sect_to_phys(*(pent)) & LPAGE_MASK) 1018c2ecf20Sopenharmony_ci#define lpage_offs(iova) (iova & (LPAGE_SIZE - 1)) 1028c2ecf20Sopenharmony_ci#define spage_phys(pent) (sect_to_phys(*(pent)) & SPAGE_MASK) 1038c2ecf20Sopenharmony_ci#define spage_offs(iova) (iova & (SPAGE_SIZE - 1)) 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci#define NUM_LV1ENTRIES 4096 1068c2ecf20Sopenharmony_ci#define NUM_LV2ENTRIES (SECT_SIZE / SPAGE_SIZE) 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic u32 lv1ent_offset(sysmmu_iova_t iova) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci return iova >> SECT_ORDER; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic u32 lv2ent_offset(sysmmu_iova_t iova) 1148c2ecf20Sopenharmony_ci{ 1158c2ecf20Sopenharmony_ci return (iova >> SPAGE_ORDER) & (NUM_LV2ENTRIES - 1); 1168c2ecf20Sopenharmony_ci} 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci#define LV1TABLE_SIZE (NUM_LV1ENTRIES * sizeof(sysmmu_pte_t)) 1198c2ecf20Sopenharmony_ci#define LV2TABLE_SIZE (NUM_LV2ENTRIES * sizeof(sysmmu_pte_t)) 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci#define SPAGES_PER_LPAGE (LPAGE_SIZE / SPAGE_SIZE) 1228c2ecf20Sopenharmony_ci#define lv2table_base(sent) (sect_to_phys(*(sent) & 0xFFFFFFC0)) 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci#define mk_lv1ent_sect(pa, prot) ((pa >> PG_ENT_SHIFT) | LV1_PROT[prot] | 2) 1258c2ecf20Sopenharmony_ci#define mk_lv1ent_page(pa) ((pa >> PG_ENT_SHIFT) | 1) 1268c2ecf20Sopenharmony_ci#define mk_lv2ent_lpage(pa, prot) ((pa >> PG_ENT_SHIFT) | LV2_PROT[prot] | 1) 1278c2ecf20Sopenharmony_ci#define mk_lv2ent_spage(pa, prot) ((pa >> PG_ENT_SHIFT) | LV2_PROT[prot] | 2) 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci#define CTRL_ENABLE 0x5 1308c2ecf20Sopenharmony_ci#define CTRL_BLOCK 0x7 1318c2ecf20Sopenharmony_ci#define CTRL_DISABLE 0x0 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci#define CFG_LRU 0x1 1348c2ecf20Sopenharmony_ci#define CFG_EAP (1 << 2) 1358c2ecf20Sopenharmony_ci#define CFG_QOS(n) ((n & 0xF) << 7) 1368c2ecf20Sopenharmony_ci#define CFG_ACGEN (1 << 24) /* System MMU 3.3 only */ 1378c2ecf20Sopenharmony_ci#define CFG_SYSSEL (1 << 22) /* System MMU 3.2 only */ 1388c2ecf20Sopenharmony_ci#define CFG_FLPDCACHE (1 << 20) /* System MMU 3.2+ only */ 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci/* common registers */ 1418c2ecf20Sopenharmony_ci#define REG_MMU_CTRL 0x000 1428c2ecf20Sopenharmony_ci#define REG_MMU_CFG 0x004 1438c2ecf20Sopenharmony_ci#define REG_MMU_STATUS 0x008 1448c2ecf20Sopenharmony_ci#define REG_MMU_VERSION 0x034 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci#define MMU_MAJ_VER(val) ((val) >> 7) 1478c2ecf20Sopenharmony_ci#define MMU_MIN_VER(val) ((val) & 0x7F) 1488c2ecf20Sopenharmony_ci#define MMU_RAW_VER(reg) (((reg) >> 21) & ((1 << 11) - 1)) /* 11 bits */ 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci#define MAKE_MMU_VER(maj, min) ((((maj) & 0xF) << 7) | ((min) & 0x7F)) 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci/* v1.x - v3.x registers */ 1538c2ecf20Sopenharmony_ci#define REG_MMU_FLUSH 0x00C 1548c2ecf20Sopenharmony_ci#define REG_MMU_FLUSH_ENTRY 0x010 1558c2ecf20Sopenharmony_ci#define REG_PT_BASE_ADDR 0x014 1568c2ecf20Sopenharmony_ci#define REG_INT_STATUS 0x018 1578c2ecf20Sopenharmony_ci#define REG_INT_CLEAR 0x01C 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci#define REG_PAGE_FAULT_ADDR 0x024 1608c2ecf20Sopenharmony_ci#define REG_AW_FAULT_ADDR 0x028 1618c2ecf20Sopenharmony_ci#define REG_AR_FAULT_ADDR 0x02C 1628c2ecf20Sopenharmony_ci#define REG_DEFAULT_SLAVE_ADDR 0x030 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci/* v5.x registers */ 1658c2ecf20Sopenharmony_ci#define REG_V5_PT_BASE_PFN 0x00C 1668c2ecf20Sopenharmony_ci#define REG_V5_MMU_FLUSH_ALL 0x010 1678c2ecf20Sopenharmony_ci#define REG_V5_MMU_FLUSH_ENTRY 0x014 1688c2ecf20Sopenharmony_ci#define REG_V5_MMU_FLUSH_RANGE 0x018 1698c2ecf20Sopenharmony_ci#define REG_V5_MMU_FLUSH_START 0x020 1708c2ecf20Sopenharmony_ci#define REG_V5_MMU_FLUSH_END 0x024 1718c2ecf20Sopenharmony_ci#define REG_V5_INT_STATUS 0x060 1728c2ecf20Sopenharmony_ci#define REG_V5_INT_CLEAR 0x064 1738c2ecf20Sopenharmony_ci#define REG_V5_FAULT_AR_VA 0x070 1748c2ecf20Sopenharmony_ci#define REG_V5_FAULT_AW_VA 0x080 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci#define has_sysmmu(dev) (dev_iommu_priv_get(dev) != NULL) 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic struct device *dma_dev; 1798c2ecf20Sopenharmony_cistatic struct kmem_cache *lv2table_kmem_cache; 1808c2ecf20Sopenharmony_cistatic sysmmu_pte_t *zero_lv2_table; 1818c2ecf20Sopenharmony_ci#define ZERO_LV2LINK mk_lv1ent_page(virt_to_phys(zero_lv2_table)) 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_cistatic sysmmu_pte_t *section_entry(sysmmu_pte_t *pgtable, sysmmu_iova_t iova) 1848c2ecf20Sopenharmony_ci{ 1858c2ecf20Sopenharmony_ci return pgtable + lv1ent_offset(iova); 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic sysmmu_pte_t *page_entry(sysmmu_pte_t *sent, sysmmu_iova_t iova) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci return (sysmmu_pte_t *)phys_to_virt( 1918c2ecf20Sopenharmony_ci lv2table_base(sent)) + lv2ent_offset(iova); 1928c2ecf20Sopenharmony_ci} 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci/* 1958c2ecf20Sopenharmony_ci * IOMMU fault information register 1968c2ecf20Sopenharmony_ci */ 1978c2ecf20Sopenharmony_cistruct sysmmu_fault_info { 1988c2ecf20Sopenharmony_ci unsigned int bit; /* bit number in STATUS register */ 1998c2ecf20Sopenharmony_ci unsigned short addr_reg; /* register to read VA fault address */ 2008c2ecf20Sopenharmony_ci const char *name; /* human readable fault name */ 2018c2ecf20Sopenharmony_ci unsigned int type; /* fault type for report_iommu_fault */ 2028c2ecf20Sopenharmony_ci}; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistatic const struct sysmmu_fault_info sysmmu_faults[] = { 2058c2ecf20Sopenharmony_ci { 0, REG_PAGE_FAULT_ADDR, "PAGE", IOMMU_FAULT_READ }, 2068c2ecf20Sopenharmony_ci { 1, REG_AR_FAULT_ADDR, "AR MULTI-HIT", IOMMU_FAULT_READ }, 2078c2ecf20Sopenharmony_ci { 2, REG_AW_FAULT_ADDR, "AW MULTI-HIT", IOMMU_FAULT_WRITE }, 2088c2ecf20Sopenharmony_ci { 3, REG_DEFAULT_SLAVE_ADDR, "BUS ERROR", IOMMU_FAULT_READ }, 2098c2ecf20Sopenharmony_ci { 4, REG_AR_FAULT_ADDR, "AR SECURITY PROTECTION", IOMMU_FAULT_READ }, 2108c2ecf20Sopenharmony_ci { 5, REG_AR_FAULT_ADDR, "AR ACCESS PROTECTION", IOMMU_FAULT_READ }, 2118c2ecf20Sopenharmony_ci { 6, REG_AW_FAULT_ADDR, "AW SECURITY PROTECTION", IOMMU_FAULT_WRITE }, 2128c2ecf20Sopenharmony_ci { 7, REG_AW_FAULT_ADDR, "AW ACCESS PROTECTION", IOMMU_FAULT_WRITE }, 2138c2ecf20Sopenharmony_ci}; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic const struct sysmmu_fault_info sysmmu_v5_faults[] = { 2168c2ecf20Sopenharmony_ci { 0, REG_V5_FAULT_AR_VA, "AR PTW", IOMMU_FAULT_READ }, 2178c2ecf20Sopenharmony_ci { 1, REG_V5_FAULT_AR_VA, "AR PAGE", IOMMU_FAULT_READ }, 2188c2ecf20Sopenharmony_ci { 2, REG_V5_FAULT_AR_VA, "AR MULTI-HIT", IOMMU_FAULT_READ }, 2198c2ecf20Sopenharmony_ci { 3, REG_V5_FAULT_AR_VA, "AR ACCESS PROTECTION", IOMMU_FAULT_READ }, 2208c2ecf20Sopenharmony_ci { 4, REG_V5_FAULT_AR_VA, "AR SECURITY PROTECTION", IOMMU_FAULT_READ }, 2218c2ecf20Sopenharmony_ci { 16, REG_V5_FAULT_AW_VA, "AW PTW", IOMMU_FAULT_WRITE }, 2228c2ecf20Sopenharmony_ci { 17, REG_V5_FAULT_AW_VA, "AW PAGE", IOMMU_FAULT_WRITE }, 2238c2ecf20Sopenharmony_ci { 18, REG_V5_FAULT_AW_VA, "AW MULTI-HIT", IOMMU_FAULT_WRITE }, 2248c2ecf20Sopenharmony_ci { 19, REG_V5_FAULT_AW_VA, "AW ACCESS PROTECTION", IOMMU_FAULT_WRITE }, 2258c2ecf20Sopenharmony_ci { 20, REG_V5_FAULT_AW_VA, "AW SECURITY PROTECTION", IOMMU_FAULT_WRITE }, 2268c2ecf20Sopenharmony_ci}; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci/* 2298c2ecf20Sopenharmony_ci * This structure is attached to dev->iommu->priv of the master device 2308c2ecf20Sopenharmony_ci * on device add, contains a list of SYSMMU controllers defined by device tree, 2318c2ecf20Sopenharmony_ci * which are bound to given master device. It is usually referenced by 'owner' 2328c2ecf20Sopenharmony_ci * pointer. 2338c2ecf20Sopenharmony_ci*/ 2348c2ecf20Sopenharmony_cistruct exynos_iommu_owner { 2358c2ecf20Sopenharmony_ci struct list_head controllers; /* list of sysmmu_drvdata.owner_node */ 2368c2ecf20Sopenharmony_ci struct iommu_domain *domain; /* domain this device is attached */ 2378c2ecf20Sopenharmony_ci struct mutex rpm_lock; /* for runtime pm of all sysmmus */ 2388c2ecf20Sopenharmony_ci}; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci/* 2418c2ecf20Sopenharmony_ci * This structure exynos specific generalization of struct iommu_domain. 2428c2ecf20Sopenharmony_ci * It contains list of SYSMMU controllers from all master devices, which has 2438c2ecf20Sopenharmony_ci * been attached to this domain and page tables of IO address space defined by 2448c2ecf20Sopenharmony_ci * it. It is usually referenced by 'domain' pointer. 2458c2ecf20Sopenharmony_ci */ 2468c2ecf20Sopenharmony_cistruct exynos_iommu_domain { 2478c2ecf20Sopenharmony_ci struct list_head clients; /* list of sysmmu_drvdata.domain_node */ 2488c2ecf20Sopenharmony_ci sysmmu_pte_t *pgtable; /* lv1 page table, 16KB */ 2498c2ecf20Sopenharmony_ci short *lv2entcnt; /* free lv2 entry counter for each section */ 2508c2ecf20Sopenharmony_ci spinlock_t lock; /* lock for modyfying list of clients */ 2518c2ecf20Sopenharmony_ci spinlock_t pgtablelock; /* lock for modifying page table @ pgtable */ 2528c2ecf20Sopenharmony_ci struct iommu_domain domain; /* generic domain data structure */ 2538c2ecf20Sopenharmony_ci}; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci/* 2568c2ecf20Sopenharmony_ci * This structure hold all data of a single SYSMMU controller, this includes 2578c2ecf20Sopenharmony_ci * hw resources like registers and clocks, pointers and list nodes to connect 2588c2ecf20Sopenharmony_ci * it to all other structures, internal state and parameters read from device 2598c2ecf20Sopenharmony_ci * tree. It is usually referenced by 'data' pointer. 2608c2ecf20Sopenharmony_ci */ 2618c2ecf20Sopenharmony_cistruct sysmmu_drvdata { 2628c2ecf20Sopenharmony_ci struct device *sysmmu; /* SYSMMU controller device */ 2638c2ecf20Sopenharmony_ci struct device *master; /* master device (owner) */ 2648c2ecf20Sopenharmony_ci struct device_link *link; /* runtime PM link to master */ 2658c2ecf20Sopenharmony_ci void __iomem *sfrbase; /* our registers */ 2668c2ecf20Sopenharmony_ci struct clk *clk; /* SYSMMU's clock */ 2678c2ecf20Sopenharmony_ci struct clk *aclk; /* SYSMMU's aclk clock */ 2688c2ecf20Sopenharmony_ci struct clk *pclk; /* SYSMMU's pclk clock */ 2698c2ecf20Sopenharmony_ci struct clk *clk_master; /* master's device clock */ 2708c2ecf20Sopenharmony_ci spinlock_t lock; /* lock for modyfying state */ 2718c2ecf20Sopenharmony_ci bool active; /* current status */ 2728c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain; /* domain we belong to */ 2738c2ecf20Sopenharmony_ci struct list_head domain_node; /* node for domain clients list */ 2748c2ecf20Sopenharmony_ci struct list_head owner_node; /* node for owner controllers list */ 2758c2ecf20Sopenharmony_ci phys_addr_t pgtable; /* assigned page table structure */ 2768c2ecf20Sopenharmony_ci unsigned int version; /* our version */ 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci struct iommu_device iommu; /* IOMMU core handle */ 2798c2ecf20Sopenharmony_ci}; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_cistatic struct exynos_iommu_domain *to_exynos_domain(struct iommu_domain *dom) 2828c2ecf20Sopenharmony_ci{ 2838c2ecf20Sopenharmony_ci return container_of(dom, struct exynos_iommu_domain, domain); 2848c2ecf20Sopenharmony_ci} 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_cistatic void sysmmu_unblock(struct sysmmu_drvdata *data) 2878c2ecf20Sopenharmony_ci{ 2888c2ecf20Sopenharmony_ci writel(CTRL_ENABLE, data->sfrbase + REG_MMU_CTRL); 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic bool sysmmu_block(struct sysmmu_drvdata *data) 2928c2ecf20Sopenharmony_ci{ 2938c2ecf20Sopenharmony_ci int i = 120; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci writel(CTRL_BLOCK, data->sfrbase + REG_MMU_CTRL); 2968c2ecf20Sopenharmony_ci while ((i > 0) && !(readl(data->sfrbase + REG_MMU_STATUS) & 1)) 2978c2ecf20Sopenharmony_ci --i; 2988c2ecf20Sopenharmony_ci 2998c2ecf20Sopenharmony_ci if (!(readl(data->sfrbase + REG_MMU_STATUS) & 1)) { 3008c2ecf20Sopenharmony_ci sysmmu_unblock(data); 3018c2ecf20Sopenharmony_ci return false; 3028c2ecf20Sopenharmony_ci } 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci return true; 3058c2ecf20Sopenharmony_ci} 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_cistatic void __sysmmu_tlb_invalidate(struct sysmmu_drvdata *data) 3088c2ecf20Sopenharmony_ci{ 3098c2ecf20Sopenharmony_ci if (MMU_MAJ_VER(data->version) < 5) 3108c2ecf20Sopenharmony_ci writel(0x1, data->sfrbase + REG_MMU_FLUSH); 3118c2ecf20Sopenharmony_ci else 3128c2ecf20Sopenharmony_ci writel(0x1, data->sfrbase + REG_V5_MMU_FLUSH_ALL); 3138c2ecf20Sopenharmony_ci} 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_cistatic void __sysmmu_tlb_invalidate_entry(struct sysmmu_drvdata *data, 3168c2ecf20Sopenharmony_ci sysmmu_iova_t iova, unsigned int num_inv) 3178c2ecf20Sopenharmony_ci{ 3188c2ecf20Sopenharmony_ci unsigned int i; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci if (MMU_MAJ_VER(data->version) < 5) { 3218c2ecf20Sopenharmony_ci for (i = 0; i < num_inv; i++) { 3228c2ecf20Sopenharmony_ci writel((iova & SPAGE_MASK) | 1, 3238c2ecf20Sopenharmony_ci data->sfrbase + REG_MMU_FLUSH_ENTRY); 3248c2ecf20Sopenharmony_ci iova += SPAGE_SIZE; 3258c2ecf20Sopenharmony_ci } 3268c2ecf20Sopenharmony_ci } else { 3278c2ecf20Sopenharmony_ci if (num_inv == 1) { 3288c2ecf20Sopenharmony_ci writel((iova & SPAGE_MASK) | 1, 3298c2ecf20Sopenharmony_ci data->sfrbase + REG_V5_MMU_FLUSH_ENTRY); 3308c2ecf20Sopenharmony_ci } else { 3318c2ecf20Sopenharmony_ci writel((iova & SPAGE_MASK), 3328c2ecf20Sopenharmony_ci data->sfrbase + REG_V5_MMU_FLUSH_START); 3338c2ecf20Sopenharmony_ci writel((iova & SPAGE_MASK) + (num_inv - 1) * SPAGE_SIZE, 3348c2ecf20Sopenharmony_ci data->sfrbase + REG_V5_MMU_FLUSH_END); 3358c2ecf20Sopenharmony_ci writel(1, data->sfrbase + REG_V5_MMU_FLUSH_RANGE); 3368c2ecf20Sopenharmony_ci } 3378c2ecf20Sopenharmony_ci } 3388c2ecf20Sopenharmony_ci} 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_cistatic void __sysmmu_set_ptbase(struct sysmmu_drvdata *data, phys_addr_t pgd) 3418c2ecf20Sopenharmony_ci{ 3428c2ecf20Sopenharmony_ci if (MMU_MAJ_VER(data->version) < 5) 3438c2ecf20Sopenharmony_ci writel(pgd, data->sfrbase + REG_PT_BASE_ADDR); 3448c2ecf20Sopenharmony_ci else 3458c2ecf20Sopenharmony_ci writel(pgd >> PAGE_SHIFT, 3468c2ecf20Sopenharmony_ci data->sfrbase + REG_V5_PT_BASE_PFN); 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci __sysmmu_tlb_invalidate(data); 3498c2ecf20Sopenharmony_ci} 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_cistatic void __sysmmu_enable_clocks(struct sysmmu_drvdata *data) 3528c2ecf20Sopenharmony_ci{ 3538c2ecf20Sopenharmony_ci BUG_ON(clk_prepare_enable(data->clk_master)); 3548c2ecf20Sopenharmony_ci BUG_ON(clk_prepare_enable(data->clk)); 3558c2ecf20Sopenharmony_ci BUG_ON(clk_prepare_enable(data->pclk)); 3568c2ecf20Sopenharmony_ci BUG_ON(clk_prepare_enable(data->aclk)); 3578c2ecf20Sopenharmony_ci} 3588c2ecf20Sopenharmony_ci 3598c2ecf20Sopenharmony_cistatic void __sysmmu_disable_clocks(struct sysmmu_drvdata *data) 3608c2ecf20Sopenharmony_ci{ 3618c2ecf20Sopenharmony_ci clk_disable_unprepare(data->aclk); 3628c2ecf20Sopenharmony_ci clk_disable_unprepare(data->pclk); 3638c2ecf20Sopenharmony_ci clk_disable_unprepare(data->clk); 3648c2ecf20Sopenharmony_ci clk_disable_unprepare(data->clk_master); 3658c2ecf20Sopenharmony_ci} 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_cistatic void __sysmmu_get_version(struct sysmmu_drvdata *data) 3688c2ecf20Sopenharmony_ci{ 3698c2ecf20Sopenharmony_ci u32 ver; 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci __sysmmu_enable_clocks(data); 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_ci ver = readl(data->sfrbase + REG_MMU_VERSION); 3748c2ecf20Sopenharmony_ci 3758c2ecf20Sopenharmony_ci /* controllers on some SoCs don't report proper version */ 3768c2ecf20Sopenharmony_ci if (ver == 0x80000001u) 3778c2ecf20Sopenharmony_ci data->version = MAKE_MMU_VER(1, 0); 3788c2ecf20Sopenharmony_ci else 3798c2ecf20Sopenharmony_ci data->version = MMU_RAW_VER(ver); 3808c2ecf20Sopenharmony_ci 3818c2ecf20Sopenharmony_ci dev_dbg(data->sysmmu, "hardware version: %d.%d\n", 3828c2ecf20Sopenharmony_ci MMU_MAJ_VER(data->version), MMU_MIN_VER(data->version)); 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci __sysmmu_disable_clocks(data); 3858c2ecf20Sopenharmony_ci} 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_cistatic void show_fault_information(struct sysmmu_drvdata *data, 3888c2ecf20Sopenharmony_ci const struct sysmmu_fault_info *finfo, 3898c2ecf20Sopenharmony_ci sysmmu_iova_t fault_addr) 3908c2ecf20Sopenharmony_ci{ 3918c2ecf20Sopenharmony_ci sysmmu_pte_t *ent; 3928c2ecf20Sopenharmony_ci 3938c2ecf20Sopenharmony_ci dev_err(data->sysmmu, "%s: %s FAULT occurred at %#x\n", 3948c2ecf20Sopenharmony_ci dev_name(data->master), finfo->name, fault_addr); 3958c2ecf20Sopenharmony_ci dev_dbg(data->sysmmu, "Page table base: %pa\n", &data->pgtable); 3968c2ecf20Sopenharmony_ci ent = section_entry(phys_to_virt(data->pgtable), fault_addr); 3978c2ecf20Sopenharmony_ci dev_dbg(data->sysmmu, "\tLv1 entry: %#x\n", *ent); 3988c2ecf20Sopenharmony_ci if (lv1ent_page(ent)) { 3998c2ecf20Sopenharmony_ci ent = page_entry(ent, fault_addr); 4008c2ecf20Sopenharmony_ci dev_dbg(data->sysmmu, "\t Lv2 entry: %#x\n", *ent); 4018c2ecf20Sopenharmony_ci } 4028c2ecf20Sopenharmony_ci} 4038c2ecf20Sopenharmony_ci 4048c2ecf20Sopenharmony_cistatic irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id) 4058c2ecf20Sopenharmony_ci{ 4068c2ecf20Sopenharmony_ci /* SYSMMU is in blocked state when interrupt occurred. */ 4078c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data = dev_id; 4088c2ecf20Sopenharmony_ci const struct sysmmu_fault_info *finfo; 4098c2ecf20Sopenharmony_ci unsigned int i, n, itype; 4108c2ecf20Sopenharmony_ci sysmmu_iova_t fault_addr = -1; 4118c2ecf20Sopenharmony_ci unsigned short reg_status, reg_clear; 4128c2ecf20Sopenharmony_ci int ret = -ENOSYS; 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci WARN_ON(!data->active); 4158c2ecf20Sopenharmony_ci 4168c2ecf20Sopenharmony_ci if (MMU_MAJ_VER(data->version) < 5) { 4178c2ecf20Sopenharmony_ci reg_status = REG_INT_STATUS; 4188c2ecf20Sopenharmony_ci reg_clear = REG_INT_CLEAR; 4198c2ecf20Sopenharmony_ci finfo = sysmmu_faults; 4208c2ecf20Sopenharmony_ci n = ARRAY_SIZE(sysmmu_faults); 4218c2ecf20Sopenharmony_ci } else { 4228c2ecf20Sopenharmony_ci reg_status = REG_V5_INT_STATUS; 4238c2ecf20Sopenharmony_ci reg_clear = REG_V5_INT_CLEAR; 4248c2ecf20Sopenharmony_ci finfo = sysmmu_v5_faults; 4258c2ecf20Sopenharmony_ci n = ARRAY_SIZE(sysmmu_v5_faults); 4268c2ecf20Sopenharmony_ci } 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci spin_lock(&data->lock); 4298c2ecf20Sopenharmony_ci 4308c2ecf20Sopenharmony_ci clk_enable(data->clk_master); 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_ci itype = __ffs(readl(data->sfrbase + reg_status)); 4338c2ecf20Sopenharmony_ci for (i = 0; i < n; i++, finfo++) 4348c2ecf20Sopenharmony_ci if (finfo->bit == itype) 4358c2ecf20Sopenharmony_ci break; 4368c2ecf20Sopenharmony_ci /* unknown/unsupported fault */ 4378c2ecf20Sopenharmony_ci BUG_ON(i == n); 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci /* print debug message */ 4408c2ecf20Sopenharmony_ci fault_addr = readl(data->sfrbase + finfo->addr_reg); 4418c2ecf20Sopenharmony_ci show_fault_information(data, finfo, fault_addr); 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_ci if (data->domain) 4448c2ecf20Sopenharmony_ci ret = report_iommu_fault(&data->domain->domain, 4458c2ecf20Sopenharmony_ci data->master, fault_addr, finfo->type); 4468c2ecf20Sopenharmony_ci /* fault is not recovered by fault handler */ 4478c2ecf20Sopenharmony_ci BUG_ON(ret != 0); 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci writel(1 << itype, data->sfrbase + reg_clear); 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_ci sysmmu_unblock(data); 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci clk_disable(data->clk_master); 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci spin_unlock(&data->lock); 4568c2ecf20Sopenharmony_ci 4578c2ecf20Sopenharmony_ci return IRQ_HANDLED; 4588c2ecf20Sopenharmony_ci} 4598c2ecf20Sopenharmony_ci 4608c2ecf20Sopenharmony_cistatic void __sysmmu_disable(struct sysmmu_drvdata *data) 4618c2ecf20Sopenharmony_ci{ 4628c2ecf20Sopenharmony_ci unsigned long flags; 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci clk_enable(data->clk_master); 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 4678c2ecf20Sopenharmony_ci writel(CTRL_DISABLE, data->sfrbase + REG_MMU_CTRL); 4688c2ecf20Sopenharmony_ci writel(0, data->sfrbase + REG_MMU_CFG); 4698c2ecf20Sopenharmony_ci data->active = false; 4708c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 4718c2ecf20Sopenharmony_ci 4728c2ecf20Sopenharmony_ci __sysmmu_disable_clocks(data); 4738c2ecf20Sopenharmony_ci} 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_cistatic void __sysmmu_init_config(struct sysmmu_drvdata *data) 4768c2ecf20Sopenharmony_ci{ 4778c2ecf20Sopenharmony_ci unsigned int cfg; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci if (data->version <= MAKE_MMU_VER(3, 1)) 4808c2ecf20Sopenharmony_ci cfg = CFG_LRU | CFG_QOS(15); 4818c2ecf20Sopenharmony_ci else if (data->version <= MAKE_MMU_VER(3, 2)) 4828c2ecf20Sopenharmony_ci cfg = CFG_LRU | CFG_QOS(15) | CFG_FLPDCACHE | CFG_SYSSEL; 4838c2ecf20Sopenharmony_ci else 4848c2ecf20Sopenharmony_ci cfg = CFG_QOS(15) | CFG_FLPDCACHE | CFG_ACGEN; 4858c2ecf20Sopenharmony_ci 4868c2ecf20Sopenharmony_ci cfg |= CFG_EAP; /* enable access protection bits check */ 4878c2ecf20Sopenharmony_ci 4888c2ecf20Sopenharmony_ci writel(cfg, data->sfrbase + REG_MMU_CFG); 4898c2ecf20Sopenharmony_ci} 4908c2ecf20Sopenharmony_ci 4918c2ecf20Sopenharmony_cistatic void __sysmmu_enable(struct sysmmu_drvdata *data) 4928c2ecf20Sopenharmony_ci{ 4938c2ecf20Sopenharmony_ci unsigned long flags; 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci __sysmmu_enable_clocks(data); 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 4988c2ecf20Sopenharmony_ci writel(CTRL_BLOCK, data->sfrbase + REG_MMU_CTRL); 4998c2ecf20Sopenharmony_ci __sysmmu_init_config(data); 5008c2ecf20Sopenharmony_ci __sysmmu_set_ptbase(data, data->pgtable); 5018c2ecf20Sopenharmony_ci writel(CTRL_ENABLE, data->sfrbase + REG_MMU_CTRL); 5028c2ecf20Sopenharmony_ci data->active = true; 5038c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 5048c2ecf20Sopenharmony_ci 5058c2ecf20Sopenharmony_ci /* 5068c2ecf20Sopenharmony_ci * SYSMMU driver keeps master's clock enabled only for the short 5078c2ecf20Sopenharmony_ci * time, while accessing the registers. For performing address 5088c2ecf20Sopenharmony_ci * translation during DMA transaction it relies on the client 5098c2ecf20Sopenharmony_ci * driver to enable it. 5108c2ecf20Sopenharmony_ci */ 5118c2ecf20Sopenharmony_ci clk_disable(data->clk_master); 5128c2ecf20Sopenharmony_ci} 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_cistatic void sysmmu_tlb_invalidate_flpdcache(struct sysmmu_drvdata *data, 5158c2ecf20Sopenharmony_ci sysmmu_iova_t iova) 5168c2ecf20Sopenharmony_ci{ 5178c2ecf20Sopenharmony_ci unsigned long flags; 5188c2ecf20Sopenharmony_ci 5198c2ecf20Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 5208c2ecf20Sopenharmony_ci if (data->active && data->version >= MAKE_MMU_VER(3, 3)) { 5218c2ecf20Sopenharmony_ci clk_enable(data->clk_master); 5228c2ecf20Sopenharmony_ci if (sysmmu_block(data)) { 5238c2ecf20Sopenharmony_ci if (data->version >= MAKE_MMU_VER(5, 0)) 5248c2ecf20Sopenharmony_ci __sysmmu_tlb_invalidate(data); 5258c2ecf20Sopenharmony_ci else 5268c2ecf20Sopenharmony_ci __sysmmu_tlb_invalidate_entry(data, iova, 1); 5278c2ecf20Sopenharmony_ci sysmmu_unblock(data); 5288c2ecf20Sopenharmony_ci } 5298c2ecf20Sopenharmony_ci clk_disable(data->clk_master); 5308c2ecf20Sopenharmony_ci } 5318c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 5328c2ecf20Sopenharmony_ci} 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_cistatic void sysmmu_tlb_invalidate_entry(struct sysmmu_drvdata *data, 5358c2ecf20Sopenharmony_ci sysmmu_iova_t iova, size_t size) 5368c2ecf20Sopenharmony_ci{ 5378c2ecf20Sopenharmony_ci unsigned long flags; 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci spin_lock_irqsave(&data->lock, flags); 5408c2ecf20Sopenharmony_ci if (data->active) { 5418c2ecf20Sopenharmony_ci unsigned int num_inv = 1; 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci clk_enable(data->clk_master); 5448c2ecf20Sopenharmony_ci 5458c2ecf20Sopenharmony_ci /* 5468c2ecf20Sopenharmony_ci * L2TLB invalidation required 5478c2ecf20Sopenharmony_ci * 4KB page: 1 invalidation 5488c2ecf20Sopenharmony_ci * 64KB page: 16 invalidations 5498c2ecf20Sopenharmony_ci * 1MB page: 64 invalidations 5508c2ecf20Sopenharmony_ci * because it is set-associative TLB 5518c2ecf20Sopenharmony_ci * with 8-way and 64 sets. 5528c2ecf20Sopenharmony_ci * 1MB page can be cached in one of all sets. 5538c2ecf20Sopenharmony_ci * 64KB page can be one of 16 consecutive sets. 5548c2ecf20Sopenharmony_ci */ 5558c2ecf20Sopenharmony_ci if (MMU_MAJ_VER(data->version) == 2) 5568c2ecf20Sopenharmony_ci num_inv = min_t(unsigned int, size / PAGE_SIZE, 64); 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci if (sysmmu_block(data)) { 5598c2ecf20Sopenharmony_ci __sysmmu_tlb_invalidate_entry(data, iova, num_inv); 5608c2ecf20Sopenharmony_ci sysmmu_unblock(data); 5618c2ecf20Sopenharmony_ci } 5628c2ecf20Sopenharmony_ci clk_disable(data->clk_master); 5638c2ecf20Sopenharmony_ci } 5648c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&data->lock, flags); 5658c2ecf20Sopenharmony_ci} 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_cistatic const struct iommu_ops exynos_iommu_ops; 5688c2ecf20Sopenharmony_ci 5698c2ecf20Sopenharmony_cistatic int exynos_sysmmu_probe(struct platform_device *pdev) 5708c2ecf20Sopenharmony_ci{ 5718c2ecf20Sopenharmony_ci int irq, ret; 5728c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 5738c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data; 5748c2ecf20Sopenharmony_ci struct resource *res; 5758c2ecf20Sopenharmony_ci 5768c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 5778c2ecf20Sopenharmony_ci if (!data) 5788c2ecf20Sopenharmony_ci return -ENOMEM; 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 5818c2ecf20Sopenharmony_ci data->sfrbase = devm_ioremap_resource(dev, res); 5828c2ecf20Sopenharmony_ci if (IS_ERR(data->sfrbase)) 5838c2ecf20Sopenharmony_ci return PTR_ERR(data->sfrbase); 5848c2ecf20Sopenharmony_ci 5858c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 5868c2ecf20Sopenharmony_ci if (irq <= 0) 5878c2ecf20Sopenharmony_ci return irq; 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ci ret = devm_request_irq(dev, irq, exynos_sysmmu_irq, 0, 5908c2ecf20Sopenharmony_ci dev_name(dev), data); 5918c2ecf20Sopenharmony_ci if (ret) { 5928c2ecf20Sopenharmony_ci dev_err(dev, "Unabled to register handler of irq %d\n", irq); 5938c2ecf20Sopenharmony_ci return ret; 5948c2ecf20Sopenharmony_ci } 5958c2ecf20Sopenharmony_ci 5968c2ecf20Sopenharmony_ci data->clk = devm_clk_get(dev, "sysmmu"); 5978c2ecf20Sopenharmony_ci if (PTR_ERR(data->clk) == -ENOENT) 5988c2ecf20Sopenharmony_ci data->clk = NULL; 5998c2ecf20Sopenharmony_ci else if (IS_ERR(data->clk)) 6008c2ecf20Sopenharmony_ci return PTR_ERR(data->clk); 6018c2ecf20Sopenharmony_ci 6028c2ecf20Sopenharmony_ci data->aclk = devm_clk_get(dev, "aclk"); 6038c2ecf20Sopenharmony_ci if (PTR_ERR(data->aclk) == -ENOENT) 6048c2ecf20Sopenharmony_ci data->aclk = NULL; 6058c2ecf20Sopenharmony_ci else if (IS_ERR(data->aclk)) 6068c2ecf20Sopenharmony_ci return PTR_ERR(data->aclk); 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_ci data->pclk = devm_clk_get(dev, "pclk"); 6098c2ecf20Sopenharmony_ci if (PTR_ERR(data->pclk) == -ENOENT) 6108c2ecf20Sopenharmony_ci data->pclk = NULL; 6118c2ecf20Sopenharmony_ci else if (IS_ERR(data->pclk)) 6128c2ecf20Sopenharmony_ci return PTR_ERR(data->pclk); 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci if (!data->clk && (!data->aclk || !data->pclk)) { 6158c2ecf20Sopenharmony_ci dev_err(dev, "Failed to get device clock(s)!\n"); 6168c2ecf20Sopenharmony_ci return -ENOSYS; 6178c2ecf20Sopenharmony_ci } 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_ci data->clk_master = devm_clk_get(dev, "master"); 6208c2ecf20Sopenharmony_ci if (PTR_ERR(data->clk_master) == -ENOENT) 6218c2ecf20Sopenharmony_ci data->clk_master = NULL; 6228c2ecf20Sopenharmony_ci else if (IS_ERR(data->clk_master)) 6238c2ecf20Sopenharmony_ci return PTR_ERR(data->clk_master); 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci data->sysmmu = dev; 6268c2ecf20Sopenharmony_ci spin_lock_init(&data->lock); 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_ci ret = iommu_device_sysfs_add(&data->iommu, &pdev->dev, NULL, 6298c2ecf20Sopenharmony_ci dev_name(data->sysmmu)); 6308c2ecf20Sopenharmony_ci if (ret) 6318c2ecf20Sopenharmony_ci return ret; 6328c2ecf20Sopenharmony_ci 6338c2ecf20Sopenharmony_ci iommu_device_set_ops(&data->iommu, &exynos_iommu_ops); 6348c2ecf20Sopenharmony_ci iommu_device_set_fwnode(&data->iommu, &dev->of_node->fwnode); 6358c2ecf20Sopenharmony_ci 6368c2ecf20Sopenharmony_ci ret = iommu_device_register(&data->iommu); 6378c2ecf20Sopenharmony_ci if (ret) 6388c2ecf20Sopenharmony_ci goto err_iommu_register; 6398c2ecf20Sopenharmony_ci 6408c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 6418c2ecf20Sopenharmony_ci 6428c2ecf20Sopenharmony_ci __sysmmu_get_version(data); 6438c2ecf20Sopenharmony_ci if (PG_ENT_SHIFT < 0) { 6448c2ecf20Sopenharmony_ci if (MMU_MAJ_VER(data->version) < 5) { 6458c2ecf20Sopenharmony_ci PG_ENT_SHIFT = SYSMMU_PG_ENT_SHIFT; 6468c2ecf20Sopenharmony_ci LV1_PROT = SYSMMU_LV1_PROT; 6478c2ecf20Sopenharmony_ci LV2_PROT = SYSMMU_LV2_PROT; 6488c2ecf20Sopenharmony_ci } else { 6498c2ecf20Sopenharmony_ci PG_ENT_SHIFT = SYSMMU_V5_PG_ENT_SHIFT; 6508c2ecf20Sopenharmony_ci LV1_PROT = SYSMMU_V5_LV1_PROT; 6518c2ecf20Sopenharmony_ci LV2_PROT = SYSMMU_V5_LV2_PROT; 6528c2ecf20Sopenharmony_ci } 6538c2ecf20Sopenharmony_ci } 6548c2ecf20Sopenharmony_ci 6558c2ecf20Sopenharmony_ci /* 6568c2ecf20Sopenharmony_ci * use the first registered sysmmu device for performing 6578c2ecf20Sopenharmony_ci * dma mapping operations on iommu page tables (cpu cache flush) 6588c2ecf20Sopenharmony_ci */ 6598c2ecf20Sopenharmony_ci if (!dma_dev) 6608c2ecf20Sopenharmony_ci dma_dev = &pdev->dev; 6618c2ecf20Sopenharmony_ci 6628c2ecf20Sopenharmony_ci pm_runtime_enable(dev); 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_ci return 0; 6658c2ecf20Sopenharmony_ci 6668c2ecf20Sopenharmony_cierr_iommu_register: 6678c2ecf20Sopenharmony_ci iommu_device_sysfs_remove(&data->iommu); 6688c2ecf20Sopenharmony_ci return ret; 6698c2ecf20Sopenharmony_ci} 6708c2ecf20Sopenharmony_ci 6718c2ecf20Sopenharmony_cistatic int __maybe_unused exynos_sysmmu_suspend(struct device *dev) 6728c2ecf20Sopenharmony_ci{ 6738c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data = dev_get_drvdata(dev); 6748c2ecf20Sopenharmony_ci struct device *master = data->master; 6758c2ecf20Sopenharmony_ci 6768c2ecf20Sopenharmony_ci if (master) { 6778c2ecf20Sopenharmony_ci struct exynos_iommu_owner *owner = dev_iommu_priv_get(master); 6788c2ecf20Sopenharmony_ci 6798c2ecf20Sopenharmony_ci mutex_lock(&owner->rpm_lock); 6808c2ecf20Sopenharmony_ci if (data->domain) { 6818c2ecf20Sopenharmony_ci dev_dbg(data->sysmmu, "saving state\n"); 6828c2ecf20Sopenharmony_ci __sysmmu_disable(data); 6838c2ecf20Sopenharmony_ci } 6848c2ecf20Sopenharmony_ci mutex_unlock(&owner->rpm_lock); 6858c2ecf20Sopenharmony_ci } 6868c2ecf20Sopenharmony_ci return 0; 6878c2ecf20Sopenharmony_ci} 6888c2ecf20Sopenharmony_ci 6898c2ecf20Sopenharmony_cistatic int __maybe_unused exynos_sysmmu_resume(struct device *dev) 6908c2ecf20Sopenharmony_ci{ 6918c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data = dev_get_drvdata(dev); 6928c2ecf20Sopenharmony_ci struct device *master = data->master; 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_ci if (master) { 6958c2ecf20Sopenharmony_ci struct exynos_iommu_owner *owner = dev_iommu_priv_get(master); 6968c2ecf20Sopenharmony_ci 6978c2ecf20Sopenharmony_ci mutex_lock(&owner->rpm_lock); 6988c2ecf20Sopenharmony_ci if (data->domain) { 6998c2ecf20Sopenharmony_ci dev_dbg(data->sysmmu, "restoring state\n"); 7008c2ecf20Sopenharmony_ci __sysmmu_enable(data); 7018c2ecf20Sopenharmony_ci } 7028c2ecf20Sopenharmony_ci mutex_unlock(&owner->rpm_lock); 7038c2ecf20Sopenharmony_ci } 7048c2ecf20Sopenharmony_ci return 0; 7058c2ecf20Sopenharmony_ci} 7068c2ecf20Sopenharmony_ci 7078c2ecf20Sopenharmony_cistatic const struct dev_pm_ops sysmmu_pm_ops = { 7088c2ecf20Sopenharmony_ci SET_RUNTIME_PM_OPS(exynos_sysmmu_suspend, exynos_sysmmu_resume, NULL) 7098c2ecf20Sopenharmony_ci SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 7108c2ecf20Sopenharmony_ci pm_runtime_force_resume) 7118c2ecf20Sopenharmony_ci}; 7128c2ecf20Sopenharmony_ci 7138c2ecf20Sopenharmony_cistatic const struct of_device_id sysmmu_of_match[] = { 7148c2ecf20Sopenharmony_ci { .compatible = "samsung,exynos-sysmmu", }, 7158c2ecf20Sopenharmony_ci { }, 7168c2ecf20Sopenharmony_ci}; 7178c2ecf20Sopenharmony_ci 7188c2ecf20Sopenharmony_cistatic struct platform_driver exynos_sysmmu_driver __refdata = { 7198c2ecf20Sopenharmony_ci .probe = exynos_sysmmu_probe, 7208c2ecf20Sopenharmony_ci .driver = { 7218c2ecf20Sopenharmony_ci .name = "exynos-sysmmu", 7228c2ecf20Sopenharmony_ci .of_match_table = sysmmu_of_match, 7238c2ecf20Sopenharmony_ci .pm = &sysmmu_pm_ops, 7248c2ecf20Sopenharmony_ci .suppress_bind_attrs = true, 7258c2ecf20Sopenharmony_ci } 7268c2ecf20Sopenharmony_ci}; 7278c2ecf20Sopenharmony_ci 7288c2ecf20Sopenharmony_cistatic inline void exynos_iommu_set_pte(sysmmu_pte_t *ent, sysmmu_pte_t val) 7298c2ecf20Sopenharmony_ci{ 7308c2ecf20Sopenharmony_ci dma_sync_single_for_cpu(dma_dev, virt_to_phys(ent), sizeof(*ent), 7318c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 7328c2ecf20Sopenharmony_ci *ent = cpu_to_le32(val); 7338c2ecf20Sopenharmony_ci dma_sync_single_for_device(dma_dev, virt_to_phys(ent), sizeof(*ent), 7348c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 7358c2ecf20Sopenharmony_ci} 7368c2ecf20Sopenharmony_ci 7378c2ecf20Sopenharmony_cistatic struct iommu_domain *exynos_iommu_domain_alloc(unsigned type) 7388c2ecf20Sopenharmony_ci{ 7398c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain; 7408c2ecf20Sopenharmony_ci dma_addr_t handle; 7418c2ecf20Sopenharmony_ci int i; 7428c2ecf20Sopenharmony_ci 7438c2ecf20Sopenharmony_ci /* Check if correct PTE offsets are initialized */ 7448c2ecf20Sopenharmony_ci BUG_ON(PG_ENT_SHIFT < 0 || !dma_dev); 7458c2ecf20Sopenharmony_ci 7468c2ecf20Sopenharmony_ci domain = kzalloc(sizeof(*domain), GFP_KERNEL); 7478c2ecf20Sopenharmony_ci if (!domain) 7488c2ecf20Sopenharmony_ci return NULL; 7498c2ecf20Sopenharmony_ci 7508c2ecf20Sopenharmony_ci if (type == IOMMU_DOMAIN_DMA) { 7518c2ecf20Sopenharmony_ci if (iommu_get_dma_cookie(&domain->domain) != 0) 7528c2ecf20Sopenharmony_ci goto err_pgtable; 7538c2ecf20Sopenharmony_ci } else if (type != IOMMU_DOMAIN_UNMANAGED) { 7548c2ecf20Sopenharmony_ci goto err_pgtable; 7558c2ecf20Sopenharmony_ci } 7568c2ecf20Sopenharmony_ci 7578c2ecf20Sopenharmony_ci domain->pgtable = (sysmmu_pte_t *)__get_free_pages(GFP_KERNEL, 2); 7588c2ecf20Sopenharmony_ci if (!domain->pgtable) 7598c2ecf20Sopenharmony_ci goto err_dma_cookie; 7608c2ecf20Sopenharmony_ci 7618c2ecf20Sopenharmony_ci domain->lv2entcnt = (short *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 1); 7628c2ecf20Sopenharmony_ci if (!domain->lv2entcnt) 7638c2ecf20Sopenharmony_ci goto err_counter; 7648c2ecf20Sopenharmony_ci 7658c2ecf20Sopenharmony_ci /* Workaround for System MMU v3.3 to prevent caching 1MiB mapping */ 7668c2ecf20Sopenharmony_ci for (i = 0; i < NUM_LV1ENTRIES; i++) 7678c2ecf20Sopenharmony_ci domain->pgtable[i] = ZERO_LV2LINK; 7688c2ecf20Sopenharmony_ci 7698c2ecf20Sopenharmony_ci handle = dma_map_single(dma_dev, domain->pgtable, LV1TABLE_SIZE, 7708c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 7718c2ecf20Sopenharmony_ci /* For mapping page table entries we rely on dma == phys */ 7728c2ecf20Sopenharmony_ci BUG_ON(handle != virt_to_phys(domain->pgtable)); 7738c2ecf20Sopenharmony_ci if (dma_mapping_error(dma_dev, handle)) 7748c2ecf20Sopenharmony_ci goto err_lv2ent; 7758c2ecf20Sopenharmony_ci 7768c2ecf20Sopenharmony_ci spin_lock_init(&domain->lock); 7778c2ecf20Sopenharmony_ci spin_lock_init(&domain->pgtablelock); 7788c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&domain->clients); 7798c2ecf20Sopenharmony_ci 7808c2ecf20Sopenharmony_ci domain->domain.geometry.aperture_start = 0; 7818c2ecf20Sopenharmony_ci domain->domain.geometry.aperture_end = ~0UL; 7828c2ecf20Sopenharmony_ci domain->domain.geometry.force_aperture = true; 7838c2ecf20Sopenharmony_ci 7848c2ecf20Sopenharmony_ci return &domain->domain; 7858c2ecf20Sopenharmony_ci 7868c2ecf20Sopenharmony_cierr_lv2ent: 7878c2ecf20Sopenharmony_ci free_pages((unsigned long)domain->lv2entcnt, 1); 7888c2ecf20Sopenharmony_cierr_counter: 7898c2ecf20Sopenharmony_ci free_pages((unsigned long)domain->pgtable, 2); 7908c2ecf20Sopenharmony_cierr_dma_cookie: 7918c2ecf20Sopenharmony_ci if (type == IOMMU_DOMAIN_DMA) 7928c2ecf20Sopenharmony_ci iommu_put_dma_cookie(&domain->domain); 7938c2ecf20Sopenharmony_cierr_pgtable: 7948c2ecf20Sopenharmony_ci kfree(domain); 7958c2ecf20Sopenharmony_ci return NULL; 7968c2ecf20Sopenharmony_ci} 7978c2ecf20Sopenharmony_ci 7988c2ecf20Sopenharmony_cistatic void exynos_iommu_domain_free(struct iommu_domain *iommu_domain) 7998c2ecf20Sopenharmony_ci{ 8008c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain); 8018c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data, *next; 8028c2ecf20Sopenharmony_ci unsigned long flags; 8038c2ecf20Sopenharmony_ci int i; 8048c2ecf20Sopenharmony_ci 8058c2ecf20Sopenharmony_ci WARN_ON(!list_empty(&domain->clients)); 8068c2ecf20Sopenharmony_ci 8078c2ecf20Sopenharmony_ci spin_lock_irqsave(&domain->lock, flags); 8088c2ecf20Sopenharmony_ci 8098c2ecf20Sopenharmony_ci list_for_each_entry_safe(data, next, &domain->clients, domain_node) { 8108c2ecf20Sopenharmony_ci spin_lock(&data->lock); 8118c2ecf20Sopenharmony_ci __sysmmu_disable(data); 8128c2ecf20Sopenharmony_ci data->pgtable = 0; 8138c2ecf20Sopenharmony_ci data->domain = NULL; 8148c2ecf20Sopenharmony_ci list_del_init(&data->domain_node); 8158c2ecf20Sopenharmony_ci spin_unlock(&data->lock); 8168c2ecf20Sopenharmony_ci } 8178c2ecf20Sopenharmony_ci 8188c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->lock, flags); 8198c2ecf20Sopenharmony_ci 8208c2ecf20Sopenharmony_ci if (iommu_domain->type == IOMMU_DOMAIN_DMA) 8218c2ecf20Sopenharmony_ci iommu_put_dma_cookie(iommu_domain); 8228c2ecf20Sopenharmony_ci 8238c2ecf20Sopenharmony_ci dma_unmap_single(dma_dev, virt_to_phys(domain->pgtable), LV1TABLE_SIZE, 8248c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 8258c2ecf20Sopenharmony_ci 8268c2ecf20Sopenharmony_ci for (i = 0; i < NUM_LV1ENTRIES; i++) 8278c2ecf20Sopenharmony_ci if (lv1ent_page(domain->pgtable + i)) { 8288c2ecf20Sopenharmony_ci phys_addr_t base = lv2table_base(domain->pgtable + i); 8298c2ecf20Sopenharmony_ci 8308c2ecf20Sopenharmony_ci dma_unmap_single(dma_dev, base, LV2TABLE_SIZE, 8318c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 8328c2ecf20Sopenharmony_ci kmem_cache_free(lv2table_kmem_cache, 8338c2ecf20Sopenharmony_ci phys_to_virt(base)); 8348c2ecf20Sopenharmony_ci } 8358c2ecf20Sopenharmony_ci 8368c2ecf20Sopenharmony_ci free_pages((unsigned long)domain->pgtable, 2); 8378c2ecf20Sopenharmony_ci free_pages((unsigned long)domain->lv2entcnt, 1); 8388c2ecf20Sopenharmony_ci kfree(domain); 8398c2ecf20Sopenharmony_ci} 8408c2ecf20Sopenharmony_ci 8418c2ecf20Sopenharmony_cistatic void exynos_iommu_detach_device(struct iommu_domain *iommu_domain, 8428c2ecf20Sopenharmony_ci struct device *dev) 8438c2ecf20Sopenharmony_ci{ 8448c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain); 8458c2ecf20Sopenharmony_ci struct exynos_iommu_owner *owner = dev_iommu_priv_get(dev); 8468c2ecf20Sopenharmony_ci phys_addr_t pagetable = virt_to_phys(domain->pgtable); 8478c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data, *next; 8488c2ecf20Sopenharmony_ci unsigned long flags; 8498c2ecf20Sopenharmony_ci 8508c2ecf20Sopenharmony_ci if (!has_sysmmu(dev) || owner->domain != iommu_domain) 8518c2ecf20Sopenharmony_ci return; 8528c2ecf20Sopenharmony_ci 8538c2ecf20Sopenharmony_ci mutex_lock(&owner->rpm_lock); 8548c2ecf20Sopenharmony_ci 8558c2ecf20Sopenharmony_ci list_for_each_entry(data, &owner->controllers, owner_node) { 8568c2ecf20Sopenharmony_ci pm_runtime_get_noresume(data->sysmmu); 8578c2ecf20Sopenharmony_ci if (pm_runtime_active(data->sysmmu)) 8588c2ecf20Sopenharmony_ci __sysmmu_disable(data); 8598c2ecf20Sopenharmony_ci pm_runtime_put(data->sysmmu); 8608c2ecf20Sopenharmony_ci } 8618c2ecf20Sopenharmony_ci 8628c2ecf20Sopenharmony_ci spin_lock_irqsave(&domain->lock, flags); 8638c2ecf20Sopenharmony_ci list_for_each_entry_safe(data, next, &domain->clients, domain_node) { 8648c2ecf20Sopenharmony_ci spin_lock(&data->lock); 8658c2ecf20Sopenharmony_ci data->pgtable = 0; 8668c2ecf20Sopenharmony_ci data->domain = NULL; 8678c2ecf20Sopenharmony_ci list_del_init(&data->domain_node); 8688c2ecf20Sopenharmony_ci spin_unlock(&data->lock); 8698c2ecf20Sopenharmony_ci } 8708c2ecf20Sopenharmony_ci owner->domain = NULL; 8718c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->lock, flags); 8728c2ecf20Sopenharmony_ci 8738c2ecf20Sopenharmony_ci mutex_unlock(&owner->rpm_lock); 8748c2ecf20Sopenharmony_ci 8758c2ecf20Sopenharmony_ci dev_dbg(dev, "%s: Detached IOMMU with pgtable %pa\n", __func__, 8768c2ecf20Sopenharmony_ci &pagetable); 8778c2ecf20Sopenharmony_ci} 8788c2ecf20Sopenharmony_ci 8798c2ecf20Sopenharmony_cistatic int exynos_iommu_attach_device(struct iommu_domain *iommu_domain, 8808c2ecf20Sopenharmony_ci struct device *dev) 8818c2ecf20Sopenharmony_ci{ 8828c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain); 8838c2ecf20Sopenharmony_ci struct exynos_iommu_owner *owner = dev_iommu_priv_get(dev); 8848c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data; 8858c2ecf20Sopenharmony_ci phys_addr_t pagetable = virt_to_phys(domain->pgtable); 8868c2ecf20Sopenharmony_ci unsigned long flags; 8878c2ecf20Sopenharmony_ci 8888c2ecf20Sopenharmony_ci if (!has_sysmmu(dev)) 8898c2ecf20Sopenharmony_ci return -ENODEV; 8908c2ecf20Sopenharmony_ci 8918c2ecf20Sopenharmony_ci if (owner->domain) 8928c2ecf20Sopenharmony_ci exynos_iommu_detach_device(owner->domain, dev); 8938c2ecf20Sopenharmony_ci 8948c2ecf20Sopenharmony_ci mutex_lock(&owner->rpm_lock); 8958c2ecf20Sopenharmony_ci 8968c2ecf20Sopenharmony_ci spin_lock_irqsave(&domain->lock, flags); 8978c2ecf20Sopenharmony_ci list_for_each_entry(data, &owner->controllers, owner_node) { 8988c2ecf20Sopenharmony_ci spin_lock(&data->lock); 8998c2ecf20Sopenharmony_ci data->pgtable = pagetable; 9008c2ecf20Sopenharmony_ci data->domain = domain; 9018c2ecf20Sopenharmony_ci list_add_tail(&data->domain_node, &domain->clients); 9028c2ecf20Sopenharmony_ci spin_unlock(&data->lock); 9038c2ecf20Sopenharmony_ci } 9048c2ecf20Sopenharmony_ci owner->domain = iommu_domain; 9058c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->lock, flags); 9068c2ecf20Sopenharmony_ci 9078c2ecf20Sopenharmony_ci list_for_each_entry(data, &owner->controllers, owner_node) { 9088c2ecf20Sopenharmony_ci pm_runtime_get_noresume(data->sysmmu); 9098c2ecf20Sopenharmony_ci if (pm_runtime_active(data->sysmmu)) 9108c2ecf20Sopenharmony_ci __sysmmu_enable(data); 9118c2ecf20Sopenharmony_ci pm_runtime_put(data->sysmmu); 9128c2ecf20Sopenharmony_ci } 9138c2ecf20Sopenharmony_ci 9148c2ecf20Sopenharmony_ci mutex_unlock(&owner->rpm_lock); 9158c2ecf20Sopenharmony_ci 9168c2ecf20Sopenharmony_ci dev_dbg(dev, "%s: Attached IOMMU with pgtable %pa\n", __func__, 9178c2ecf20Sopenharmony_ci &pagetable); 9188c2ecf20Sopenharmony_ci 9198c2ecf20Sopenharmony_ci return 0; 9208c2ecf20Sopenharmony_ci} 9218c2ecf20Sopenharmony_ci 9228c2ecf20Sopenharmony_cistatic sysmmu_pte_t *alloc_lv2entry(struct exynos_iommu_domain *domain, 9238c2ecf20Sopenharmony_ci sysmmu_pte_t *sent, sysmmu_iova_t iova, short *pgcounter) 9248c2ecf20Sopenharmony_ci{ 9258c2ecf20Sopenharmony_ci if (lv1ent_section(sent)) { 9268c2ecf20Sopenharmony_ci WARN(1, "Trying mapping on %#08x mapped with 1MiB page", iova); 9278c2ecf20Sopenharmony_ci return ERR_PTR(-EADDRINUSE); 9288c2ecf20Sopenharmony_ci } 9298c2ecf20Sopenharmony_ci 9308c2ecf20Sopenharmony_ci if (lv1ent_fault(sent)) { 9318c2ecf20Sopenharmony_ci dma_addr_t handle; 9328c2ecf20Sopenharmony_ci sysmmu_pte_t *pent; 9338c2ecf20Sopenharmony_ci bool need_flush_flpd_cache = lv1ent_zero(sent); 9348c2ecf20Sopenharmony_ci 9358c2ecf20Sopenharmony_ci pent = kmem_cache_zalloc(lv2table_kmem_cache, GFP_ATOMIC); 9368c2ecf20Sopenharmony_ci BUG_ON((uintptr_t)pent & (LV2TABLE_SIZE - 1)); 9378c2ecf20Sopenharmony_ci if (!pent) 9388c2ecf20Sopenharmony_ci return ERR_PTR(-ENOMEM); 9398c2ecf20Sopenharmony_ci 9408c2ecf20Sopenharmony_ci exynos_iommu_set_pte(sent, mk_lv1ent_page(virt_to_phys(pent))); 9418c2ecf20Sopenharmony_ci kmemleak_ignore(pent); 9428c2ecf20Sopenharmony_ci *pgcounter = NUM_LV2ENTRIES; 9438c2ecf20Sopenharmony_ci handle = dma_map_single(dma_dev, pent, LV2TABLE_SIZE, 9448c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 9458c2ecf20Sopenharmony_ci if (dma_mapping_error(dma_dev, handle)) { 9468c2ecf20Sopenharmony_ci kmem_cache_free(lv2table_kmem_cache, pent); 9478c2ecf20Sopenharmony_ci return ERR_PTR(-EADDRINUSE); 9488c2ecf20Sopenharmony_ci } 9498c2ecf20Sopenharmony_ci 9508c2ecf20Sopenharmony_ci /* 9518c2ecf20Sopenharmony_ci * If pre-fetched SLPD is a faulty SLPD in zero_l2_table, 9528c2ecf20Sopenharmony_ci * FLPD cache may cache the address of zero_l2_table. This 9538c2ecf20Sopenharmony_ci * function replaces the zero_l2_table with new L2 page table 9548c2ecf20Sopenharmony_ci * to write valid mappings. 9558c2ecf20Sopenharmony_ci * Accessing the valid area may cause page fault since FLPD 9568c2ecf20Sopenharmony_ci * cache may still cache zero_l2_table for the valid area 9578c2ecf20Sopenharmony_ci * instead of new L2 page table that has the mapping 9588c2ecf20Sopenharmony_ci * information of the valid area. 9598c2ecf20Sopenharmony_ci * Thus any replacement of zero_l2_table with other valid L2 9608c2ecf20Sopenharmony_ci * page table must involve FLPD cache invalidation for System 9618c2ecf20Sopenharmony_ci * MMU v3.3. 9628c2ecf20Sopenharmony_ci * FLPD cache invalidation is performed with TLB invalidation 9638c2ecf20Sopenharmony_ci * by VPN without blocking. It is safe to invalidate TLB without 9648c2ecf20Sopenharmony_ci * blocking because the target address of TLB invalidation is 9658c2ecf20Sopenharmony_ci * not currently mapped. 9668c2ecf20Sopenharmony_ci */ 9678c2ecf20Sopenharmony_ci if (need_flush_flpd_cache) { 9688c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data; 9698c2ecf20Sopenharmony_ci 9708c2ecf20Sopenharmony_ci spin_lock(&domain->lock); 9718c2ecf20Sopenharmony_ci list_for_each_entry(data, &domain->clients, domain_node) 9728c2ecf20Sopenharmony_ci sysmmu_tlb_invalidate_flpdcache(data, iova); 9738c2ecf20Sopenharmony_ci spin_unlock(&domain->lock); 9748c2ecf20Sopenharmony_ci } 9758c2ecf20Sopenharmony_ci } 9768c2ecf20Sopenharmony_ci 9778c2ecf20Sopenharmony_ci return page_entry(sent, iova); 9788c2ecf20Sopenharmony_ci} 9798c2ecf20Sopenharmony_ci 9808c2ecf20Sopenharmony_cistatic int lv1set_section(struct exynos_iommu_domain *domain, 9818c2ecf20Sopenharmony_ci sysmmu_pte_t *sent, sysmmu_iova_t iova, 9828c2ecf20Sopenharmony_ci phys_addr_t paddr, int prot, short *pgcnt) 9838c2ecf20Sopenharmony_ci{ 9848c2ecf20Sopenharmony_ci if (lv1ent_section(sent)) { 9858c2ecf20Sopenharmony_ci WARN(1, "Trying mapping on 1MiB@%#08x that is mapped", 9868c2ecf20Sopenharmony_ci iova); 9878c2ecf20Sopenharmony_ci return -EADDRINUSE; 9888c2ecf20Sopenharmony_ci } 9898c2ecf20Sopenharmony_ci 9908c2ecf20Sopenharmony_ci if (lv1ent_page(sent)) { 9918c2ecf20Sopenharmony_ci if (*pgcnt != NUM_LV2ENTRIES) { 9928c2ecf20Sopenharmony_ci WARN(1, "Trying mapping on 1MiB@%#08x that is mapped", 9938c2ecf20Sopenharmony_ci iova); 9948c2ecf20Sopenharmony_ci return -EADDRINUSE; 9958c2ecf20Sopenharmony_ci } 9968c2ecf20Sopenharmony_ci 9978c2ecf20Sopenharmony_ci kmem_cache_free(lv2table_kmem_cache, page_entry(sent, 0)); 9988c2ecf20Sopenharmony_ci *pgcnt = 0; 9998c2ecf20Sopenharmony_ci } 10008c2ecf20Sopenharmony_ci 10018c2ecf20Sopenharmony_ci exynos_iommu_set_pte(sent, mk_lv1ent_sect(paddr, prot)); 10028c2ecf20Sopenharmony_ci 10038c2ecf20Sopenharmony_ci spin_lock(&domain->lock); 10048c2ecf20Sopenharmony_ci if (lv1ent_page_zero(sent)) { 10058c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data; 10068c2ecf20Sopenharmony_ci /* 10078c2ecf20Sopenharmony_ci * Flushing FLPD cache in System MMU v3.3 that may cache a FLPD 10088c2ecf20Sopenharmony_ci * entry by speculative prefetch of SLPD which has no mapping. 10098c2ecf20Sopenharmony_ci */ 10108c2ecf20Sopenharmony_ci list_for_each_entry(data, &domain->clients, domain_node) 10118c2ecf20Sopenharmony_ci sysmmu_tlb_invalidate_flpdcache(data, iova); 10128c2ecf20Sopenharmony_ci } 10138c2ecf20Sopenharmony_ci spin_unlock(&domain->lock); 10148c2ecf20Sopenharmony_ci 10158c2ecf20Sopenharmony_ci return 0; 10168c2ecf20Sopenharmony_ci} 10178c2ecf20Sopenharmony_ci 10188c2ecf20Sopenharmony_cistatic int lv2set_page(sysmmu_pte_t *pent, phys_addr_t paddr, size_t size, 10198c2ecf20Sopenharmony_ci int prot, short *pgcnt) 10208c2ecf20Sopenharmony_ci{ 10218c2ecf20Sopenharmony_ci if (size == SPAGE_SIZE) { 10228c2ecf20Sopenharmony_ci if (WARN_ON(!lv2ent_fault(pent))) 10238c2ecf20Sopenharmony_ci return -EADDRINUSE; 10248c2ecf20Sopenharmony_ci 10258c2ecf20Sopenharmony_ci exynos_iommu_set_pte(pent, mk_lv2ent_spage(paddr, prot)); 10268c2ecf20Sopenharmony_ci *pgcnt -= 1; 10278c2ecf20Sopenharmony_ci } else { /* size == LPAGE_SIZE */ 10288c2ecf20Sopenharmony_ci int i; 10298c2ecf20Sopenharmony_ci dma_addr_t pent_base = virt_to_phys(pent); 10308c2ecf20Sopenharmony_ci 10318c2ecf20Sopenharmony_ci dma_sync_single_for_cpu(dma_dev, pent_base, 10328c2ecf20Sopenharmony_ci sizeof(*pent) * SPAGES_PER_LPAGE, 10338c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 10348c2ecf20Sopenharmony_ci for (i = 0; i < SPAGES_PER_LPAGE; i++, pent++) { 10358c2ecf20Sopenharmony_ci if (WARN_ON(!lv2ent_fault(pent))) { 10368c2ecf20Sopenharmony_ci if (i > 0) 10378c2ecf20Sopenharmony_ci memset(pent - i, 0, sizeof(*pent) * i); 10388c2ecf20Sopenharmony_ci return -EADDRINUSE; 10398c2ecf20Sopenharmony_ci } 10408c2ecf20Sopenharmony_ci 10418c2ecf20Sopenharmony_ci *pent = mk_lv2ent_lpage(paddr, prot); 10428c2ecf20Sopenharmony_ci } 10438c2ecf20Sopenharmony_ci dma_sync_single_for_device(dma_dev, pent_base, 10448c2ecf20Sopenharmony_ci sizeof(*pent) * SPAGES_PER_LPAGE, 10458c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 10468c2ecf20Sopenharmony_ci *pgcnt -= SPAGES_PER_LPAGE; 10478c2ecf20Sopenharmony_ci } 10488c2ecf20Sopenharmony_ci 10498c2ecf20Sopenharmony_ci return 0; 10508c2ecf20Sopenharmony_ci} 10518c2ecf20Sopenharmony_ci 10528c2ecf20Sopenharmony_ci/* 10538c2ecf20Sopenharmony_ci * *CAUTION* to the I/O virtual memory managers that support exynos-iommu: 10548c2ecf20Sopenharmony_ci * 10558c2ecf20Sopenharmony_ci * System MMU v3.x has advanced logic to improve address translation 10568c2ecf20Sopenharmony_ci * performance with caching more page table entries by a page table walk. 10578c2ecf20Sopenharmony_ci * However, the logic has a bug that while caching faulty page table entries, 10588c2ecf20Sopenharmony_ci * System MMU reports page fault if the cached fault entry is hit even though 10598c2ecf20Sopenharmony_ci * the fault entry is updated to a valid entry after the entry is cached. 10608c2ecf20Sopenharmony_ci * To prevent caching faulty page table entries which may be updated to valid 10618c2ecf20Sopenharmony_ci * entries later, the virtual memory manager should care about the workaround 10628c2ecf20Sopenharmony_ci * for the problem. The following describes the workaround. 10638c2ecf20Sopenharmony_ci * 10648c2ecf20Sopenharmony_ci * Any two consecutive I/O virtual address regions must have a hole of 128KiB 10658c2ecf20Sopenharmony_ci * at maximum to prevent misbehavior of System MMU 3.x (workaround for h/w bug). 10668c2ecf20Sopenharmony_ci * 10678c2ecf20Sopenharmony_ci * Precisely, any start address of I/O virtual region must be aligned with 10688c2ecf20Sopenharmony_ci * the following sizes for System MMU v3.1 and v3.2. 10698c2ecf20Sopenharmony_ci * System MMU v3.1: 128KiB 10708c2ecf20Sopenharmony_ci * System MMU v3.2: 256KiB 10718c2ecf20Sopenharmony_ci * 10728c2ecf20Sopenharmony_ci * Because System MMU v3.3 caches page table entries more aggressively, it needs 10738c2ecf20Sopenharmony_ci * more workarounds. 10748c2ecf20Sopenharmony_ci * - Any two consecutive I/O virtual regions must have a hole of size larger 10758c2ecf20Sopenharmony_ci * than or equal to 128KiB. 10768c2ecf20Sopenharmony_ci * - Start address of an I/O virtual region must be aligned by 128KiB. 10778c2ecf20Sopenharmony_ci */ 10788c2ecf20Sopenharmony_cistatic int exynos_iommu_map(struct iommu_domain *iommu_domain, 10798c2ecf20Sopenharmony_ci unsigned long l_iova, phys_addr_t paddr, size_t size, 10808c2ecf20Sopenharmony_ci int prot, gfp_t gfp) 10818c2ecf20Sopenharmony_ci{ 10828c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain); 10838c2ecf20Sopenharmony_ci sysmmu_pte_t *entry; 10848c2ecf20Sopenharmony_ci sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; 10858c2ecf20Sopenharmony_ci unsigned long flags; 10868c2ecf20Sopenharmony_ci int ret = -ENOMEM; 10878c2ecf20Sopenharmony_ci 10888c2ecf20Sopenharmony_ci BUG_ON(domain->pgtable == NULL); 10898c2ecf20Sopenharmony_ci prot &= SYSMMU_SUPPORTED_PROT_BITS; 10908c2ecf20Sopenharmony_ci 10918c2ecf20Sopenharmony_ci spin_lock_irqsave(&domain->pgtablelock, flags); 10928c2ecf20Sopenharmony_ci 10938c2ecf20Sopenharmony_ci entry = section_entry(domain->pgtable, iova); 10948c2ecf20Sopenharmony_ci 10958c2ecf20Sopenharmony_ci if (size == SECT_SIZE) { 10968c2ecf20Sopenharmony_ci ret = lv1set_section(domain, entry, iova, paddr, prot, 10978c2ecf20Sopenharmony_ci &domain->lv2entcnt[lv1ent_offset(iova)]); 10988c2ecf20Sopenharmony_ci } else { 10998c2ecf20Sopenharmony_ci sysmmu_pte_t *pent; 11008c2ecf20Sopenharmony_ci 11018c2ecf20Sopenharmony_ci pent = alloc_lv2entry(domain, entry, iova, 11028c2ecf20Sopenharmony_ci &domain->lv2entcnt[lv1ent_offset(iova)]); 11038c2ecf20Sopenharmony_ci 11048c2ecf20Sopenharmony_ci if (IS_ERR(pent)) 11058c2ecf20Sopenharmony_ci ret = PTR_ERR(pent); 11068c2ecf20Sopenharmony_ci else 11078c2ecf20Sopenharmony_ci ret = lv2set_page(pent, paddr, size, prot, 11088c2ecf20Sopenharmony_ci &domain->lv2entcnt[lv1ent_offset(iova)]); 11098c2ecf20Sopenharmony_ci } 11108c2ecf20Sopenharmony_ci 11118c2ecf20Sopenharmony_ci if (ret) 11128c2ecf20Sopenharmony_ci pr_err("%s: Failed(%d) to map %#zx bytes @ %#x\n", 11138c2ecf20Sopenharmony_ci __func__, ret, size, iova); 11148c2ecf20Sopenharmony_ci 11158c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->pgtablelock, flags); 11168c2ecf20Sopenharmony_ci 11178c2ecf20Sopenharmony_ci return ret; 11188c2ecf20Sopenharmony_ci} 11198c2ecf20Sopenharmony_ci 11208c2ecf20Sopenharmony_cistatic void exynos_iommu_tlb_invalidate_entry(struct exynos_iommu_domain *domain, 11218c2ecf20Sopenharmony_ci sysmmu_iova_t iova, size_t size) 11228c2ecf20Sopenharmony_ci{ 11238c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data; 11248c2ecf20Sopenharmony_ci unsigned long flags; 11258c2ecf20Sopenharmony_ci 11268c2ecf20Sopenharmony_ci spin_lock_irqsave(&domain->lock, flags); 11278c2ecf20Sopenharmony_ci 11288c2ecf20Sopenharmony_ci list_for_each_entry(data, &domain->clients, domain_node) 11298c2ecf20Sopenharmony_ci sysmmu_tlb_invalidate_entry(data, iova, size); 11308c2ecf20Sopenharmony_ci 11318c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->lock, flags); 11328c2ecf20Sopenharmony_ci} 11338c2ecf20Sopenharmony_ci 11348c2ecf20Sopenharmony_cistatic size_t exynos_iommu_unmap(struct iommu_domain *iommu_domain, 11358c2ecf20Sopenharmony_ci unsigned long l_iova, size_t size, 11368c2ecf20Sopenharmony_ci struct iommu_iotlb_gather *gather) 11378c2ecf20Sopenharmony_ci{ 11388c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain); 11398c2ecf20Sopenharmony_ci sysmmu_iova_t iova = (sysmmu_iova_t)l_iova; 11408c2ecf20Sopenharmony_ci sysmmu_pte_t *ent; 11418c2ecf20Sopenharmony_ci size_t err_pgsize; 11428c2ecf20Sopenharmony_ci unsigned long flags; 11438c2ecf20Sopenharmony_ci 11448c2ecf20Sopenharmony_ci BUG_ON(domain->pgtable == NULL); 11458c2ecf20Sopenharmony_ci 11468c2ecf20Sopenharmony_ci spin_lock_irqsave(&domain->pgtablelock, flags); 11478c2ecf20Sopenharmony_ci 11488c2ecf20Sopenharmony_ci ent = section_entry(domain->pgtable, iova); 11498c2ecf20Sopenharmony_ci 11508c2ecf20Sopenharmony_ci if (lv1ent_section(ent)) { 11518c2ecf20Sopenharmony_ci if (WARN_ON(size < SECT_SIZE)) { 11528c2ecf20Sopenharmony_ci err_pgsize = SECT_SIZE; 11538c2ecf20Sopenharmony_ci goto err; 11548c2ecf20Sopenharmony_ci } 11558c2ecf20Sopenharmony_ci 11568c2ecf20Sopenharmony_ci /* workaround for h/w bug in System MMU v3.3 */ 11578c2ecf20Sopenharmony_ci exynos_iommu_set_pte(ent, ZERO_LV2LINK); 11588c2ecf20Sopenharmony_ci size = SECT_SIZE; 11598c2ecf20Sopenharmony_ci goto done; 11608c2ecf20Sopenharmony_ci } 11618c2ecf20Sopenharmony_ci 11628c2ecf20Sopenharmony_ci if (unlikely(lv1ent_fault(ent))) { 11638c2ecf20Sopenharmony_ci if (size > SECT_SIZE) 11648c2ecf20Sopenharmony_ci size = SECT_SIZE; 11658c2ecf20Sopenharmony_ci goto done; 11668c2ecf20Sopenharmony_ci } 11678c2ecf20Sopenharmony_ci 11688c2ecf20Sopenharmony_ci /* lv1ent_page(sent) == true here */ 11698c2ecf20Sopenharmony_ci 11708c2ecf20Sopenharmony_ci ent = page_entry(ent, iova); 11718c2ecf20Sopenharmony_ci 11728c2ecf20Sopenharmony_ci if (unlikely(lv2ent_fault(ent))) { 11738c2ecf20Sopenharmony_ci size = SPAGE_SIZE; 11748c2ecf20Sopenharmony_ci goto done; 11758c2ecf20Sopenharmony_ci } 11768c2ecf20Sopenharmony_ci 11778c2ecf20Sopenharmony_ci if (lv2ent_small(ent)) { 11788c2ecf20Sopenharmony_ci exynos_iommu_set_pte(ent, 0); 11798c2ecf20Sopenharmony_ci size = SPAGE_SIZE; 11808c2ecf20Sopenharmony_ci domain->lv2entcnt[lv1ent_offset(iova)] += 1; 11818c2ecf20Sopenharmony_ci goto done; 11828c2ecf20Sopenharmony_ci } 11838c2ecf20Sopenharmony_ci 11848c2ecf20Sopenharmony_ci /* lv1ent_large(ent) == true here */ 11858c2ecf20Sopenharmony_ci if (WARN_ON(size < LPAGE_SIZE)) { 11868c2ecf20Sopenharmony_ci err_pgsize = LPAGE_SIZE; 11878c2ecf20Sopenharmony_ci goto err; 11888c2ecf20Sopenharmony_ci } 11898c2ecf20Sopenharmony_ci 11908c2ecf20Sopenharmony_ci dma_sync_single_for_cpu(dma_dev, virt_to_phys(ent), 11918c2ecf20Sopenharmony_ci sizeof(*ent) * SPAGES_PER_LPAGE, 11928c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 11938c2ecf20Sopenharmony_ci memset(ent, 0, sizeof(*ent) * SPAGES_PER_LPAGE); 11948c2ecf20Sopenharmony_ci dma_sync_single_for_device(dma_dev, virt_to_phys(ent), 11958c2ecf20Sopenharmony_ci sizeof(*ent) * SPAGES_PER_LPAGE, 11968c2ecf20Sopenharmony_ci DMA_TO_DEVICE); 11978c2ecf20Sopenharmony_ci size = LPAGE_SIZE; 11988c2ecf20Sopenharmony_ci domain->lv2entcnt[lv1ent_offset(iova)] += SPAGES_PER_LPAGE; 11998c2ecf20Sopenharmony_cidone: 12008c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->pgtablelock, flags); 12018c2ecf20Sopenharmony_ci 12028c2ecf20Sopenharmony_ci exynos_iommu_tlb_invalidate_entry(domain, iova, size); 12038c2ecf20Sopenharmony_ci 12048c2ecf20Sopenharmony_ci return size; 12058c2ecf20Sopenharmony_cierr: 12068c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->pgtablelock, flags); 12078c2ecf20Sopenharmony_ci 12088c2ecf20Sopenharmony_ci pr_err("%s: Failed: size(%#zx) @ %#x is smaller than page size %#zx\n", 12098c2ecf20Sopenharmony_ci __func__, size, iova, err_pgsize); 12108c2ecf20Sopenharmony_ci 12118c2ecf20Sopenharmony_ci return 0; 12128c2ecf20Sopenharmony_ci} 12138c2ecf20Sopenharmony_ci 12148c2ecf20Sopenharmony_cistatic phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *iommu_domain, 12158c2ecf20Sopenharmony_ci dma_addr_t iova) 12168c2ecf20Sopenharmony_ci{ 12178c2ecf20Sopenharmony_ci struct exynos_iommu_domain *domain = to_exynos_domain(iommu_domain); 12188c2ecf20Sopenharmony_ci sysmmu_pte_t *entry; 12198c2ecf20Sopenharmony_ci unsigned long flags; 12208c2ecf20Sopenharmony_ci phys_addr_t phys = 0; 12218c2ecf20Sopenharmony_ci 12228c2ecf20Sopenharmony_ci spin_lock_irqsave(&domain->pgtablelock, flags); 12238c2ecf20Sopenharmony_ci 12248c2ecf20Sopenharmony_ci entry = section_entry(domain->pgtable, iova); 12258c2ecf20Sopenharmony_ci 12268c2ecf20Sopenharmony_ci if (lv1ent_section(entry)) { 12278c2ecf20Sopenharmony_ci phys = section_phys(entry) + section_offs(iova); 12288c2ecf20Sopenharmony_ci } else if (lv1ent_page(entry)) { 12298c2ecf20Sopenharmony_ci entry = page_entry(entry, iova); 12308c2ecf20Sopenharmony_ci 12318c2ecf20Sopenharmony_ci if (lv2ent_large(entry)) 12328c2ecf20Sopenharmony_ci phys = lpage_phys(entry) + lpage_offs(iova); 12338c2ecf20Sopenharmony_ci else if (lv2ent_small(entry)) 12348c2ecf20Sopenharmony_ci phys = spage_phys(entry) + spage_offs(iova); 12358c2ecf20Sopenharmony_ci } 12368c2ecf20Sopenharmony_ci 12378c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&domain->pgtablelock, flags); 12388c2ecf20Sopenharmony_ci 12398c2ecf20Sopenharmony_ci return phys; 12408c2ecf20Sopenharmony_ci} 12418c2ecf20Sopenharmony_ci 12428c2ecf20Sopenharmony_cistatic struct iommu_device *exynos_iommu_probe_device(struct device *dev) 12438c2ecf20Sopenharmony_ci{ 12448c2ecf20Sopenharmony_ci struct exynos_iommu_owner *owner = dev_iommu_priv_get(dev); 12458c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data; 12468c2ecf20Sopenharmony_ci 12478c2ecf20Sopenharmony_ci if (!has_sysmmu(dev)) 12488c2ecf20Sopenharmony_ci return ERR_PTR(-ENODEV); 12498c2ecf20Sopenharmony_ci 12508c2ecf20Sopenharmony_ci list_for_each_entry(data, &owner->controllers, owner_node) { 12518c2ecf20Sopenharmony_ci /* 12528c2ecf20Sopenharmony_ci * SYSMMU will be runtime activated via device link 12538c2ecf20Sopenharmony_ci * (dependency) to its master device, so there are no 12548c2ecf20Sopenharmony_ci * direct calls to pm_runtime_get/put in this driver. 12558c2ecf20Sopenharmony_ci */ 12568c2ecf20Sopenharmony_ci data->link = device_link_add(dev, data->sysmmu, 12578c2ecf20Sopenharmony_ci DL_FLAG_STATELESS | 12588c2ecf20Sopenharmony_ci DL_FLAG_PM_RUNTIME); 12598c2ecf20Sopenharmony_ci } 12608c2ecf20Sopenharmony_ci 12618c2ecf20Sopenharmony_ci /* There is always at least one entry, see exynos_iommu_of_xlate() */ 12628c2ecf20Sopenharmony_ci data = list_first_entry(&owner->controllers, 12638c2ecf20Sopenharmony_ci struct sysmmu_drvdata, owner_node); 12648c2ecf20Sopenharmony_ci 12658c2ecf20Sopenharmony_ci return &data->iommu; 12668c2ecf20Sopenharmony_ci} 12678c2ecf20Sopenharmony_ci 12688c2ecf20Sopenharmony_cistatic void exynos_iommu_release_device(struct device *dev) 12698c2ecf20Sopenharmony_ci{ 12708c2ecf20Sopenharmony_ci struct exynos_iommu_owner *owner = dev_iommu_priv_get(dev); 12718c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data; 12728c2ecf20Sopenharmony_ci 12738c2ecf20Sopenharmony_ci if (!has_sysmmu(dev)) 12748c2ecf20Sopenharmony_ci return; 12758c2ecf20Sopenharmony_ci 12768c2ecf20Sopenharmony_ci if (owner->domain) { 12778c2ecf20Sopenharmony_ci struct iommu_group *group = iommu_group_get(dev); 12788c2ecf20Sopenharmony_ci 12798c2ecf20Sopenharmony_ci if (group) { 12808c2ecf20Sopenharmony_ci WARN_ON(owner->domain != 12818c2ecf20Sopenharmony_ci iommu_group_default_domain(group)); 12828c2ecf20Sopenharmony_ci exynos_iommu_detach_device(owner->domain, dev); 12838c2ecf20Sopenharmony_ci iommu_group_put(group); 12848c2ecf20Sopenharmony_ci } 12858c2ecf20Sopenharmony_ci } 12868c2ecf20Sopenharmony_ci 12878c2ecf20Sopenharmony_ci list_for_each_entry(data, &owner->controllers, owner_node) 12888c2ecf20Sopenharmony_ci device_link_del(data->link); 12898c2ecf20Sopenharmony_ci} 12908c2ecf20Sopenharmony_ci 12918c2ecf20Sopenharmony_cistatic int exynos_iommu_of_xlate(struct device *dev, 12928c2ecf20Sopenharmony_ci struct of_phandle_args *spec) 12938c2ecf20Sopenharmony_ci{ 12948c2ecf20Sopenharmony_ci struct platform_device *sysmmu = of_find_device_by_node(spec->np); 12958c2ecf20Sopenharmony_ci struct exynos_iommu_owner *owner = dev_iommu_priv_get(dev); 12968c2ecf20Sopenharmony_ci struct sysmmu_drvdata *data, *entry; 12978c2ecf20Sopenharmony_ci 12988c2ecf20Sopenharmony_ci if (!sysmmu) 12998c2ecf20Sopenharmony_ci return -ENODEV; 13008c2ecf20Sopenharmony_ci 13018c2ecf20Sopenharmony_ci data = platform_get_drvdata(sysmmu); 13028c2ecf20Sopenharmony_ci if (!data) { 13038c2ecf20Sopenharmony_ci put_device(&sysmmu->dev); 13048c2ecf20Sopenharmony_ci return -ENODEV; 13058c2ecf20Sopenharmony_ci } 13068c2ecf20Sopenharmony_ci 13078c2ecf20Sopenharmony_ci if (!owner) { 13088c2ecf20Sopenharmony_ci owner = kzalloc(sizeof(*owner), GFP_KERNEL); 13098c2ecf20Sopenharmony_ci if (!owner) { 13108c2ecf20Sopenharmony_ci put_device(&sysmmu->dev); 13118c2ecf20Sopenharmony_ci return -ENOMEM; 13128c2ecf20Sopenharmony_ci } 13138c2ecf20Sopenharmony_ci 13148c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&owner->controllers); 13158c2ecf20Sopenharmony_ci mutex_init(&owner->rpm_lock); 13168c2ecf20Sopenharmony_ci dev_iommu_priv_set(dev, owner); 13178c2ecf20Sopenharmony_ci } 13188c2ecf20Sopenharmony_ci 13198c2ecf20Sopenharmony_ci list_for_each_entry(entry, &owner->controllers, owner_node) 13208c2ecf20Sopenharmony_ci if (entry == data) 13218c2ecf20Sopenharmony_ci return 0; 13228c2ecf20Sopenharmony_ci 13238c2ecf20Sopenharmony_ci list_add_tail(&data->owner_node, &owner->controllers); 13248c2ecf20Sopenharmony_ci data->master = dev; 13258c2ecf20Sopenharmony_ci 13268c2ecf20Sopenharmony_ci return 0; 13278c2ecf20Sopenharmony_ci} 13288c2ecf20Sopenharmony_ci 13298c2ecf20Sopenharmony_cistatic const struct iommu_ops exynos_iommu_ops = { 13308c2ecf20Sopenharmony_ci .domain_alloc = exynos_iommu_domain_alloc, 13318c2ecf20Sopenharmony_ci .domain_free = exynos_iommu_domain_free, 13328c2ecf20Sopenharmony_ci .attach_dev = exynos_iommu_attach_device, 13338c2ecf20Sopenharmony_ci .detach_dev = exynos_iommu_detach_device, 13348c2ecf20Sopenharmony_ci .map = exynos_iommu_map, 13358c2ecf20Sopenharmony_ci .unmap = exynos_iommu_unmap, 13368c2ecf20Sopenharmony_ci .iova_to_phys = exynos_iommu_iova_to_phys, 13378c2ecf20Sopenharmony_ci .device_group = generic_device_group, 13388c2ecf20Sopenharmony_ci .probe_device = exynos_iommu_probe_device, 13398c2ecf20Sopenharmony_ci .release_device = exynos_iommu_release_device, 13408c2ecf20Sopenharmony_ci .pgsize_bitmap = SECT_SIZE | LPAGE_SIZE | SPAGE_SIZE, 13418c2ecf20Sopenharmony_ci .of_xlate = exynos_iommu_of_xlate, 13428c2ecf20Sopenharmony_ci}; 13438c2ecf20Sopenharmony_ci 13448c2ecf20Sopenharmony_cistatic int __init exynos_iommu_init(void) 13458c2ecf20Sopenharmony_ci{ 13468c2ecf20Sopenharmony_ci struct device_node *np; 13478c2ecf20Sopenharmony_ci int ret; 13488c2ecf20Sopenharmony_ci 13498c2ecf20Sopenharmony_ci np = of_find_matching_node(NULL, sysmmu_of_match); 13508c2ecf20Sopenharmony_ci if (!np) 13518c2ecf20Sopenharmony_ci return 0; 13528c2ecf20Sopenharmony_ci 13538c2ecf20Sopenharmony_ci of_node_put(np); 13548c2ecf20Sopenharmony_ci 13558c2ecf20Sopenharmony_ci lv2table_kmem_cache = kmem_cache_create("exynos-iommu-lv2table", 13568c2ecf20Sopenharmony_ci LV2TABLE_SIZE, LV2TABLE_SIZE, 0, NULL); 13578c2ecf20Sopenharmony_ci if (!lv2table_kmem_cache) { 13588c2ecf20Sopenharmony_ci pr_err("%s: Failed to create kmem cache\n", __func__); 13598c2ecf20Sopenharmony_ci return -ENOMEM; 13608c2ecf20Sopenharmony_ci } 13618c2ecf20Sopenharmony_ci 13628c2ecf20Sopenharmony_ci ret = platform_driver_register(&exynos_sysmmu_driver); 13638c2ecf20Sopenharmony_ci if (ret) { 13648c2ecf20Sopenharmony_ci pr_err("%s: Failed to register driver\n", __func__); 13658c2ecf20Sopenharmony_ci goto err_reg_driver; 13668c2ecf20Sopenharmony_ci } 13678c2ecf20Sopenharmony_ci 13688c2ecf20Sopenharmony_ci zero_lv2_table = kmem_cache_zalloc(lv2table_kmem_cache, GFP_KERNEL); 13698c2ecf20Sopenharmony_ci if (zero_lv2_table == NULL) { 13708c2ecf20Sopenharmony_ci pr_err("%s: Failed to allocate zero level2 page table\n", 13718c2ecf20Sopenharmony_ci __func__); 13728c2ecf20Sopenharmony_ci ret = -ENOMEM; 13738c2ecf20Sopenharmony_ci goto err_zero_lv2; 13748c2ecf20Sopenharmony_ci } 13758c2ecf20Sopenharmony_ci 13768c2ecf20Sopenharmony_ci ret = bus_set_iommu(&platform_bus_type, &exynos_iommu_ops); 13778c2ecf20Sopenharmony_ci if (ret) { 13788c2ecf20Sopenharmony_ci pr_err("%s: Failed to register exynos-iommu driver.\n", 13798c2ecf20Sopenharmony_ci __func__); 13808c2ecf20Sopenharmony_ci goto err_set_iommu; 13818c2ecf20Sopenharmony_ci } 13828c2ecf20Sopenharmony_ci 13838c2ecf20Sopenharmony_ci return 0; 13848c2ecf20Sopenharmony_cierr_set_iommu: 13858c2ecf20Sopenharmony_ci kmem_cache_free(lv2table_kmem_cache, zero_lv2_table); 13868c2ecf20Sopenharmony_cierr_zero_lv2: 13878c2ecf20Sopenharmony_ci platform_driver_unregister(&exynos_sysmmu_driver); 13888c2ecf20Sopenharmony_cierr_reg_driver: 13898c2ecf20Sopenharmony_ci kmem_cache_destroy(lv2table_kmem_cache); 13908c2ecf20Sopenharmony_ci return ret; 13918c2ecf20Sopenharmony_ci} 13928c2ecf20Sopenharmony_cicore_initcall(exynos_iommu_init); 1393