162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) 262306a36Sopenharmony_ci// Copyright (C) 2016-2018, Allwinner Technology CO., LTD. 362306a36Sopenharmony_ci// Copyright (C) 2019-2020, Cerno 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci#include <linux/bitfield.h> 662306a36Sopenharmony_ci#include <linux/bug.h> 762306a36Sopenharmony_ci#include <linux/clk.h> 862306a36Sopenharmony_ci#include <linux/device.h> 962306a36Sopenharmony_ci#include <linux/dma-direction.h> 1062306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/errno.h> 1362306a36Sopenharmony_ci#include <linux/interrupt.h> 1462306a36Sopenharmony_ci#include <linux/iommu.h> 1562306a36Sopenharmony_ci#include <linux/iopoll.h> 1662306a36Sopenharmony_ci#include <linux/ioport.h> 1762306a36Sopenharmony_ci#include <linux/log2.h> 1862306a36Sopenharmony_ci#include <linux/module.h> 1962306a36Sopenharmony_ci#include <linux/of_platform.h> 2062306a36Sopenharmony_ci#include <linux/platform_device.h> 2162306a36Sopenharmony_ci#include <linux/pm.h> 2262306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2362306a36Sopenharmony_ci#include <linux/reset.h> 2462306a36Sopenharmony_ci#include <linux/sizes.h> 2562306a36Sopenharmony_ci#include <linux/slab.h> 2662306a36Sopenharmony_ci#include <linux/spinlock.h> 2762306a36Sopenharmony_ci#include <linux/types.h> 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#define IOMMU_RESET_REG 0x010 3062306a36Sopenharmony_ci#define IOMMU_RESET_RELEASE_ALL 0xffffffff 3162306a36Sopenharmony_ci#define IOMMU_ENABLE_REG 0x020 3262306a36Sopenharmony_ci#define IOMMU_ENABLE_ENABLE BIT(0) 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define IOMMU_BYPASS_REG 0x030 3562306a36Sopenharmony_ci#define IOMMU_AUTO_GATING_REG 0x040 3662306a36Sopenharmony_ci#define IOMMU_AUTO_GATING_ENABLE BIT(0) 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci#define IOMMU_WBUF_CTRL_REG 0x044 3962306a36Sopenharmony_ci#define IOMMU_OOO_CTRL_REG 0x048 4062306a36Sopenharmony_ci#define IOMMU_4KB_BDY_PRT_CTRL_REG 0x04c 4162306a36Sopenharmony_ci#define IOMMU_TTB_REG 0x050 4262306a36Sopenharmony_ci#define IOMMU_TLB_ENABLE_REG 0x060 4362306a36Sopenharmony_ci#define IOMMU_TLB_PREFETCH_REG 0x070 4462306a36Sopenharmony_ci#define IOMMU_TLB_PREFETCH_MASTER_ENABLE(m) BIT(m) 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_ci#define IOMMU_TLB_FLUSH_REG 0x080 4762306a36Sopenharmony_ci#define IOMMU_TLB_FLUSH_PTW_CACHE BIT(17) 4862306a36Sopenharmony_ci#define IOMMU_TLB_FLUSH_MACRO_TLB BIT(16) 4962306a36Sopenharmony_ci#define IOMMU_TLB_FLUSH_MICRO_TLB(i) (BIT(i) & GENMASK(5, 0)) 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define IOMMU_TLB_IVLD_ADDR_REG 0x090 5262306a36Sopenharmony_ci#define IOMMU_TLB_IVLD_ADDR_MASK_REG 0x094 5362306a36Sopenharmony_ci#define IOMMU_TLB_IVLD_ENABLE_REG 0x098 5462306a36Sopenharmony_ci#define IOMMU_TLB_IVLD_ENABLE_ENABLE BIT(0) 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci#define IOMMU_PC_IVLD_ADDR_REG 0x0a0 5762306a36Sopenharmony_ci#define IOMMU_PC_IVLD_ENABLE_REG 0x0a8 5862306a36Sopenharmony_ci#define IOMMU_PC_IVLD_ENABLE_ENABLE BIT(0) 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci#define IOMMU_DM_AUT_CTRL_REG(d) (0x0b0 + ((d) / 2) * 4) 6162306a36Sopenharmony_ci#define IOMMU_DM_AUT_CTRL_RD_UNAVAIL(d, m) (1 << (((d & 1) * 16) + ((m) * 2))) 6262306a36Sopenharmony_ci#define IOMMU_DM_AUT_CTRL_WR_UNAVAIL(d, m) (1 << (((d & 1) * 16) + ((m) * 2) + 1)) 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci#define IOMMU_DM_AUT_OVWT_REG 0x0d0 6562306a36Sopenharmony_ci#define IOMMU_INT_ENABLE_REG 0x100 6662306a36Sopenharmony_ci#define IOMMU_INT_CLR_REG 0x104 6762306a36Sopenharmony_ci#define IOMMU_INT_STA_REG 0x108 6862306a36Sopenharmony_ci#define IOMMU_INT_ERR_ADDR_REG(i) (0x110 + (i) * 4) 6962306a36Sopenharmony_ci#define IOMMU_INT_ERR_ADDR_L1_REG 0x130 7062306a36Sopenharmony_ci#define IOMMU_INT_ERR_ADDR_L2_REG 0x134 7162306a36Sopenharmony_ci#define IOMMU_INT_ERR_DATA_REG(i) (0x150 + (i) * 4) 7262306a36Sopenharmony_ci#define IOMMU_L1PG_INT_REG 0x0180 7362306a36Sopenharmony_ci#define IOMMU_L2PG_INT_REG 0x0184 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci#define IOMMU_INT_INVALID_L2PG BIT(17) 7662306a36Sopenharmony_ci#define IOMMU_INT_INVALID_L1PG BIT(16) 7762306a36Sopenharmony_ci#define IOMMU_INT_MASTER_PERMISSION(m) BIT(m) 7862306a36Sopenharmony_ci#define IOMMU_INT_MASTER_MASK (IOMMU_INT_MASTER_PERMISSION(0) | \ 7962306a36Sopenharmony_ci IOMMU_INT_MASTER_PERMISSION(1) | \ 8062306a36Sopenharmony_ci IOMMU_INT_MASTER_PERMISSION(2) | \ 8162306a36Sopenharmony_ci IOMMU_INT_MASTER_PERMISSION(3) | \ 8262306a36Sopenharmony_ci IOMMU_INT_MASTER_PERMISSION(4) | \ 8362306a36Sopenharmony_ci IOMMU_INT_MASTER_PERMISSION(5)) 8462306a36Sopenharmony_ci#define IOMMU_INT_MASK (IOMMU_INT_INVALID_L1PG | \ 8562306a36Sopenharmony_ci IOMMU_INT_INVALID_L2PG | \ 8662306a36Sopenharmony_ci IOMMU_INT_MASTER_MASK) 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci#define PT_ENTRY_SIZE sizeof(u32) 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci#define NUM_DT_ENTRIES 4096 9162306a36Sopenharmony_ci#define DT_SIZE (NUM_DT_ENTRIES * PT_ENTRY_SIZE) 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci#define NUM_PT_ENTRIES 256 9462306a36Sopenharmony_ci#define PT_SIZE (NUM_PT_ENTRIES * PT_ENTRY_SIZE) 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci#define SPAGE_SIZE 4096 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistruct sun50i_iommu { 9962306a36Sopenharmony_ci struct iommu_device iommu; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci /* Lock to modify the IOMMU registers */ 10262306a36Sopenharmony_ci spinlock_t iommu_lock; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci struct device *dev; 10562306a36Sopenharmony_ci void __iomem *base; 10662306a36Sopenharmony_ci struct reset_control *reset; 10762306a36Sopenharmony_ci struct clk *clk; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci struct iommu_domain *domain; 11062306a36Sopenharmony_ci struct iommu_group *group; 11162306a36Sopenharmony_ci struct kmem_cache *pt_pool; 11262306a36Sopenharmony_ci}; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistruct sun50i_iommu_domain { 11562306a36Sopenharmony_ci struct iommu_domain domain; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci /* Number of devices attached to the domain */ 11862306a36Sopenharmony_ci refcount_t refcnt; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* L1 Page Table */ 12162306a36Sopenharmony_ci u32 *dt; 12262306a36Sopenharmony_ci dma_addr_t dt_dma; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci struct sun50i_iommu *iommu; 12562306a36Sopenharmony_ci}; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_cistatic struct sun50i_iommu_domain *to_sun50i_domain(struct iommu_domain *domain) 12862306a36Sopenharmony_ci{ 12962306a36Sopenharmony_ci return container_of(domain, struct sun50i_iommu_domain, domain); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic struct sun50i_iommu *sun50i_iommu_from_dev(struct device *dev) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci return dev_iommu_priv_get(dev); 13562306a36Sopenharmony_ci} 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic u32 iommu_read(struct sun50i_iommu *iommu, u32 offset) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci return readl(iommu->base + offset); 14062306a36Sopenharmony_ci} 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_cistatic void iommu_write(struct sun50i_iommu *iommu, u32 offset, u32 value) 14362306a36Sopenharmony_ci{ 14462306a36Sopenharmony_ci writel(value, iommu->base + offset); 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci/* 14862306a36Sopenharmony_ci * The Allwinner H6 IOMMU uses a 2-level page table. 14962306a36Sopenharmony_ci * 15062306a36Sopenharmony_ci * The first level is the usual Directory Table (DT), that consists of 15162306a36Sopenharmony_ci * 4096 4-bytes Directory Table Entries (DTE), each pointing to a Page 15262306a36Sopenharmony_ci * Table (PT). 15362306a36Sopenharmony_ci * 15462306a36Sopenharmony_ci * Each PT consits of 256 4-bytes Page Table Entries (PTE), each 15562306a36Sopenharmony_ci * pointing to a 4kB page of physical memory. 15662306a36Sopenharmony_ci * 15762306a36Sopenharmony_ci * The IOMMU supports a single DT, pointed by the IOMMU_TTB_REG 15862306a36Sopenharmony_ci * register that contains its physical address. 15962306a36Sopenharmony_ci */ 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci#define SUN50I_IOVA_DTE_MASK GENMASK(31, 20) 16262306a36Sopenharmony_ci#define SUN50I_IOVA_PTE_MASK GENMASK(19, 12) 16362306a36Sopenharmony_ci#define SUN50I_IOVA_PAGE_MASK GENMASK(11, 0) 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic u32 sun50i_iova_get_dte_index(dma_addr_t iova) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci return FIELD_GET(SUN50I_IOVA_DTE_MASK, iova); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic u32 sun50i_iova_get_pte_index(dma_addr_t iova) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci return FIELD_GET(SUN50I_IOVA_PTE_MASK, iova); 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic u32 sun50i_iova_get_page_offset(dma_addr_t iova) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci return FIELD_GET(SUN50I_IOVA_PAGE_MASK, iova); 17862306a36Sopenharmony_ci} 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci/* 18162306a36Sopenharmony_ci * Each Directory Table Entry has a Page Table address and a valid 18262306a36Sopenharmony_ci * bit: 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci * +---------------------+-----------+-+ 18562306a36Sopenharmony_ci * | PT address | Reserved |V| 18662306a36Sopenharmony_ci * +---------------------+-----------+-+ 18762306a36Sopenharmony_ci * 31:10 - Page Table address 18862306a36Sopenharmony_ci * 9:2 - Reserved 18962306a36Sopenharmony_ci * 1:0 - 1 if the entry is valid 19062306a36Sopenharmony_ci */ 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci#define SUN50I_DTE_PT_ADDRESS_MASK GENMASK(31, 10) 19362306a36Sopenharmony_ci#define SUN50I_DTE_PT_ATTRS GENMASK(1, 0) 19462306a36Sopenharmony_ci#define SUN50I_DTE_PT_VALID 1 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_cistatic phys_addr_t sun50i_dte_get_pt_address(u32 dte) 19762306a36Sopenharmony_ci{ 19862306a36Sopenharmony_ci return (phys_addr_t)dte & SUN50I_DTE_PT_ADDRESS_MASK; 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_cistatic bool sun50i_dte_is_pt_valid(u32 dte) 20262306a36Sopenharmony_ci{ 20362306a36Sopenharmony_ci return (dte & SUN50I_DTE_PT_ATTRS) == SUN50I_DTE_PT_VALID; 20462306a36Sopenharmony_ci} 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_cistatic u32 sun50i_mk_dte(dma_addr_t pt_dma) 20762306a36Sopenharmony_ci{ 20862306a36Sopenharmony_ci return (pt_dma & SUN50I_DTE_PT_ADDRESS_MASK) | SUN50I_DTE_PT_VALID; 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci/* 21262306a36Sopenharmony_ci * Each PTE has a Page address, an authority index and a valid bit: 21362306a36Sopenharmony_ci * 21462306a36Sopenharmony_ci * +----------------+-----+-----+-----+---+-----+ 21562306a36Sopenharmony_ci * | Page address | Rsv | ACI | Rsv | V | Rsv | 21662306a36Sopenharmony_ci * +----------------+-----+-----+-----+---+-----+ 21762306a36Sopenharmony_ci * 31:12 - Page address 21862306a36Sopenharmony_ci * 11:8 - Reserved 21962306a36Sopenharmony_ci * 7:4 - Authority Control Index 22062306a36Sopenharmony_ci * 3:2 - Reserved 22162306a36Sopenharmony_ci * 1 - 1 if the entry is valid 22262306a36Sopenharmony_ci * 0 - Reserved 22362306a36Sopenharmony_ci * 22462306a36Sopenharmony_ci * The way permissions work is that the IOMMU has 16 "domains" that 22562306a36Sopenharmony_ci * can be configured to give each masters either read or write 22662306a36Sopenharmony_ci * permissions through the IOMMU_DM_AUT_CTRL_REG registers. The domain 22762306a36Sopenharmony_ci * 0 seems like the default domain, and its permissions in the 22862306a36Sopenharmony_ci * IOMMU_DM_AUT_CTRL_REG are only read-only, so it's not really 22962306a36Sopenharmony_ci * useful to enforce any particular permission. 23062306a36Sopenharmony_ci * 23162306a36Sopenharmony_ci * Each page entry will then have a reference to the domain they are 23262306a36Sopenharmony_ci * affected to, so that we can actually enforce them on a per-page 23362306a36Sopenharmony_ci * basis. 23462306a36Sopenharmony_ci * 23562306a36Sopenharmony_ci * In order to make it work with the IOMMU framework, we will be using 23662306a36Sopenharmony_ci * 4 different domains, starting at 1: RD_WR, RD, WR and NONE 23762306a36Sopenharmony_ci * depending on the permission we want to enforce. Each domain will 23862306a36Sopenharmony_ci * have each master setup in the same way, since the IOMMU framework 23962306a36Sopenharmony_ci * doesn't seem to restrict page access on a per-device basis. And 24062306a36Sopenharmony_ci * then we will use the relevant domain index when generating the page 24162306a36Sopenharmony_ci * table entry depending on the permissions we want to be enforced. 24262306a36Sopenharmony_ci */ 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cienum sun50i_iommu_aci { 24562306a36Sopenharmony_ci SUN50I_IOMMU_ACI_DO_NOT_USE = 0, 24662306a36Sopenharmony_ci SUN50I_IOMMU_ACI_NONE, 24762306a36Sopenharmony_ci SUN50I_IOMMU_ACI_RD, 24862306a36Sopenharmony_ci SUN50I_IOMMU_ACI_WR, 24962306a36Sopenharmony_ci SUN50I_IOMMU_ACI_RD_WR, 25062306a36Sopenharmony_ci}; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci#define SUN50I_PTE_PAGE_ADDRESS_MASK GENMASK(31, 12) 25362306a36Sopenharmony_ci#define SUN50I_PTE_ACI_MASK GENMASK(7, 4) 25462306a36Sopenharmony_ci#define SUN50I_PTE_PAGE_VALID BIT(1) 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_cistatic phys_addr_t sun50i_pte_get_page_address(u32 pte) 25762306a36Sopenharmony_ci{ 25862306a36Sopenharmony_ci return (phys_addr_t)pte & SUN50I_PTE_PAGE_ADDRESS_MASK; 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cistatic enum sun50i_iommu_aci sun50i_get_pte_aci(u32 pte) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci return FIELD_GET(SUN50I_PTE_ACI_MASK, pte); 26462306a36Sopenharmony_ci} 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_cistatic bool sun50i_pte_is_page_valid(u32 pte) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci return pte & SUN50I_PTE_PAGE_VALID; 26962306a36Sopenharmony_ci} 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_cistatic u32 sun50i_mk_pte(phys_addr_t page, int prot) 27262306a36Sopenharmony_ci{ 27362306a36Sopenharmony_ci enum sun50i_iommu_aci aci; 27462306a36Sopenharmony_ci u32 flags = 0; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci if ((prot & (IOMMU_READ | IOMMU_WRITE)) == (IOMMU_READ | IOMMU_WRITE)) 27762306a36Sopenharmony_ci aci = SUN50I_IOMMU_ACI_RD_WR; 27862306a36Sopenharmony_ci else if (prot & IOMMU_READ) 27962306a36Sopenharmony_ci aci = SUN50I_IOMMU_ACI_RD; 28062306a36Sopenharmony_ci else if (prot & IOMMU_WRITE) 28162306a36Sopenharmony_ci aci = SUN50I_IOMMU_ACI_WR; 28262306a36Sopenharmony_ci else 28362306a36Sopenharmony_ci aci = SUN50I_IOMMU_ACI_NONE; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci flags |= FIELD_PREP(SUN50I_PTE_ACI_MASK, aci); 28662306a36Sopenharmony_ci page &= SUN50I_PTE_PAGE_ADDRESS_MASK; 28762306a36Sopenharmony_ci return page | flags | SUN50I_PTE_PAGE_VALID; 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_cistatic void sun50i_table_flush(struct sun50i_iommu_domain *sun50i_domain, 29162306a36Sopenharmony_ci void *vaddr, unsigned int count) 29262306a36Sopenharmony_ci{ 29362306a36Sopenharmony_ci struct sun50i_iommu *iommu = sun50i_domain->iommu; 29462306a36Sopenharmony_ci dma_addr_t dma = virt_to_phys(vaddr); 29562306a36Sopenharmony_ci size_t size = count * PT_ENTRY_SIZE; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci dma_sync_single_for_device(iommu->dev, dma, size, DMA_TO_DEVICE); 29862306a36Sopenharmony_ci} 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_cistatic void sun50i_iommu_zap_iova(struct sun50i_iommu *iommu, 30162306a36Sopenharmony_ci unsigned long iova) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci u32 reg; 30462306a36Sopenharmony_ci int ret; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci iommu_write(iommu, IOMMU_TLB_IVLD_ADDR_REG, iova); 30762306a36Sopenharmony_ci iommu_write(iommu, IOMMU_TLB_IVLD_ADDR_MASK_REG, GENMASK(31, 12)); 30862306a36Sopenharmony_ci iommu_write(iommu, IOMMU_TLB_IVLD_ENABLE_REG, 30962306a36Sopenharmony_ci IOMMU_TLB_IVLD_ENABLE_ENABLE); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci ret = readl_poll_timeout_atomic(iommu->base + IOMMU_TLB_IVLD_ENABLE_REG, 31262306a36Sopenharmony_ci reg, !reg, 1, 2000); 31362306a36Sopenharmony_ci if (ret) 31462306a36Sopenharmony_ci dev_warn(iommu->dev, "TLB invalidation timed out!\n"); 31562306a36Sopenharmony_ci} 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_cistatic void sun50i_iommu_zap_ptw_cache(struct sun50i_iommu *iommu, 31862306a36Sopenharmony_ci unsigned long iova) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci u32 reg; 32162306a36Sopenharmony_ci int ret; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci iommu_write(iommu, IOMMU_PC_IVLD_ADDR_REG, iova); 32462306a36Sopenharmony_ci iommu_write(iommu, IOMMU_PC_IVLD_ENABLE_REG, 32562306a36Sopenharmony_ci IOMMU_PC_IVLD_ENABLE_ENABLE); 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci ret = readl_poll_timeout_atomic(iommu->base + IOMMU_PC_IVLD_ENABLE_REG, 32862306a36Sopenharmony_ci reg, !reg, 1, 2000); 32962306a36Sopenharmony_ci if (ret) 33062306a36Sopenharmony_ci dev_warn(iommu->dev, "PTW cache invalidation timed out!\n"); 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic void sun50i_iommu_zap_range(struct sun50i_iommu *iommu, 33462306a36Sopenharmony_ci unsigned long iova, size_t size) 33562306a36Sopenharmony_ci{ 33662306a36Sopenharmony_ci assert_spin_locked(&iommu->iommu_lock); 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci iommu_write(iommu, IOMMU_AUTO_GATING_REG, 0); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci sun50i_iommu_zap_iova(iommu, iova); 34162306a36Sopenharmony_ci sun50i_iommu_zap_iova(iommu, iova + SPAGE_SIZE); 34262306a36Sopenharmony_ci if (size > SPAGE_SIZE) { 34362306a36Sopenharmony_ci sun50i_iommu_zap_iova(iommu, iova + size); 34462306a36Sopenharmony_ci sun50i_iommu_zap_iova(iommu, iova + size + SPAGE_SIZE); 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci sun50i_iommu_zap_ptw_cache(iommu, iova); 34762306a36Sopenharmony_ci sun50i_iommu_zap_ptw_cache(iommu, iova + SZ_1M); 34862306a36Sopenharmony_ci if (size > SZ_1M) { 34962306a36Sopenharmony_ci sun50i_iommu_zap_ptw_cache(iommu, iova + size); 35062306a36Sopenharmony_ci sun50i_iommu_zap_ptw_cache(iommu, iova + size + SZ_1M); 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci iommu_write(iommu, IOMMU_AUTO_GATING_REG, IOMMU_AUTO_GATING_ENABLE); 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic int sun50i_iommu_flush_all_tlb(struct sun50i_iommu *iommu) 35762306a36Sopenharmony_ci{ 35862306a36Sopenharmony_ci u32 reg; 35962306a36Sopenharmony_ci int ret; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci assert_spin_locked(&iommu->iommu_lock); 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci iommu_write(iommu, 36462306a36Sopenharmony_ci IOMMU_TLB_FLUSH_REG, 36562306a36Sopenharmony_ci IOMMU_TLB_FLUSH_PTW_CACHE | 36662306a36Sopenharmony_ci IOMMU_TLB_FLUSH_MACRO_TLB | 36762306a36Sopenharmony_ci IOMMU_TLB_FLUSH_MICRO_TLB(5) | 36862306a36Sopenharmony_ci IOMMU_TLB_FLUSH_MICRO_TLB(4) | 36962306a36Sopenharmony_ci IOMMU_TLB_FLUSH_MICRO_TLB(3) | 37062306a36Sopenharmony_ci IOMMU_TLB_FLUSH_MICRO_TLB(2) | 37162306a36Sopenharmony_ci IOMMU_TLB_FLUSH_MICRO_TLB(1) | 37262306a36Sopenharmony_ci IOMMU_TLB_FLUSH_MICRO_TLB(0)); 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci ret = readl_poll_timeout_atomic(iommu->base + IOMMU_TLB_FLUSH_REG, 37562306a36Sopenharmony_ci reg, !reg, 37662306a36Sopenharmony_ci 1, 2000); 37762306a36Sopenharmony_ci if (ret) 37862306a36Sopenharmony_ci dev_warn(iommu->dev, "TLB Flush timed out!\n"); 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci return ret; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_cistatic void sun50i_iommu_flush_iotlb_all(struct iommu_domain *domain) 38462306a36Sopenharmony_ci{ 38562306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 38662306a36Sopenharmony_ci struct sun50i_iommu *iommu = sun50i_domain->iommu; 38762306a36Sopenharmony_ci unsigned long flags; 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci /* 39062306a36Sopenharmony_ci * At boot, we'll have a first call into .flush_iotlb_all right after 39162306a36Sopenharmony_ci * .probe_device, and since we link our (single) domain to our iommu in 39262306a36Sopenharmony_ci * the .attach_device callback, we don't have that pointer set. 39362306a36Sopenharmony_ci * 39462306a36Sopenharmony_ci * It shouldn't really be any trouble to ignore it though since we flush 39562306a36Sopenharmony_ci * all caches as part of the device powerup. 39662306a36Sopenharmony_ci */ 39762306a36Sopenharmony_ci if (!iommu) 39862306a36Sopenharmony_ci return; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci spin_lock_irqsave(&iommu->iommu_lock, flags); 40162306a36Sopenharmony_ci sun50i_iommu_flush_all_tlb(iommu); 40262306a36Sopenharmony_ci spin_unlock_irqrestore(&iommu->iommu_lock, flags); 40362306a36Sopenharmony_ci} 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_cistatic void sun50i_iommu_iotlb_sync_map(struct iommu_domain *domain, 40662306a36Sopenharmony_ci unsigned long iova, size_t size) 40762306a36Sopenharmony_ci{ 40862306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 40962306a36Sopenharmony_ci struct sun50i_iommu *iommu = sun50i_domain->iommu; 41062306a36Sopenharmony_ci unsigned long flags; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci spin_lock_irqsave(&iommu->iommu_lock, flags); 41362306a36Sopenharmony_ci sun50i_iommu_zap_range(iommu, iova, size); 41462306a36Sopenharmony_ci spin_unlock_irqrestore(&iommu->iommu_lock, flags); 41562306a36Sopenharmony_ci} 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_cistatic void sun50i_iommu_iotlb_sync(struct iommu_domain *domain, 41862306a36Sopenharmony_ci struct iommu_iotlb_gather *gather) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci sun50i_iommu_flush_iotlb_all(domain); 42162306a36Sopenharmony_ci} 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_cistatic int sun50i_iommu_enable(struct sun50i_iommu *iommu) 42462306a36Sopenharmony_ci{ 42562306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain; 42662306a36Sopenharmony_ci unsigned long flags; 42762306a36Sopenharmony_ci int ret; 42862306a36Sopenharmony_ci 42962306a36Sopenharmony_ci if (!iommu->domain) 43062306a36Sopenharmony_ci return 0; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci sun50i_domain = to_sun50i_domain(iommu->domain); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci ret = reset_control_deassert(iommu->reset); 43562306a36Sopenharmony_ci if (ret) 43662306a36Sopenharmony_ci return ret; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci ret = clk_prepare_enable(iommu->clk); 43962306a36Sopenharmony_ci if (ret) 44062306a36Sopenharmony_ci goto err_reset_assert; 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ci spin_lock_irqsave(&iommu->iommu_lock, flags); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ci iommu_write(iommu, IOMMU_TTB_REG, sun50i_domain->dt_dma); 44562306a36Sopenharmony_ci iommu_write(iommu, IOMMU_TLB_PREFETCH_REG, 44662306a36Sopenharmony_ci IOMMU_TLB_PREFETCH_MASTER_ENABLE(0) | 44762306a36Sopenharmony_ci IOMMU_TLB_PREFETCH_MASTER_ENABLE(1) | 44862306a36Sopenharmony_ci IOMMU_TLB_PREFETCH_MASTER_ENABLE(2) | 44962306a36Sopenharmony_ci IOMMU_TLB_PREFETCH_MASTER_ENABLE(3) | 45062306a36Sopenharmony_ci IOMMU_TLB_PREFETCH_MASTER_ENABLE(4) | 45162306a36Sopenharmony_ci IOMMU_TLB_PREFETCH_MASTER_ENABLE(5)); 45262306a36Sopenharmony_ci iommu_write(iommu, IOMMU_INT_ENABLE_REG, IOMMU_INT_MASK); 45362306a36Sopenharmony_ci iommu_write(iommu, IOMMU_DM_AUT_CTRL_REG(SUN50I_IOMMU_ACI_NONE), 45462306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 0) | 45562306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 0) | 45662306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 1) | 45762306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 1) | 45862306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 2) | 45962306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 2) | 46062306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 3) | 46162306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 3) | 46262306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 4) | 46362306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 4) | 46462306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 5) | 46562306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_NONE, 5)); 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci iommu_write(iommu, IOMMU_DM_AUT_CTRL_REG(SUN50I_IOMMU_ACI_RD), 46862306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 0) | 46962306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 1) | 47062306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 2) | 47162306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 3) | 47262306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 4) | 47362306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_WR_UNAVAIL(SUN50I_IOMMU_ACI_RD, 5)); 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci iommu_write(iommu, IOMMU_DM_AUT_CTRL_REG(SUN50I_IOMMU_ACI_WR), 47662306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 0) | 47762306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 1) | 47862306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 2) | 47962306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 3) | 48062306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 4) | 48162306a36Sopenharmony_ci IOMMU_DM_AUT_CTRL_RD_UNAVAIL(SUN50I_IOMMU_ACI_WR, 5)); 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci ret = sun50i_iommu_flush_all_tlb(iommu); 48462306a36Sopenharmony_ci if (ret) { 48562306a36Sopenharmony_ci spin_unlock_irqrestore(&iommu->iommu_lock, flags); 48662306a36Sopenharmony_ci goto err_clk_disable; 48762306a36Sopenharmony_ci } 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci iommu_write(iommu, IOMMU_AUTO_GATING_REG, IOMMU_AUTO_GATING_ENABLE); 49062306a36Sopenharmony_ci iommu_write(iommu, IOMMU_ENABLE_REG, IOMMU_ENABLE_ENABLE); 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci spin_unlock_irqrestore(&iommu->iommu_lock, flags); 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci return 0; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_cierr_clk_disable: 49762306a36Sopenharmony_ci clk_disable_unprepare(iommu->clk); 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_cierr_reset_assert: 50062306a36Sopenharmony_ci reset_control_assert(iommu->reset); 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci return ret; 50362306a36Sopenharmony_ci} 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_cistatic void sun50i_iommu_disable(struct sun50i_iommu *iommu) 50662306a36Sopenharmony_ci{ 50762306a36Sopenharmony_ci unsigned long flags; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci spin_lock_irqsave(&iommu->iommu_lock, flags); 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_ci iommu_write(iommu, IOMMU_ENABLE_REG, 0); 51262306a36Sopenharmony_ci iommu_write(iommu, IOMMU_TTB_REG, 0); 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci spin_unlock_irqrestore(&iommu->iommu_lock, flags); 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci clk_disable_unprepare(iommu->clk); 51762306a36Sopenharmony_ci reset_control_assert(iommu->reset); 51862306a36Sopenharmony_ci} 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_cistatic void *sun50i_iommu_alloc_page_table(struct sun50i_iommu *iommu, 52162306a36Sopenharmony_ci gfp_t gfp) 52262306a36Sopenharmony_ci{ 52362306a36Sopenharmony_ci dma_addr_t pt_dma; 52462306a36Sopenharmony_ci u32 *page_table; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci page_table = kmem_cache_zalloc(iommu->pt_pool, gfp); 52762306a36Sopenharmony_ci if (!page_table) 52862306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci pt_dma = dma_map_single(iommu->dev, page_table, PT_SIZE, DMA_TO_DEVICE); 53162306a36Sopenharmony_ci if (dma_mapping_error(iommu->dev, pt_dma)) { 53262306a36Sopenharmony_ci dev_err(iommu->dev, "Couldn't map L2 Page Table\n"); 53362306a36Sopenharmony_ci kmem_cache_free(iommu->pt_pool, page_table); 53462306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 53562306a36Sopenharmony_ci } 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci /* We rely on the physical address and DMA address being the same */ 53862306a36Sopenharmony_ci WARN_ON(pt_dma != virt_to_phys(page_table)); 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci return page_table; 54162306a36Sopenharmony_ci} 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_cistatic void sun50i_iommu_free_page_table(struct sun50i_iommu *iommu, 54462306a36Sopenharmony_ci u32 *page_table) 54562306a36Sopenharmony_ci{ 54662306a36Sopenharmony_ci phys_addr_t pt_phys = virt_to_phys(page_table); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_ci dma_unmap_single(iommu->dev, pt_phys, PT_SIZE, DMA_TO_DEVICE); 54962306a36Sopenharmony_ci kmem_cache_free(iommu->pt_pool, page_table); 55062306a36Sopenharmony_ci} 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_cistatic u32 *sun50i_dte_get_page_table(struct sun50i_iommu_domain *sun50i_domain, 55362306a36Sopenharmony_ci dma_addr_t iova, gfp_t gfp) 55462306a36Sopenharmony_ci{ 55562306a36Sopenharmony_ci struct sun50i_iommu *iommu = sun50i_domain->iommu; 55662306a36Sopenharmony_ci u32 *page_table; 55762306a36Sopenharmony_ci u32 *dte_addr; 55862306a36Sopenharmony_ci u32 old_dte; 55962306a36Sopenharmony_ci u32 dte; 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_ci dte_addr = &sun50i_domain->dt[sun50i_iova_get_dte_index(iova)]; 56262306a36Sopenharmony_ci dte = *dte_addr; 56362306a36Sopenharmony_ci if (sun50i_dte_is_pt_valid(dte)) { 56462306a36Sopenharmony_ci phys_addr_t pt_phys = sun50i_dte_get_pt_address(dte); 56562306a36Sopenharmony_ci return (u32 *)phys_to_virt(pt_phys); 56662306a36Sopenharmony_ci } 56762306a36Sopenharmony_ci 56862306a36Sopenharmony_ci page_table = sun50i_iommu_alloc_page_table(iommu, gfp); 56962306a36Sopenharmony_ci if (IS_ERR(page_table)) 57062306a36Sopenharmony_ci return page_table; 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci dte = sun50i_mk_dte(virt_to_phys(page_table)); 57362306a36Sopenharmony_ci old_dte = cmpxchg(dte_addr, 0, dte); 57462306a36Sopenharmony_ci if (old_dte) { 57562306a36Sopenharmony_ci phys_addr_t installed_pt_phys = 57662306a36Sopenharmony_ci sun50i_dte_get_pt_address(old_dte); 57762306a36Sopenharmony_ci u32 *installed_pt = phys_to_virt(installed_pt_phys); 57862306a36Sopenharmony_ci u32 *drop_pt = page_table; 57962306a36Sopenharmony_ci 58062306a36Sopenharmony_ci page_table = installed_pt; 58162306a36Sopenharmony_ci dte = old_dte; 58262306a36Sopenharmony_ci sun50i_iommu_free_page_table(iommu, drop_pt); 58362306a36Sopenharmony_ci } 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci sun50i_table_flush(sun50i_domain, page_table, NUM_PT_ENTRIES); 58662306a36Sopenharmony_ci sun50i_table_flush(sun50i_domain, dte_addr, 1); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci return page_table; 58962306a36Sopenharmony_ci} 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_cistatic int sun50i_iommu_map(struct iommu_domain *domain, unsigned long iova, 59262306a36Sopenharmony_ci phys_addr_t paddr, size_t size, int prot, gfp_t gfp) 59362306a36Sopenharmony_ci{ 59462306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 59562306a36Sopenharmony_ci struct sun50i_iommu *iommu = sun50i_domain->iommu; 59662306a36Sopenharmony_ci u32 pte_index; 59762306a36Sopenharmony_ci u32 *page_table, *pte_addr; 59862306a36Sopenharmony_ci int ret = 0; 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci page_table = sun50i_dte_get_page_table(sun50i_domain, iova, gfp); 60162306a36Sopenharmony_ci if (IS_ERR(page_table)) { 60262306a36Sopenharmony_ci ret = PTR_ERR(page_table); 60362306a36Sopenharmony_ci goto out; 60462306a36Sopenharmony_ci } 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci pte_index = sun50i_iova_get_pte_index(iova); 60762306a36Sopenharmony_ci pte_addr = &page_table[pte_index]; 60862306a36Sopenharmony_ci if (unlikely(sun50i_pte_is_page_valid(*pte_addr))) { 60962306a36Sopenharmony_ci phys_addr_t page_phys = sun50i_pte_get_page_address(*pte_addr); 61062306a36Sopenharmony_ci dev_err(iommu->dev, 61162306a36Sopenharmony_ci "iova %pad already mapped to %pa cannot remap to %pa prot: %#x\n", 61262306a36Sopenharmony_ci &iova, &page_phys, &paddr, prot); 61362306a36Sopenharmony_ci ret = -EBUSY; 61462306a36Sopenharmony_ci goto out; 61562306a36Sopenharmony_ci } 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci *pte_addr = sun50i_mk_pte(paddr, prot); 61862306a36Sopenharmony_ci sun50i_table_flush(sun50i_domain, pte_addr, 1); 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ciout: 62162306a36Sopenharmony_ci return ret; 62262306a36Sopenharmony_ci} 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_cistatic size_t sun50i_iommu_unmap(struct iommu_domain *domain, unsigned long iova, 62562306a36Sopenharmony_ci size_t size, struct iommu_iotlb_gather *gather) 62662306a36Sopenharmony_ci{ 62762306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 62862306a36Sopenharmony_ci phys_addr_t pt_phys; 62962306a36Sopenharmony_ci u32 *pte_addr; 63062306a36Sopenharmony_ci u32 dte; 63162306a36Sopenharmony_ci 63262306a36Sopenharmony_ci dte = sun50i_domain->dt[sun50i_iova_get_dte_index(iova)]; 63362306a36Sopenharmony_ci if (!sun50i_dte_is_pt_valid(dte)) 63462306a36Sopenharmony_ci return 0; 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_ci pt_phys = sun50i_dte_get_pt_address(dte); 63762306a36Sopenharmony_ci pte_addr = (u32 *)phys_to_virt(pt_phys) + sun50i_iova_get_pte_index(iova); 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci if (!sun50i_pte_is_page_valid(*pte_addr)) 64062306a36Sopenharmony_ci return 0; 64162306a36Sopenharmony_ci 64262306a36Sopenharmony_ci memset(pte_addr, 0, sizeof(*pte_addr)); 64362306a36Sopenharmony_ci sun50i_table_flush(sun50i_domain, pte_addr, 1); 64462306a36Sopenharmony_ci 64562306a36Sopenharmony_ci return SZ_4K; 64662306a36Sopenharmony_ci} 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_cistatic phys_addr_t sun50i_iommu_iova_to_phys(struct iommu_domain *domain, 64962306a36Sopenharmony_ci dma_addr_t iova) 65062306a36Sopenharmony_ci{ 65162306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 65262306a36Sopenharmony_ci phys_addr_t pt_phys; 65362306a36Sopenharmony_ci u32 *page_table; 65462306a36Sopenharmony_ci u32 dte, pte; 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci dte = sun50i_domain->dt[sun50i_iova_get_dte_index(iova)]; 65762306a36Sopenharmony_ci if (!sun50i_dte_is_pt_valid(dte)) 65862306a36Sopenharmony_ci return 0; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci pt_phys = sun50i_dte_get_pt_address(dte); 66162306a36Sopenharmony_ci page_table = (u32 *)phys_to_virt(pt_phys); 66262306a36Sopenharmony_ci pte = page_table[sun50i_iova_get_pte_index(iova)]; 66362306a36Sopenharmony_ci if (!sun50i_pte_is_page_valid(pte)) 66462306a36Sopenharmony_ci return 0; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci return sun50i_pte_get_page_address(pte) + 66762306a36Sopenharmony_ci sun50i_iova_get_page_offset(iova); 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_cistatic struct iommu_domain *sun50i_iommu_domain_alloc(unsigned type) 67162306a36Sopenharmony_ci{ 67262306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain; 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ci if (type != IOMMU_DOMAIN_DMA && 67562306a36Sopenharmony_ci type != IOMMU_DOMAIN_UNMANAGED) 67662306a36Sopenharmony_ci return NULL; 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ci sun50i_domain = kzalloc(sizeof(*sun50i_domain), GFP_KERNEL); 67962306a36Sopenharmony_ci if (!sun50i_domain) 68062306a36Sopenharmony_ci return NULL; 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci sun50i_domain->dt = (u32 *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 68362306a36Sopenharmony_ci get_order(DT_SIZE)); 68462306a36Sopenharmony_ci if (!sun50i_domain->dt) 68562306a36Sopenharmony_ci goto err_free_domain; 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci refcount_set(&sun50i_domain->refcnt, 1); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci sun50i_domain->domain.geometry.aperture_start = 0; 69062306a36Sopenharmony_ci sun50i_domain->domain.geometry.aperture_end = DMA_BIT_MASK(32); 69162306a36Sopenharmony_ci sun50i_domain->domain.geometry.force_aperture = true; 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci return &sun50i_domain->domain; 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_cierr_free_domain: 69662306a36Sopenharmony_ci kfree(sun50i_domain); 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci return NULL; 69962306a36Sopenharmony_ci} 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_cistatic void sun50i_iommu_domain_free(struct iommu_domain *domain) 70262306a36Sopenharmony_ci{ 70362306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 70462306a36Sopenharmony_ci 70562306a36Sopenharmony_ci free_pages((unsigned long)sun50i_domain->dt, get_order(DT_SIZE)); 70662306a36Sopenharmony_ci sun50i_domain->dt = NULL; 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci kfree(sun50i_domain); 70962306a36Sopenharmony_ci} 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_cistatic int sun50i_iommu_attach_domain(struct sun50i_iommu *iommu, 71262306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain) 71362306a36Sopenharmony_ci{ 71462306a36Sopenharmony_ci iommu->domain = &sun50i_domain->domain; 71562306a36Sopenharmony_ci sun50i_domain->iommu = iommu; 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci sun50i_domain->dt_dma = dma_map_single(iommu->dev, sun50i_domain->dt, 71862306a36Sopenharmony_ci DT_SIZE, DMA_TO_DEVICE); 71962306a36Sopenharmony_ci if (dma_mapping_error(iommu->dev, sun50i_domain->dt_dma)) { 72062306a36Sopenharmony_ci dev_err(iommu->dev, "Couldn't map L1 Page Table\n"); 72162306a36Sopenharmony_ci return -ENOMEM; 72262306a36Sopenharmony_ci } 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_ci return sun50i_iommu_enable(iommu); 72562306a36Sopenharmony_ci} 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_cistatic void sun50i_iommu_detach_domain(struct sun50i_iommu *iommu, 72862306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain) 72962306a36Sopenharmony_ci{ 73062306a36Sopenharmony_ci unsigned int i; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci for (i = 0; i < NUM_DT_ENTRIES; i++) { 73362306a36Sopenharmony_ci phys_addr_t pt_phys; 73462306a36Sopenharmony_ci u32 *page_table; 73562306a36Sopenharmony_ci u32 *dte_addr; 73662306a36Sopenharmony_ci u32 dte; 73762306a36Sopenharmony_ci 73862306a36Sopenharmony_ci dte_addr = &sun50i_domain->dt[i]; 73962306a36Sopenharmony_ci dte = *dte_addr; 74062306a36Sopenharmony_ci if (!sun50i_dte_is_pt_valid(dte)) 74162306a36Sopenharmony_ci continue; 74262306a36Sopenharmony_ci 74362306a36Sopenharmony_ci memset(dte_addr, 0, sizeof(*dte_addr)); 74462306a36Sopenharmony_ci sun50i_table_flush(sun50i_domain, dte_addr, 1); 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci pt_phys = sun50i_dte_get_pt_address(dte); 74762306a36Sopenharmony_ci page_table = phys_to_virt(pt_phys); 74862306a36Sopenharmony_ci sun50i_iommu_free_page_table(iommu, page_table); 74962306a36Sopenharmony_ci } 75062306a36Sopenharmony_ci 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci sun50i_iommu_disable(iommu); 75362306a36Sopenharmony_ci 75462306a36Sopenharmony_ci dma_unmap_single(iommu->dev, virt_to_phys(sun50i_domain->dt), 75562306a36Sopenharmony_ci DT_SIZE, DMA_TO_DEVICE); 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci iommu->domain = NULL; 75862306a36Sopenharmony_ci} 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_cistatic void sun50i_iommu_detach_device(struct iommu_domain *domain, 76162306a36Sopenharmony_ci struct device *dev) 76262306a36Sopenharmony_ci{ 76362306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 76462306a36Sopenharmony_ci struct sun50i_iommu *iommu = dev_iommu_priv_get(dev); 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci dev_dbg(dev, "Detaching from IOMMU domain\n"); 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci if (iommu->domain != domain) 76962306a36Sopenharmony_ci return; 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci if (refcount_dec_and_test(&sun50i_domain->refcnt)) 77262306a36Sopenharmony_ci sun50i_iommu_detach_domain(iommu, sun50i_domain); 77362306a36Sopenharmony_ci} 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_cistatic int sun50i_iommu_attach_device(struct iommu_domain *domain, 77662306a36Sopenharmony_ci struct device *dev) 77762306a36Sopenharmony_ci{ 77862306a36Sopenharmony_ci struct sun50i_iommu_domain *sun50i_domain = to_sun50i_domain(domain); 77962306a36Sopenharmony_ci struct sun50i_iommu *iommu; 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci iommu = sun50i_iommu_from_dev(dev); 78262306a36Sopenharmony_ci if (!iommu) 78362306a36Sopenharmony_ci return -ENODEV; 78462306a36Sopenharmony_ci 78562306a36Sopenharmony_ci dev_dbg(dev, "Attaching to IOMMU domain\n"); 78662306a36Sopenharmony_ci 78762306a36Sopenharmony_ci refcount_inc(&sun50i_domain->refcnt); 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci if (iommu->domain == domain) 79062306a36Sopenharmony_ci return 0; 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci if (iommu->domain) 79362306a36Sopenharmony_ci sun50i_iommu_detach_device(iommu->domain, dev); 79462306a36Sopenharmony_ci 79562306a36Sopenharmony_ci sun50i_iommu_attach_domain(iommu, sun50i_domain); 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci return 0; 79862306a36Sopenharmony_ci} 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_cistatic struct iommu_device *sun50i_iommu_probe_device(struct device *dev) 80162306a36Sopenharmony_ci{ 80262306a36Sopenharmony_ci struct sun50i_iommu *iommu; 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci iommu = sun50i_iommu_from_dev(dev); 80562306a36Sopenharmony_ci if (!iommu) 80662306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci return &iommu->iommu; 80962306a36Sopenharmony_ci} 81062306a36Sopenharmony_ci 81162306a36Sopenharmony_cistatic struct iommu_group *sun50i_iommu_device_group(struct device *dev) 81262306a36Sopenharmony_ci{ 81362306a36Sopenharmony_ci struct sun50i_iommu *iommu = sun50i_iommu_from_dev(dev); 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci return iommu_group_ref_get(iommu->group); 81662306a36Sopenharmony_ci} 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_cistatic int sun50i_iommu_of_xlate(struct device *dev, 81962306a36Sopenharmony_ci struct of_phandle_args *args) 82062306a36Sopenharmony_ci{ 82162306a36Sopenharmony_ci struct platform_device *iommu_pdev = of_find_device_by_node(args->np); 82262306a36Sopenharmony_ci unsigned id = args->args[0]; 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_ci dev_iommu_priv_set(dev, platform_get_drvdata(iommu_pdev)); 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ci return iommu_fwspec_add_ids(dev, &id, 1); 82762306a36Sopenharmony_ci} 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_cistatic const struct iommu_ops sun50i_iommu_ops = { 83062306a36Sopenharmony_ci .pgsize_bitmap = SZ_4K, 83162306a36Sopenharmony_ci .device_group = sun50i_iommu_device_group, 83262306a36Sopenharmony_ci .domain_alloc = sun50i_iommu_domain_alloc, 83362306a36Sopenharmony_ci .of_xlate = sun50i_iommu_of_xlate, 83462306a36Sopenharmony_ci .probe_device = sun50i_iommu_probe_device, 83562306a36Sopenharmony_ci .default_domain_ops = &(const struct iommu_domain_ops) { 83662306a36Sopenharmony_ci .attach_dev = sun50i_iommu_attach_device, 83762306a36Sopenharmony_ci .flush_iotlb_all = sun50i_iommu_flush_iotlb_all, 83862306a36Sopenharmony_ci .iotlb_sync_map = sun50i_iommu_iotlb_sync_map, 83962306a36Sopenharmony_ci .iotlb_sync = sun50i_iommu_iotlb_sync, 84062306a36Sopenharmony_ci .iova_to_phys = sun50i_iommu_iova_to_phys, 84162306a36Sopenharmony_ci .map = sun50i_iommu_map, 84262306a36Sopenharmony_ci .unmap = sun50i_iommu_unmap, 84362306a36Sopenharmony_ci .free = sun50i_iommu_domain_free, 84462306a36Sopenharmony_ci } 84562306a36Sopenharmony_ci}; 84662306a36Sopenharmony_ci 84762306a36Sopenharmony_cistatic void sun50i_iommu_report_fault(struct sun50i_iommu *iommu, 84862306a36Sopenharmony_ci unsigned master, phys_addr_t iova, 84962306a36Sopenharmony_ci unsigned prot) 85062306a36Sopenharmony_ci{ 85162306a36Sopenharmony_ci dev_err(iommu->dev, "Page fault for %pad (master %d, dir %s)\n", 85262306a36Sopenharmony_ci &iova, master, (prot == IOMMU_FAULT_WRITE) ? "wr" : "rd"); 85362306a36Sopenharmony_ci 85462306a36Sopenharmony_ci if (iommu->domain) 85562306a36Sopenharmony_ci report_iommu_fault(iommu->domain, iommu->dev, iova, prot); 85662306a36Sopenharmony_ci else 85762306a36Sopenharmony_ci dev_err(iommu->dev, "Page fault while iommu not attached to any domain?\n"); 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci sun50i_iommu_zap_range(iommu, iova, SPAGE_SIZE); 86062306a36Sopenharmony_ci} 86162306a36Sopenharmony_ci 86262306a36Sopenharmony_cistatic phys_addr_t sun50i_iommu_handle_pt_irq(struct sun50i_iommu *iommu, 86362306a36Sopenharmony_ci unsigned addr_reg, 86462306a36Sopenharmony_ci unsigned blame_reg) 86562306a36Sopenharmony_ci{ 86662306a36Sopenharmony_ci phys_addr_t iova; 86762306a36Sopenharmony_ci unsigned master; 86862306a36Sopenharmony_ci u32 blame; 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci assert_spin_locked(&iommu->iommu_lock); 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci iova = iommu_read(iommu, addr_reg); 87362306a36Sopenharmony_ci blame = iommu_read(iommu, blame_reg); 87462306a36Sopenharmony_ci master = ilog2(blame & IOMMU_INT_MASTER_MASK); 87562306a36Sopenharmony_ci 87662306a36Sopenharmony_ci /* 87762306a36Sopenharmony_ci * If the address is not in the page table, we can't get what 87862306a36Sopenharmony_ci * operation triggered the fault. Assume it's a read 87962306a36Sopenharmony_ci * operation. 88062306a36Sopenharmony_ci */ 88162306a36Sopenharmony_ci sun50i_iommu_report_fault(iommu, master, iova, IOMMU_FAULT_READ); 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci return iova; 88462306a36Sopenharmony_ci} 88562306a36Sopenharmony_ci 88662306a36Sopenharmony_cistatic phys_addr_t sun50i_iommu_handle_perm_irq(struct sun50i_iommu *iommu) 88762306a36Sopenharmony_ci{ 88862306a36Sopenharmony_ci enum sun50i_iommu_aci aci; 88962306a36Sopenharmony_ci phys_addr_t iova; 89062306a36Sopenharmony_ci unsigned master; 89162306a36Sopenharmony_ci unsigned dir; 89262306a36Sopenharmony_ci u32 blame; 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci assert_spin_locked(&iommu->iommu_lock); 89562306a36Sopenharmony_ci 89662306a36Sopenharmony_ci blame = iommu_read(iommu, IOMMU_INT_STA_REG); 89762306a36Sopenharmony_ci master = ilog2(blame & IOMMU_INT_MASTER_MASK); 89862306a36Sopenharmony_ci iova = iommu_read(iommu, IOMMU_INT_ERR_ADDR_REG(master)); 89962306a36Sopenharmony_ci aci = sun50i_get_pte_aci(iommu_read(iommu, 90062306a36Sopenharmony_ci IOMMU_INT_ERR_DATA_REG(master))); 90162306a36Sopenharmony_ci 90262306a36Sopenharmony_ci switch (aci) { 90362306a36Sopenharmony_ci /* 90462306a36Sopenharmony_ci * If we are in the read-only domain, then it means we 90562306a36Sopenharmony_ci * tried to write. 90662306a36Sopenharmony_ci */ 90762306a36Sopenharmony_ci case SUN50I_IOMMU_ACI_RD: 90862306a36Sopenharmony_ci dir = IOMMU_FAULT_WRITE; 90962306a36Sopenharmony_ci break; 91062306a36Sopenharmony_ci 91162306a36Sopenharmony_ci /* 91262306a36Sopenharmony_ci * If we are in the write-only domain, then it means 91362306a36Sopenharmony_ci * we tried to read. 91462306a36Sopenharmony_ci */ 91562306a36Sopenharmony_ci case SUN50I_IOMMU_ACI_WR: 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci /* 91862306a36Sopenharmony_ci * If we are in the domain without any permission, we 91962306a36Sopenharmony_ci * can't really tell. Let's default to a read 92062306a36Sopenharmony_ci * operation. 92162306a36Sopenharmony_ci */ 92262306a36Sopenharmony_ci case SUN50I_IOMMU_ACI_NONE: 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci /* WTF? */ 92562306a36Sopenharmony_ci case SUN50I_IOMMU_ACI_RD_WR: 92662306a36Sopenharmony_ci default: 92762306a36Sopenharmony_ci dir = IOMMU_FAULT_READ; 92862306a36Sopenharmony_ci break; 92962306a36Sopenharmony_ci } 93062306a36Sopenharmony_ci 93162306a36Sopenharmony_ci /* 93262306a36Sopenharmony_ci * If the address is not in the page table, we can't get what 93362306a36Sopenharmony_ci * operation triggered the fault. Assume it's a read 93462306a36Sopenharmony_ci * operation. 93562306a36Sopenharmony_ci */ 93662306a36Sopenharmony_ci sun50i_iommu_report_fault(iommu, master, iova, dir); 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_ci return iova; 93962306a36Sopenharmony_ci} 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_cistatic irqreturn_t sun50i_iommu_irq(int irq, void *dev_id) 94262306a36Sopenharmony_ci{ 94362306a36Sopenharmony_ci u32 status, l1_status, l2_status, resets; 94462306a36Sopenharmony_ci struct sun50i_iommu *iommu = dev_id; 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci spin_lock(&iommu->iommu_lock); 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci status = iommu_read(iommu, IOMMU_INT_STA_REG); 94962306a36Sopenharmony_ci if (!(status & IOMMU_INT_MASK)) { 95062306a36Sopenharmony_ci spin_unlock(&iommu->iommu_lock); 95162306a36Sopenharmony_ci return IRQ_NONE; 95262306a36Sopenharmony_ci } 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_ci l1_status = iommu_read(iommu, IOMMU_L1PG_INT_REG); 95562306a36Sopenharmony_ci l2_status = iommu_read(iommu, IOMMU_L2PG_INT_REG); 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_ci if (status & IOMMU_INT_INVALID_L2PG) 95862306a36Sopenharmony_ci sun50i_iommu_handle_pt_irq(iommu, 95962306a36Sopenharmony_ci IOMMU_INT_ERR_ADDR_L2_REG, 96062306a36Sopenharmony_ci IOMMU_L2PG_INT_REG); 96162306a36Sopenharmony_ci else if (status & IOMMU_INT_INVALID_L1PG) 96262306a36Sopenharmony_ci sun50i_iommu_handle_pt_irq(iommu, 96362306a36Sopenharmony_ci IOMMU_INT_ERR_ADDR_L1_REG, 96462306a36Sopenharmony_ci IOMMU_L1PG_INT_REG); 96562306a36Sopenharmony_ci else 96662306a36Sopenharmony_ci sun50i_iommu_handle_perm_irq(iommu); 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_ci iommu_write(iommu, IOMMU_INT_CLR_REG, status); 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_ci resets = (status | l1_status | l2_status) & IOMMU_INT_MASTER_MASK; 97162306a36Sopenharmony_ci iommu_write(iommu, IOMMU_RESET_REG, ~resets); 97262306a36Sopenharmony_ci iommu_write(iommu, IOMMU_RESET_REG, IOMMU_RESET_RELEASE_ALL); 97362306a36Sopenharmony_ci 97462306a36Sopenharmony_ci spin_unlock(&iommu->iommu_lock); 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci return IRQ_HANDLED; 97762306a36Sopenharmony_ci} 97862306a36Sopenharmony_ci 97962306a36Sopenharmony_cistatic int sun50i_iommu_probe(struct platform_device *pdev) 98062306a36Sopenharmony_ci{ 98162306a36Sopenharmony_ci struct sun50i_iommu *iommu; 98262306a36Sopenharmony_ci int ret, irq; 98362306a36Sopenharmony_ci 98462306a36Sopenharmony_ci iommu = devm_kzalloc(&pdev->dev, sizeof(*iommu), GFP_KERNEL); 98562306a36Sopenharmony_ci if (!iommu) 98662306a36Sopenharmony_ci return -ENOMEM; 98762306a36Sopenharmony_ci spin_lock_init(&iommu->iommu_lock); 98862306a36Sopenharmony_ci platform_set_drvdata(pdev, iommu); 98962306a36Sopenharmony_ci iommu->dev = &pdev->dev; 99062306a36Sopenharmony_ci 99162306a36Sopenharmony_ci iommu->pt_pool = kmem_cache_create(dev_name(&pdev->dev), 99262306a36Sopenharmony_ci PT_SIZE, PT_SIZE, 99362306a36Sopenharmony_ci SLAB_HWCACHE_ALIGN, 99462306a36Sopenharmony_ci NULL); 99562306a36Sopenharmony_ci if (!iommu->pt_pool) 99662306a36Sopenharmony_ci return -ENOMEM; 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci iommu->group = iommu_group_alloc(); 99962306a36Sopenharmony_ci if (IS_ERR(iommu->group)) { 100062306a36Sopenharmony_ci ret = PTR_ERR(iommu->group); 100162306a36Sopenharmony_ci goto err_free_cache; 100262306a36Sopenharmony_ci } 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_ci iommu->base = devm_platform_ioremap_resource(pdev, 0); 100562306a36Sopenharmony_ci if (IS_ERR(iommu->base)) { 100662306a36Sopenharmony_ci ret = PTR_ERR(iommu->base); 100762306a36Sopenharmony_ci goto err_free_group; 100862306a36Sopenharmony_ci } 100962306a36Sopenharmony_ci 101062306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 101162306a36Sopenharmony_ci if (irq < 0) { 101262306a36Sopenharmony_ci ret = irq; 101362306a36Sopenharmony_ci goto err_free_group; 101462306a36Sopenharmony_ci } 101562306a36Sopenharmony_ci 101662306a36Sopenharmony_ci iommu->clk = devm_clk_get(&pdev->dev, NULL); 101762306a36Sopenharmony_ci if (IS_ERR(iommu->clk)) { 101862306a36Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get our clock.\n"); 101962306a36Sopenharmony_ci ret = PTR_ERR(iommu->clk); 102062306a36Sopenharmony_ci goto err_free_group; 102162306a36Sopenharmony_ci } 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci iommu->reset = devm_reset_control_get(&pdev->dev, NULL); 102462306a36Sopenharmony_ci if (IS_ERR(iommu->reset)) { 102562306a36Sopenharmony_ci dev_err(&pdev->dev, "Couldn't get our reset line.\n"); 102662306a36Sopenharmony_ci ret = PTR_ERR(iommu->reset); 102762306a36Sopenharmony_ci goto err_free_group; 102862306a36Sopenharmony_ci } 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_ci ret = iommu_device_sysfs_add(&iommu->iommu, &pdev->dev, 103162306a36Sopenharmony_ci NULL, dev_name(&pdev->dev)); 103262306a36Sopenharmony_ci if (ret) 103362306a36Sopenharmony_ci goto err_free_group; 103462306a36Sopenharmony_ci 103562306a36Sopenharmony_ci ret = iommu_device_register(&iommu->iommu, &sun50i_iommu_ops, &pdev->dev); 103662306a36Sopenharmony_ci if (ret) 103762306a36Sopenharmony_ci goto err_remove_sysfs; 103862306a36Sopenharmony_ci 103962306a36Sopenharmony_ci ret = devm_request_irq(&pdev->dev, irq, sun50i_iommu_irq, 0, 104062306a36Sopenharmony_ci dev_name(&pdev->dev), iommu); 104162306a36Sopenharmony_ci if (ret < 0) 104262306a36Sopenharmony_ci goto err_unregister; 104362306a36Sopenharmony_ci 104462306a36Sopenharmony_ci return 0; 104562306a36Sopenharmony_ci 104662306a36Sopenharmony_cierr_unregister: 104762306a36Sopenharmony_ci iommu_device_unregister(&iommu->iommu); 104862306a36Sopenharmony_ci 104962306a36Sopenharmony_cierr_remove_sysfs: 105062306a36Sopenharmony_ci iommu_device_sysfs_remove(&iommu->iommu); 105162306a36Sopenharmony_ci 105262306a36Sopenharmony_cierr_free_group: 105362306a36Sopenharmony_ci iommu_group_put(iommu->group); 105462306a36Sopenharmony_ci 105562306a36Sopenharmony_cierr_free_cache: 105662306a36Sopenharmony_ci kmem_cache_destroy(iommu->pt_pool); 105762306a36Sopenharmony_ci 105862306a36Sopenharmony_ci return ret; 105962306a36Sopenharmony_ci} 106062306a36Sopenharmony_ci 106162306a36Sopenharmony_cistatic const struct of_device_id sun50i_iommu_dt[] = { 106262306a36Sopenharmony_ci { .compatible = "allwinner,sun50i-h6-iommu", }, 106362306a36Sopenharmony_ci { /* sentinel */ }, 106462306a36Sopenharmony_ci}; 106562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sun50i_iommu_dt); 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_cistatic struct platform_driver sun50i_iommu_driver = { 106862306a36Sopenharmony_ci .driver = { 106962306a36Sopenharmony_ci .name = "sun50i-iommu", 107062306a36Sopenharmony_ci .of_match_table = sun50i_iommu_dt, 107162306a36Sopenharmony_ci .suppress_bind_attrs = true, 107262306a36Sopenharmony_ci } 107362306a36Sopenharmony_ci}; 107462306a36Sopenharmony_cibuiltin_platform_driver_probe(sun50i_iommu_driver, sun50i_iommu_probe); 107562306a36Sopenharmony_ci 107662306a36Sopenharmony_ciMODULE_DESCRIPTION("Allwinner H6 IOMMU driver"); 107762306a36Sopenharmony_ciMODULE_AUTHOR("Maxime Ripard <maxime@cerno.tech>"); 107862306a36Sopenharmony_ciMODULE_AUTHOR("zhuxianbin <zhuxianbin@allwinnertech.com>"); 1079