162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * omap iommu: tlb and pagetable primitives 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2008-2010 Nokia Corporation 662306a36Sopenharmony_ci * Copyright (C) 2013-2017 Texas Instruments Incorporated - https://www.ti.com/ 762306a36Sopenharmony_ci * 862306a36Sopenharmony_ci * Written by Hiroshi DOYU <Hiroshi.DOYU@nokia.com>, 962306a36Sopenharmony_ci * Paul Mundt and Toshihiro Kobayashi 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/dma-mapping.h> 1362306a36Sopenharmony_ci#include <linux/err.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <linux/interrupt.h> 1662306a36Sopenharmony_ci#include <linux/ioport.h> 1762306a36Sopenharmony_ci#include <linux/platform_device.h> 1862306a36Sopenharmony_ci#include <linux/iommu.h> 1962306a36Sopenharmony_ci#include <linux/omap-iommu.h> 2062306a36Sopenharmony_ci#include <linux/mutex.h> 2162306a36Sopenharmony_ci#include <linux/spinlock.h> 2262306a36Sopenharmony_ci#include <linux/io.h> 2362306a36Sopenharmony_ci#include <linux/pm_runtime.h> 2462306a36Sopenharmony_ci#include <linux/of.h> 2562306a36Sopenharmony_ci#include <linux/of_irq.h> 2662306a36Sopenharmony_ci#include <linux/of_platform.h> 2762306a36Sopenharmony_ci#include <linux/regmap.h> 2862306a36Sopenharmony_ci#include <linux/mfd/syscon.h> 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#include <linux/platform_data/iommu-omap.h> 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#include "omap-iopgtable.h" 3362306a36Sopenharmony_ci#include "omap-iommu.h" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistatic const struct iommu_ops omap_iommu_ops; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci#define to_iommu(dev) ((struct omap_iommu *)dev_get_drvdata(dev)) 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* bitmap of the page sizes currently supported */ 4062306a36Sopenharmony_ci#define OMAP_IOMMU_PGSIZES (SZ_4K | SZ_64K | SZ_1M | SZ_16M) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define MMU_LOCK_BASE_SHIFT 10 4362306a36Sopenharmony_ci#define MMU_LOCK_BASE_MASK (0x1f << MMU_LOCK_BASE_SHIFT) 4462306a36Sopenharmony_ci#define MMU_LOCK_BASE(x) \ 4562306a36Sopenharmony_ci ((x & MMU_LOCK_BASE_MASK) >> MMU_LOCK_BASE_SHIFT) 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci#define MMU_LOCK_VICT_SHIFT 4 4862306a36Sopenharmony_ci#define MMU_LOCK_VICT_MASK (0x1f << MMU_LOCK_VICT_SHIFT) 4962306a36Sopenharmony_ci#define MMU_LOCK_VICT(x) \ 5062306a36Sopenharmony_ci ((x & MMU_LOCK_VICT_MASK) >> MMU_LOCK_VICT_SHIFT) 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistatic struct platform_driver omap_iommu_driver; 5362306a36Sopenharmony_cistatic struct kmem_cache *iopte_cachep; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci/** 5662306a36Sopenharmony_ci * to_omap_domain - Get struct omap_iommu_domain from generic iommu_domain 5762306a36Sopenharmony_ci * @dom: generic iommu domain handle 5862306a36Sopenharmony_ci **/ 5962306a36Sopenharmony_cistatic struct omap_iommu_domain *to_omap_domain(struct iommu_domain *dom) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci return container_of(dom, struct omap_iommu_domain, domain); 6262306a36Sopenharmony_ci} 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci/** 6562306a36Sopenharmony_ci * omap_iommu_save_ctx - Save registers for pm off-mode support 6662306a36Sopenharmony_ci * @dev: client device 6762306a36Sopenharmony_ci * 6862306a36Sopenharmony_ci * This should be treated as an deprecated API. It is preserved only 6962306a36Sopenharmony_ci * to maintain existing functionality for OMAP3 ISP driver. 7062306a36Sopenharmony_ci **/ 7162306a36Sopenharmony_civoid omap_iommu_save_ctx(struct device *dev) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data = dev_iommu_priv_get(dev); 7462306a36Sopenharmony_ci struct omap_iommu *obj; 7562306a36Sopenharmony_ci u32 *p; 7662306a36Sopenharmony_ci int i; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (!arch_data) 7962306a36Sopenharmony_ci return; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci while (arch_data->iommu_dev) { 8262306a36Sopenharmony_ci obj = arch_data->iommu_dev; 8362306a36Sopenharmony_ci p = obj->ctx; 8462306a36Sopenharmony_ci for (i = 0; i < (MMU_REG_SIZE / sizeof(u32)); i++) { 8562306a36Sopenharmony_ci p[i] = iommu_read_reg(obj, i * sizeof(u32)); 8662306a36Sopenharmony_ci dev_dbg(obj->dev, "%s\t[%02d] %08x\n", __func__, i, 8762306a36Sopenharmony_ci p[i]); 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci arch_data++; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci} 9262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(omap_iommu_save_ctx); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/** 9562306a36Sopenharmony_ci * omap_iommu_restore_ctx - Restore registers for pm off-mode support 9662306a36Sopenharmony_ci * @dev: client device 9762306a36Sopenharmony_ci * 9862306a36Sopenharmony_ci * This should be treated as an deprecated API. It is preserved only 9962306a36Sopenharmony_ci * to maintain existing functionality for OMAP3 ISP driver. 10062306a36Sopenharmony_ci **/ 10162306a36Sopenharmony_civoid omap_iommu_restore_ctx(struct device *dev) 10262306a36Sopenharmony_ci{ 10362306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data = dev_iommu_priv_get(dev); 10462306a36Sopenharmony_ci struct omap_iommu *obj; 10562306a36Sopenharmony_ci u32 *p; 10662306a36Sopenharmony_ci int i; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci if (!arch_data) 10962306a36Sopenharmony_ci return; 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci while (arch_data->iommu_dev) { 11262306a36Sopenharmony_ci obj = arch_data->iommu_dev; 11362306a36Sopenharmony_ci p = obj->ctx; 11462306a36Sopenharmony_ci for (i = 0; i < (MMU_REG_SIZE / sizeof(u32)); i++) { 11562306a36Sopenharmony_ci iommu_write_reg(obj, p[i], i * sizeof(u32)); 11662306a36Sopenharmony_ci dev_dbg(obj->dev, "%s\t[%02d] %08x\n", __func__, i, 11762306a36Sopenharmony_ci p[i]); 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci arch_data++; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(omap_iommu_restore_ctx); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic void dra7_cfg_dspsys_mmu(struct omap_iommu *obj, bool enable) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci u32 val, mask; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci if (!obj->syscfg) 12962306a36Sopenharmony_ci return; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci mask = (1 << (obj->id * DSP_SYS_MMU_CONFIG_EN_SHIFT)); 13262306a36Sopenharmony_ci val = enable ? mask : 0; 13362306a36Sopenharmony_ci regmap_update_bits(obj->syscfg, DSP_SYS_MMU_CONFIG, mask, val); 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_cistatic void __iommu_set_twl(struct omap_iommu *obj, bool on) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci u32 l = iommu_read_reg(obj, MMU_CNTL); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (on) 14162306a36Sopenharmony_ci iommu_write_reg(obj, MMU_IRQ_TWL_MASK, MMU_IRQENABLE); 14262306a36Sopenharmony_ci else 14362306a36Sopenharmony_ci iommu_write_reg(obj, MMU_IRQ_TLB_MISS_MASK, MMU_IRQENABLE); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci l &= ~MMU_CNTL_MASK; 14662306a36Sopenharmony_ci if (on) 14762306a36Sopenharmony_ci l |= (MMU_CNTL_MMU_EN | MMU_CNTL_TWL_EN); 14862306a36Sopenharmony_ci else 14962306a36Sopenharmony_ci l |= (MMU_CNTL_MMU_EN); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci iommu_write_reg(obj, l, MMU_CNTL); 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic int omap2_iommu_enable(struct omap_iommu *obj) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci u32 l, pa; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci if (!obj->iopgd || !IS_ALIGNED((unsigned long)obj->iopgd, SZ_16K)) 15962306a36Sopenharmony_ci return -EINVAL; 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci pa = virt_to_phys(obj->iopgd); 16262306a36Sopenharmony_ci if (!IS_ALIGNED(pa, SZ_16K)) 16362306a36Sopenharmony_ci return -EINVAL; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci l = iommu_read_reg(obj, MMU_REVISION); 16662306a36Sopenharmony_ci dev_info(obj->dev, "%s: version %d.%d\n", obj->name, 16762306a36Sopenharmony_ci (l >> 4) & 0xf, l & 0xf); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci iommu_write_reg(obj, pa, MMU_TTB); 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci dra7_cfg_dspsys_mmu(obj, true); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (obj->has_bus_err_back) 17462306a36Sopenharmony_ci iommu_write_reg(obj, MMU_GP_REG_BUS_ERR_BACK_EN, MMU_GP_REG); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci __iommu_set_twl(obj, true); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_cistatic void omap2_iommu_disable(struct omap_iommu *obj) 18262306a36Sopenharmony_ci{ 18362306a36Sopenharmony_ci u32 l = iommu_read_reg(obj, MMU_CNTL); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci l &= ~MMU_CNTL_MASK; 18662306a36Sopenharmony_ci iommu_write_reg(obj, l, MMU_CNTL); 18762306a36Sopenharmony_ci dra7_cfg_dspsys_mmu(obj, false); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci dev_dbg(obj->dev, "%s is shutting down\n", obj->name); 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic int iommu_enable(struct omap_iommu *obj) 19362306a36Sopenharmony_ci{ 19462306a36Sopenharmony_ci int ret; 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci ret = pm_runtime_get_sync(obj->dev); 19762306a36Sopenharmony_ci if (ret < 0) 19862306a36Sopenharmony_ci pm_runtime_put_noidle(obj->dev); 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci return ret < 0 ? ret : 0; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic void iommu_disable(struct omap_iommu *obj) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci pm_runtime_put_sync(obj->dev); 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_ci/* 20962306a36Sopenharmony_ci * TLB operations 21062306a36Sopenharmony_ci */ 21162306a36Sopenharmony_cistatic u32 iotlb_cr_to_virt(struct cr_regs *cr) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci u32 page_size = cr->cam & MMU_CAM_PGSZ_MASK; 21462306a36Sopenharmony_ci u32 mask = get_cam_va_mask(cr->cam & page_size); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci return cr->cam & mask; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic u32 get_iopte_attr(struct iotlb_entry *e) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci u32 attr; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci attr = e->mixed << 5; 22462306a36Sopenharmony_ci attr |= e->endian; 22562306a36Sopenharmony_ci attr |= e->elsz >> 3; 22662306a36Sopenharmony_ci attr <<= (((e->pgsz == MMU_CAM_PGSZ_4K) || 22762306a36Sopenharmony_ci (e->pgsz == MMU_CAM_PGSZ_64K)) ? 0 : 6); 22862306a36Sopenharmony_ci return attr; 22962306a36Sopenharmony_ci} 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_cistatic u32 iommu_report_fault(struct omap_iommu *obj, u32 *da) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci u32 status, fault_addr; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci status = iommu_read_reg(obj, MMU_IRQSTATUS); 23662306a36Sopenharmony_ci status &= MMU_IRQ_MASK; 23762306a36Sopenharmony_ci if (!status) { 23862306a36Sopenharmony_ci *da = 0; 23962306a36Sopenharmony_ci return 0; 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci fault_addr = iommu_read_reg(obj, MMU_FAULT_AD); 24362306a36Sopenharmony_ci *da = fault_addr; 24462306a36Sopenharmony_ci 24562306a36Sopenharmony_ci iommu_write_reg(obj, status, MMU_IRQSTATUS); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return status; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_civoid iotlb_lock_get(struct omap_iommu *obj, struct iotlb_lock *l) 25162306a36Sopenharmony_ci{ 25262306a36Sopenharmony_ci u32 val; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci val = iommu_read_reg(obj, MMU_LOCK); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci l->base = MMU_LOCK_BASE(val); 25762306a36Sopenharmony_ci l->vict = MMU_LOCK_VICT(val); 25862306a36Sopenharmony_ci} 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_civoid iotlb_lock_set(struct omap_iommu *obj, struct iotlb_lock *l) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci u32 val; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci val = (l->base << MMU_LOCK_BASE_SHIFT); 26562306a36Sopenharmony_ci val |= (l->vict << MMU_LOCK_VICT_SHIFT); 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci iommu_write_reg(obj, val, MMU_LOCK); 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic void iotlb_read_cr(struct omap_iommu *obj, struct cr_regs *cr) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci cr->cam = iommu_read_reg(obj, MMU_READ_CAM); 27362306a36Sopenharmony_ci cr->ram = iommu_read_reg(obj, MMU_READ_RAM); 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic void iotlb_load_cr(struct omap_iommu *obj, struct cr_regs *cr) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci iommu_write_reg(obj, cr->cam | MMU_CAM_V, MMU_CAM); 27962306a36Sopenharmony_ci iommu_write_reg(obj, cr->ram, MMU_RAM); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci iommu_write_reg(obj, 1, MMU_FLUSH_ENTRY); 28262306a36Sopenharmony_ci iommu_write_reg(obj, 1, MMU_LD_TLB); 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci/* only used in iotlb iteration for-loop */ 28662306a36Sopenharmony_cistruct cr_regs __iotlb_read_cr(struct omap_iommu *obj, int n) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct cr_regs cr; 28962306a36Sopenharmony_ci struct iotlb_lock l; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci iotlb_lock_get(obj, &l); 29262306a36Sopenharmony_ci l.vict = n; 29362306a36Sopenharmony_ci iotlb_lock_set(obj, &l); 29462306a36Sopenharmony_ci iotlb_read_cr(obj, &cr); 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci return cr; 29762306a36Sopenharmony_ci} 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci#ifdef PREFETCH_IOTLB 30062306a36Sopenharmony_cistatic struct cr_regs *iotlb_alloc_cr(struct omap_iommu *obj, 30162306a36Sopenharmony_ci struct iotlb_entry *e) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci struct cr_regs *cr; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci if (!e) 30662306a36Sopenharmony_ci return NULL; 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci if (e->da & ~(get_cam_va_mask(e->pgsz))) { 30962306a36Sopenharmony_ci dev_err(obj->dev, "%s:\twrong alignment: %08x\n", __func__, 31062306a36Sopenharmony_ci e->da); 31162306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci cr = kmalloc(sizeof(*cr), GFP_KERNEL); 31562306a36Sopenharmony_ci if (!cr) 31662306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci cr->cam = (e->da & MMU_CAM_VATAG_MASK) | e->prsvd | e->pgsz | e->valid; 31962306a36Sopenharmony_ci cr->ram = e->pa | e->endian | e->elsz | e->mixed; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_ci return cr; 32262306a36Sopenharmony_ci} 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci/** 32562306a36Sopenharmony_ci * load_iotlb_entry - Set an iommu tlb entry 32662306a36Sopenharmony_ci * @obj: target iommu 32762306a36Sopenharmony_ci * @e: an iommu tlb entry info 32862306a36Sopenharmony_ci **/ 32962306a36Sopenharmony_cistatic int load_iotlb_entry(struct omap_iommu *obj, struct iotlb_entry *e) 33062306a36Sopenharmony_ci{ 33162306a36Sopenharmony_ci int err = 0; 33262306a36Sopenharmony_ci struct iotlb_lock l; 33362306a36Sopenharmony_ci struct cr_regs *cr; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci if (!obj || !obj->nr_tlb_entries || !e) 33662306a36Sopenharmony_ci return -EINVAL; 33762306a36Sopenharmony_ci 33862306a36Sopenharmony_ci pm_runtime_get_sync(obj->dev); 33962306a36Sopenharmony_ci 34062306a36Sopenharmony_ci iotlb_lock_get(obj, &l); 34162306a36Sopenharmony_ci if (l.base == obj->nr_tlb_entries) { 34262306a36Sopenharmony_ci dev_warn(obj->dev, "%s: preserve entries full\n", __func__); 34362306a36Sopenharmony_ci err = -EBUSY; 34462306a36Sopenharmony_ci goto out; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci if (!e->prsvd) { 34762306a36Sopenharmony_ci int i; 34862306a36Sopenharmony_ci struct cr_regs tmp; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci for_each_iotlb_cr(obj, obj->nr_tlb_entries, i, tmp) 35162306a36Sopenharmony_ci if (!iotlb_cr_valid(&tmp)) 35262306a36Sopenharmony_ci break; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci if (i == obj->nr_tlb_entries) { 35562306a36Sopenharmony_ci dev_dbg(obj->dev, "%s: full: no entry\n", __func__); 35662306a36Sopenharmony_ci err = -EBUSY; 35762306a36Sopenharmony_ci goto out; 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci iotlb_lock_get(obj, &l); 36162306a36Sopenharmony_ci } else { 36262306a36Sopenharmony_ci l.vict = l.base; 36362306a36Sopenharmony_ci iotlb_lock_set(obj, &l); 36462306a36Sopenharmony_ci } 36562306a36Sopenharmony_ci 36662306a36Sopenharmony_ci cr = iotlb_alloc_cr(obj, e); 36762306a36Sopenharmony_ci if (IS_ERR(cr)) { 36862306a36Sopenharmony_ci pm_runtime_put_sync(obj->dev); 36962306a36Sopenharmony_ci return PTR_ERR(cr); 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci iotlb_load_cr(obj, cr); 37362306a36Sopenharmony_ci kfree(cr); 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci if (e->prsvd) 37662306a36Sopenharmony_ci l.base++; 37762306a36Sopenharmony_ci /* increment victim for next tlb load */ 37862306a36Sopenharmony_ci if (++l.vict == obj->nr_tlb_entries) 37962306a36Sopenharmony_ci l.vict = l.base; 38062306a36Sopenharmony_ci iotlb_lock_set(obj, &l); 38162306a36Sopenharmony_ciout: 38262306a36Sopenharmony_ci pm_runtime_put_sync(obj->dev); 38362306a36Sopenharmony_ci return err; 38462306a36Sopenharmony_ci} 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci#else /* !PREFETCH_IOTLB */ 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_cistatic int load_iotlb_entry(struct omap_iommu *obj, struct iotlb_entry *e) 38962306a36Sopenharmony_ci{ 39062306a36Sopenharmony_ci return 0; 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci#endif /* !PREFETCH_IOTLB */ 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_cistatic int prefetch_iotlb_entry(struct omap_iommu *obj, struct iotlb_entry *e) 39662306a36Sopenharmony_ci{ 39762306a36Sopenharmony_ci return load_iotlb_entry(obj, e); 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci/** 40162306a36Sopenharmony_ci * flush_iotlb_page - Clear an iommu tlb entry 40262306a36Sopenharmony_ci * @obj: target iommu 40362306a36Sopenharmony_ci * @da: iommu device virtual address 40462306a36Sopenharmony_ci * 40562306a36Sopenharmony_ci * Clear an iommu tlb entry which includes 'da' address. 40662306a36Sopenharmony_ci **/ 40762306a36Sopenharmony_cistatic void flush_iotlb_page(struct omap_iommu *obj, u32 da) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci int i; 41062306a36Sopenharmony_ci struct cr_regs cr; 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci pm_runtime_get_sync(obj->dev); 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci for_each_iotlb_cr(obj, obj->nr_tlb_entries, i, cr) { 41562306a36Sopenharmony_ci u32 start; 41662306a36Sopenharmony_ci size_t bytes; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci if (!iotlb_cr_valid(&cr)) 41962306a36Sopenharmony_ci continue; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci start = iotlb_cr_to_virt(&cr); 42262306a36Sopenharmony_ci bytes = iopgsz_to_bytes(cr.cam & 3); 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci if ((start <= da) && (da < start + bytes)) { 42562306a36Sopenharmony_ci dev_dbg(obj->dev, "%s: %08x<=%08x(%zx)\n", 42662306a36Sopenharmony_ci __func__, start, da, bytes); 42762306a36Sopenharmony_ci iotlb_load_cr(obj, &cr); 42862306a36Sopenharmony_ci iommu_write_reg(obj, 1, MMU_FLUSH_ENTRY); 42962306a36Sopenharmony_ci break; 43062306a36Sopenharmony_ci } 43162306a36Sopenharmony_ci } 43262306a36Sopenharmony_ci pm_runtime_put_sync(obj->dev); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci if (i == obj->nr_tlb_entries) 43562306a36Sopenharmony_ci dev_dbg(obj->dev, "%s: no page for %08x\n", __func__, da); 43662306a36Sopenharmony_ci} 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci/** 43962306a36Sopenharmony_ci * flush_iotlb_all - Clear all iommu tlb entries 44062306a36Sopenharmony_ci * @obj: target iommu 44162306a36Sopenharmony_ci **/ 44262306a36Sopenharmony_cistatic void flush_iotlb_all(struct omap_iommu *obj) 44362306a36Sopenharmony_ci{ 44462306a36Sopenharmony_ci struct iotlb_lock l; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci pm_runtime_get_sync(obj->dev); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci l.base = 0; 44962306a36Sopenharmony_ci l.vict = 0; 45062306a36Sopenharmony_ci iotlb_lock_set(obj, &l); 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci iommu_write_reg(obj, 1, MMU_GFLUSH); 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci pm_runtime_put_sync(obj->dev); 45562306a36Sopenharmony_ci} 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci/* 45862306a36Sopenharmony_ci * H/W pagetable operations 45962306a36Sopenharmony_ci */ 46062306a36Sopenharmony_cistatic void flush_iopte_range(struct device *dev, dma_addr_t dma, 46162306a36Sopenharmony_ci unsigned long offset, int num_entries) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci size_t size = num_entries * sizeof(u32); 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci dma_sync_single_range_for_device(dev, dma, offset, size, DMA_TO_DEVICE); 46662306a36Sopenharmony_ci} 46762306a36Sopenharmony_ci 46862306a36Sopenharmony_cistatic void iopte_free(struct omap_iommu *obj, u32 *iopte, bool dma_valid) 46962306a36Sopenharmony_ci{ 47062306a36Sopenharmony_ci dma_addr_t pt_dma; 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_ci /* Note: freed iopte's must be clean ready for re-use */ 47362306a36Sopenharmony_ci if (iopte) { 47462306a36Sopenharmony_ci if (dma_valid) { 47562306a36Sopenharmony_ci pt_dma = virt_to_phys(iopte); 47662306a36Sopenharmony_ci dma_unmap_single(obj->dev, pt_dma, IOPTE_TABLE_SIZE, 47762306a36Sopenharmony_ci DMA_TO_DEVICE); 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci kmem_cache_free(iopte_cachep, iopte); 48162306a36Sopenharmony_ci } 48262306a36Sopenharmony_ci} 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_cistatic u32 *iopte_alloc(struct omap_iommu *obj, u32 *iopgd, 48562306a36Sopenharmony_ci dma_addr_t *pt_dma, u32 da) 48662306a36Sopenharmony_ci{ 48762306a36Sopenharmony_ci u32 *iopte; 48862306a36Sopenharmony_ci unsigned long offset = iopgd_index(da) * sizeof(da); 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci /* a table has already existed */ 49162306a36Sopenharmony_ci if (*iopgd) 49262306a36Sopenharmony_ci goto pte_ready; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci /* 49562306a36Sopenharmony_ci * do the allocation outside the page table lock 49662306a36Sopenharmony_ci */ 49762306a36Sopenharmony_ci spin_unlock(&obj->page_table_lock); 49862306a36Sopenharmony_ci iopte = kmem_cache_zalloc(iopte_cachep, GFP_KERNEL); 49962306a36Sopenharmony_ci spin_lock(&obj->page_table_lock); 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci if (!*iopgd) { 50262306a36Sopenharmony_ci if (!iopte) 50362306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_ci *pt_dma = dma_map_single(obj->dev, iopte, IOPTE_TABLE_SIZE, 50662306a36Sopenharmony_ci DMA_TO_DEVICE); 50762306a36Sopenharmony_ci if (dma_mapping_error(obj->dev, *pt_dma)) { 50862306a36Sopenharmony_ci dev_err(obj->dev, "DMA map error for L2 table\n"); 50962306a36Sopenharmony_ci iopte_free(obj, iopte, false); 51062306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 51162306a36Sopenharmony_ci } 51262306a36Sopenharmony_ci 51362306a36Sopenharmony_ci /* 51462306a36Sopenharmony_ci * we rely on dma address and the physical address to be 51562306a36Sopenharmony_ci * the same for mapping the L2 table 51662306a36Sopenharmony_ci */ 51762306a36Sopenharmony_ci if (WARN_ON(*pt_dma != virt_to_phys(iopte))) { 51862306a36Sopenharmony_ci dev_err(obj->dev, "DMA translation error for L2 table\n"); 51962306a36Sopenharmony_ci dma_unmap_single(obj->dev, *pt_dma, IOPTE_TABLE_SIZE, 52062306a36Sopenharmony_ci DMA_TO_DEVICE); 52162306a36Sopenharmony_ci iopte_free(obj, iopte, false); 52262306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 52362306a36Sopenharmony_ci } 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci *iopgd = virt_to_phys(iopte) | IOPGD_TABLE; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci flush_iopte_range(obj->dev, obj->pd_dma, offset, 1); 52862306a36Sopenharmony_ci dev_vdbg(obj->dev, "%s: a new pte:%p\n", __func__, iopte); 52962306a36Sopenharmony_ci } else { 53062306a36Sopenharmony_ci /* We raced, free the reduniovant table */ 53162306a36Sopenharmony_ci iopte_free(obj, iopte, false); 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_cipte_ready: 53562306a36Sopenharmony_ci iopte = iopte_offset(iopgd, da); 53662306a36Sopenharmony_ci *pt_dma = iopgd_page_paddr(iopgd); 53762306a36Sopenharmony_ci dev_vdbg(obj->dev, 53862306a36Sopenharmony_ci "%s: da:%08x pgd:%p *pgd:%08x pte:%p *pte:%08x\n", 53962306a36Sopenharmony_ci __func__, da, iopgd, *iopgd, iopte, *iopte); 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci return iopte; 54262306a36Sopenharmony_ci} 54362306a36Sopenharmony_ci 54462306a36Sopenharmony_cistatic int iopgd_alloc_section(struct omap_iommu *obj, u32 da, u32 pa, u32 prot) 54562306a36Sopenharmony_ci{ 54662306a36Sopenharmony_ci u32 *iopgd = iopgd_offset(obj, da); 54762306a36Sopenharmony_ci unsigned long offset = iopgd_index(da) * sizeof(da); 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci if ((da | pa) & ~IOSECTION_MASK) { 55062306a36Sopenharmony_ci dev_err(obj->dev, "%s: %08x:%08x should aligned on %08lx\n", 55162306a36Sopenharmony_ci __func__, da, pa, IOSECTION_SIZE); 55262306a36Sopenharmony_ci return -EINVAL; 55362306a36Sopenharmony_ci } 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci *iopgd = (pa & IOSECTION_MASK) | prot | IOPGD_SECTION; 55662306a36Sopenharmony_ci flush_iopte_range(obj->dev, obj->pd_dma, offset, 1); 55762306a36Sopenharmony_ci return 0; 55862306a36Sopenharmony_ci} 55962306a36Sopenharmony_ci 56062306a36Sopenharmony_cistatic int iopgd_alloc_super(struct omap_iommu *obj, u32 da, u32 pa, u32 prot) 56162306a36Sopenharmony_ci{ 56262306a36Sopenharmony_ci u32 *iopgd = iopgd_offset(obj, da); 56362306a36Sopenharmony_ci unsigned long offset = iopgd_index(da) * sizeof(da); 56462306a36Sopenharmony_ci int i; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci if ((da | pa) & ~IOSUPER_MASK) { 56762306a36Sopenharmony_ci dev_err(obj->dev, "%s: %08x:%08x should aligned on %08lx\n", 56862306a36Sopenharmony_ci __func__, da, pa, IOSUPER_SIZE); 56962306a36Sopenharmony_ci return -EINVAL; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci for (i = 0; i < 16; i++) 57362306a36Sopenharmony_ci *(iopgd + i) = (pa & IOSUPER_MASK) | prot | IOPGD_SUPER; 57462306a36Sopenharmony_ci flush_iopte_range(obj->dev, obj->pd_dma, offset, 16); 57562306a36Sopenharmony_ci return 0; 57662306a36Sopenharmony_ci} 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_cistatic int iopte_alloc_page(struct omap_iommu *obj, u32 da, u32 pa, u32 prot) 57962306a36Sopenharmony_ci{ 58062306a36Sopenharmony_ci u32 *iopgd = iopgd_offset(obj, da); 58162306a36Sopenharmony_ci dma_addr_t pt_dma; 58262306a36Sopenharmony_ci u32 *iopte = iopte_alloc(obj, iopgd, &pt_dma, da); 58362306a36Sopenharmony_ci unsigned long offset = iopte_index(da) * sizeof(da); 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci if (IS_ERR(iopte)) 58662306a36Sopenharmony_ci return PTR_ERR(iopte); 58762306a36Sopenharmony_ci 58862306a36Sopenharmony_ci *iopte = (pa & IOPAGE_MASK) | prot | IOPTE_SMALL; 58962306a36Sopenharmony_ci flush_iopte_range(obj->dev, pt_dma, offset, 1); 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci dev_vdbg(obj->dev, "%s: da:%08x pa:%08x pte:%p *pte:%08x\n", 59262306a36Sopenharmony_ci __func__, da, pa, iopte, *iopte); 59362306a36Sopenharmony_ci 59462306a36Sopenharmony_ci return 0; 59562306a36Sopenharmony_ci} 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_cistatic int iopte_alloc_large(struct omap_iommu *obj, u32 da, u32 pa, u32 prot) 59862306a36Sopenharmony_ci{ 59962306a36Sopenharmony_ci u32 *iopgd = iopgd_offset(obj, da); 60062306a36Sopenharmony_ci dma_addr_t pt_dma; 60162306a36Sopenharmony_ci u32 *iopte = iopte_alloc(obj, iopgd, &pt_dma, da); 60262306a36Sopenharmony_ci unsigned long offset = iopte_index(da) * sizeof(da); 60362306a36Sopenharmony_ci int i; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci if ((da | pa) & ~IOLARGE_MASK) { 60662306a36Sopenharmony_ci dev_err(obj->dev, "%s: %08x:%08x should aligned on %08lx\n", 60762306a36Sopenharmony_ci __func__, da, pa, IOLARGE_SIZE); 60862306a36Sopenharmony_ci return -EINVAL; 60962306a36Sopenharmony_ci } 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci if (IS_ERR(iopte)) 61262306a36Sopenharmony_ci return PTR_ERR(iopte); 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci for (i = 0; i < 16; i++) 61562306a36Sopenharmony_ci *(iopte + i) = (pa & IOLARGE_MASK) | prot | IOPTE_LARGE; 61662306a36Sopenharmony_ci flush_iopte_range(obj->dev, pt_dma, offset, 16); 61762306a36Sopenharmony_ci return 0; 61862306a36Sopenharmony_ci} 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic int 62162306a36Sopenharmony_ciiopgtable_store_entry_core(struct omap_iommu *obj, struct iotlb_entry *e) 62262306a36Sopenharmony_ci{ 62362306a36Sopenharmony_ci int (*fn)(struct omap_iommu *, u32, u32, u32); 62462306a36Sopenharmony_ci u32 prot; 62562306a36Sopenharmony_ci int err; 62662306a36Sopenharmony_ci 62762306a36Sopenharmony_ci if (!obj || !e) 62862306a36Sopenharmony_ci return -EINVAL; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci switch (e->pgsz) { 63162306a36Sopenharmony_ci case MMU_CAM_PGSZ_16M: 63262306a36Sopenharmony_ci fn = iopgd_alloc_super; 63362306a36Sopenharmony_ci break; 63462306a36Sopenharmony_ci case MMU_CAM_PGSZ_1M: 63562306a36Sopenharmony_ci fn = iopgd_alloc_section; 63662306a36Sopenharmony_ci break; 63762306a36Sopenharmony_ci case MMU_CAM_PGSZ_64K: 63862306a36Sopenharmony_ci fn = iopte_alloc_large; 63962306a36Sopenharmony_ci break; 64062306a36Sopenharmony_ci case MMU_CAM_PGSZ_4K: 64162306a36Sopenharmony_ci fn = iopte_alloc_page; 64262306a36Sopenharmony_ci break; 64362306a36Sopenharmony_ci default: 64462306a36Sopenharmony_ci fn = NULL; 64562306a36Sopenharmony_ci break; 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci if (WARN_ON(!fn)) 64962306a36Sopenharmony_ci return -EINVAL; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_ci prot = get_iopte_attr(e); 65262306a36Sopenharmony_ci 65362306a36Sopenharmony_ci spin_lock(&obj->page_table_lock); 65462306a36Sopenharmony_ci err = fn(obj, e->da, e->pa, prot); 65562306a36Sopenharmony_ci spin_unlock(&obj->page_table_lock); 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci return err; 65862306a36Sopenharmony_ci} 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci/** 66162306a36Sopenharmony_ci * omap_iopgtable_store_entry - Make an iommu pte entry 66262306a36Sopenharmony_ci * @obj: target iommu 66362306a36Sopenharmony_ci * @e: an iommu tlb entry info 66462306a36Sopenharmony_ci **/ 66562306a36Sopenharmony_cistatic int 66662306a36Sopenharmony_ciomap_iopgtable_store_entry(struct omap_iommu *obj, struct iotlb_entry *e) 66762306a36Sopenharmony_ci{ 66862306a36Sopenharmony_ci int err; 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci flush_iotlb_page(obj, e->da); 67162306a36Sopenharmony_ci err = iopgtable_store_entry_core(obj, e); 67262306a36Sopenharmony_ci if (!err) 67362306a36Sopenharmony_ci prefetch_iotlb_entry(obj, e); 67462306a36Sopenharmony_ci return err; 67562306a36Sopenharmony_ci} 67662306a36Sopenharmony_ci 67762306a36Sopenharmony_ci/** 67862306a36Sopenharmony_ci * iopgtable_lookup_entry - Lookup an iommu pte entry 67962306a36Sopenharmony_ci * @obj: target iommu 68062306a36Sopenharmony_ci * @da: iommu device virtual address 68162306a36Sopenharmony_ci * @ppgd: iommu pgd entry pointer to be returned 68262306a36Sopenharmony_ci * @ppte: iommu pte entry pointer to be returned 68362306a36Sopenharmony_ci **/ 68462306a36Sopenharmony_cistatic void 68562306a36Sopenharmony_ciiopgtable_lookup_entry(struct omap_iommu *obj, u32 da, u32 **ppgd, u32 **ppte) 68662306a36Sopenharmony_ci{ 68762306a36Sopenharmony_ci u32 *iopgd, *iopte = NULL; 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci iopgd = iopgd_offset(obj, da); 69062306a36Sopenharmony_ci if (!*iopgd) 69162306a36Sopenharmony_ci goto out; 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci if (iopgd_is_table(*iopgd)) 69462306a36Sopenharmony_ci iopte = iopte_offset(iopgd, da); 69562306a36Sopenharmony_ciout: 69662306a36Sopenharmony_ci *ppgd = iopgd; 69762306a36Sopenharmony_ci *ppte = iopte; 69862306a36Sopenharmony_ci} 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_cistatic size_t iopgtable_clear_entry_core(struct omap_iommu *obj, u32 da) 70162306a36Sopenharmony_ci{ 70262306a36Sopenharmony_ci size_t bytes; 70362306a36Sopenharmony_ci u32 *iopgd = iopgd_offset(obj, da); 70462306a36Sopenharmony_ci int nent = 1; 70562306a36Sopenharmony_ci dma_addr_t pt_dma; 70662306a36Sopenharmony_ci unsigned long pd_offset = iopgd_index(da) * sizeof(da); 70762306a36Sopenharmony_ci unsigned long pt_offset = iopte_index(da) * sizeof(da); 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci if (!*iopgd) 71062306a36Sopenharmony_ci return 0; 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci if (iopgd_is_table(*iopgd)) { 71362306a36Sopenharmony_ci int i; 71462306a36Sopenharmony_ci u32 *iopte = iopte_offset(iopgd, da); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci bytes = IOPTE_SIZE; 71762306a36Sopenharmony_ci if (*iopte & IOPTE_LARGE) { 71862306a36Sopenharmony_ci nent *= 16; 71962306a36Sopenharmony_ci /* rewind to the 1st entry */ 72062306a36Sopenharmony_ci iopte = iopte_offset(iopgd, (da & IOLARGE_MASK)); 72162306a36Sopenharmony_ci } 72262306a36Sopenharmony_ci bytes *= nent; 72362306a36Sopenharmony_ci memset(iopte, 0, nent * sizeof(*iopte)); 72462306a36Sopenharmony_ci pt_dma = iopgd_page_paddr(iopgd); 72562306a36Sopenharmony_ci flush_iopte_range(obj->dev, pt_dma, pt_offset, nent); 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci /* 72862306a36Sopenharmony_ci * do table walk to check if this table is necessary or not 72962306a36Sopenharmony_ci */ 73062306a36Sopenharmony_ci iopte = iopte_offset(iopgd, 0); 73162306a36Sopenharmony_ci for (i = 0; i < PTRS_PER_IOPTE; i++) 73262306a36Sopenharmony_ci if (iopte[i]) 73362306a36Sopenharmony_ci goto out; 73462306a36Sopenharmony_ci 73562306a36Sopenharmony_ci iopte_free(obj, iopte, true); 73662306a36Sopenharmony_ci nent = 1; /* for the next L1 entry */ 73762306a36Sopenharmony_ci } else { 73862306a36Sopenharmony_ci bytes = IOPGD_SIZE; 73962306a36Sopenharmony_ci if ((*iopgd & IOPGD_SUPER) == IOPGD_SUPER) { 74062306a36Sopenharmony_ci nent *= 16; 74162306a36Sopenharmony_ci /* rewind to the 1st entry */ 74262306a36Sopenharmony_ci iopgd = iopgd_offset(obj, (da & IOSUPER_MASK)); 74362306a36Sopenharmony_ci } 74462306a36Sopenharmony_ci bytes *= nent; 74562306a36Sopenharmony_ci } 74662306a36Sopenharmony_ci memset(iopgd, 0, nent * sizeof(*iopgd)); 74762306a36Sopenharmony_ci flush_iopte_range(obj->dev, obj->pd_dma, pd_offset, nent); 74862306a36Sopenharmony_ciout: 74962306a36Sopenharmony_ci return bytes; 75062306a36Sopenharmony_ci} 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci/** 75362306a36Sopenharmony_ci * iopgtable_clear_entry - Remove an iommu pte entry 75462306a36Sopenharmony_ci * @obj: target iommu 75562306a36Sopenharmony_ci * @da: iommu device virtual address 75662306a36Sopenharmony_ci **/ 75762306a36Sopenharmony_cistatic size_t iopgtable_clear_entry(struct omap_iommu *obj, u32 da) 75862306a36Sopenharmony_ci{ 75962306a36Sopenharmony_ci size_t bytes; 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci spin_lock(&obj->page_table_lock); 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci bytes = iopgtable_clear_entry_core(obj, da); 76462306a36Sopenharmony_ci flush_iotlb_page(obj, da); 76562306a36Sopenharmony_ci 76662306a36Sopenharmony_ci spin_unlock(&obj->page_table_lock); 76762306a36Sopenharmony_ci 76862306a36Sopenharmony_ci return bytes; 76962306a36Sopenharmony_ci} 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_cistatic void iopgtable_clear_entry_all(struct omap_iommu *obj) 77262306a36Sopenharmony_ci{ 77362306a36Sopenharmony_ci unsigned long offset; 77462306a36Sopenharmony_ci int i; 77562306a36Sopenharmony_ci 77662306a36Sopenharmony_ci spin_lock(&obj->page_table_lock); 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci for (i = 0; i < PTRS_PER_IOPGD; i++) { 77962306a36Sopenharmony_ci u32 da; 78062306a36Sopenharmony_ci u32 *iopgd; 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci da = i << IOPGD_SHIFT; 78362306a36Sopenharmony_ci iopgd = iopgd_offset(obj, da); 78462306a36Sopenharmony_ci offset = iopgd_index(da) * sizeof(da); 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci if (!*iopgd) 78762306a36Sopenharmony_ci continue; 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_ci if (iopgd_is_table(*iopgd)) 79062306a36Sopenharmony_ci iopte_free(obj, iopte_offset(iopgd, 0), true); 79162306a36Sopenharmony_ci 79262306a36Sopenharmony_ci *iopgd = 0; 79362306a36Sopenharmony_ci flush_iopte_range(obj->dev, obj->pd_dma, offset, 1); 79462306a36Sopenharmony_ci } 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci flush_iotlb_all(obj); 79762306a36Sopenharmony_ci 79862306a36Sopenharmony_ci spin_unlock(&obj->page_table_lock); 79962306a36Sopenharmony_ci} 80062306a36Sopenharmony_ci 80162306a36Sopenharmony_ci/* 80262306a36Sopenharmony_ci * Device IOMMU generic operations 80362306a36Sopenharmony_ci */ 80462306a36Sopenharmony_cistatic irqreturn_t iommu_fault_handler(int irq, void *data) 80562306a36Sopenharmony_ci{ 80662306a36Sopenharmony_ci u32 da, errs; 80762306a36Sopenharmony_ci u32 *iopgd, *iopte; 80862306a36Sopenharmony_ci struct omap_iommu *obj = data; 80962306a36Sopenharmony_ci struct iommu_domain *domain = obj->domain; 81062306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci if (!omap_domain->dev) 81362306a36Sopenharmony_ci return IRQ_NONE; 81462306a36Sopenharmony_ci 81562306a36Sopenharmony_ci errs = iommu_report_fault(obj, &da); 81662306a36Sopenharmony_ci if (errs == 0) 81762306a36Sopenharmony_ci return IRQ_HANDLED; 81862306a36Sopenharmony_ci 81962306a36Sopenharmony_ci /* Fault callback or TLB/PTE Dynamic loading */ 82062306a36Sopenharmony_ci if (!report_iommu_fault(domain, obj->dev, da, 0)) 82162306a36Sopenharmony_ci return IRQ_HANDLED; 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci iommu_write_reg(obj, 0, MMU_IRQENABLE); 82462306a36Sopenharmony_ci 82562306a36Sopenharmony_ci iopgd = iopgd_offset(obj, da); 82662306a36Sopenharmony_ci 82762306a36Sopenharmony_ci if (!iopgd_is_table(*iopgd)) { 82862306a36Sopenharmony_ci dev_err(obj->dev, "%s: errs:0x%08x da:0x%08x pgd:0x%p *pgd:px%08x\n", 82962306a36Sopenharmony_ci obj->name, errs, da, iopgd, *iopgd); 83062306a36Sopenharmony_ci return IRQ_NONE; 83162306a36Sopenharmony_ci } 83262306a36Sopenharmony_ci 83362306a36Sopenharmony_ci iopte = iopte_offset(iopgd, da); 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci dev_err(obj->dev, "%s: errs:0x%08x da:0x%08x pgd:0x%p *pgd:0x%08x pte:0x%p *pte:0x%08x\n", 83662306a36Sopenharmony_ci obj->name, errs, da, iopgd, *iopgd, iopte, *iopte); 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci return IRQ_NONE; 83962306a36Sopenharmony_ci} 84062306a36Sopenharmony_ci 84162306a36Sopenharmony_ci/** 84262306a36Sopenharmony_ci * omap_iommu_attach() - attach iommu device to an iommu domain 84362306a36Sopenharmony_ci * @obj: target omap iommu device 84462306a36Sopenharmony_ci * @iopgd: page table 84562306a36Sopenharmony_ci **/ 84662306a36Sopenharmony_cistatic int omap_iommu_attach(struct omap_iommu *obj, u32 *iopgd) 84762306a36Sopenharmony_ci{ 84862306a36Sopenharmony_ci int err; 84962306a36Sopenharmony_ci 85062306a36Sopenharmony_ci spin_lock(&obj->iommu_lock); 85162306a36Sopenharmony_ci 85262306a36Sopenharmony_ci obj->pd_dma = dma_map_single(obj->dev, iopgd, IOPGD_TABLE_SIZE, 85362306a36Sopenharmony_ci DMA_TO_DEVICE); 85462306a36Sopenharmony_ci if (dma_mapping_error(obj->dev, obj->pd_dma)) { 85562306a36Sopenharmony_ci dev_err(obj->dev, "DMA map error for L1 table\n"); 85662306a36Sopenharmony_ci err = -ENOMEM; 85762306a36Sopenharmony_ci goto out_err; 85862306a36Sopenharmony_ci } 85962306a36Sopenharmony_ci 86062306a36Sopenharmony_ci obj->iopgd = iopgd; 86162306a36Sopenharmony_ci err = iommu_enable(obj); 86262306a36Sopenharmony_ci if (err) 86362306a36Sopenharmony_ci goto out_err; 86462306a36Sopenharmony_ci flush_iotlb_all(obj); 86562306a36Sopenharmony_ci 86662306a36Sopenharmony_ci spin_unlock(&obj->iommu_lock); 86762306a36Sopenharmony_ci 86862306a36Sopenharmony_ci dev_dbg(obj->dev, "%s: %s\n", __func__, obj->name); 86962306a36Sopenharmony_ci 87062306a36Sopenharmony_ci return 0; 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ciout_err: 87362306a36Sopenharmony_ci spin_unlock(&obj->iommu_lock); 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci return err; 87662306a36Sopenharmony_ci} 87762306a36Sopenharmony_ci 87862306a36Sopenharmony_ci/** 87962306a36Sopenharmony_ci * omap_iommu_detach - release iommu device 88062306a36Sopenharmony_ci * @obj: target iommu 88162306a36Sopenharmony_ci **/ 88262306a36Sopenharmony_cistatic void omap_iommu_detach(struct omap_iommu *obj) 88362306a36Sopenharmony_ci{ 88462306a36Sopenharmony_ci if (!obj || IS_ERR(obj)) 88562306a36Sopenharmony_ci return; 88662306a36Sopenharmony_ci 88762306a36Sopenharmony_ci spin_lock(&obj->iommu_lock); 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci dma_unmap_single(obj->dev, obj->pd_dma, IOPGD_TABLE_SIZE, 89062306a36Sopenharmony_ci DMA_TO_DEVICE); 89162306a36Sopenharmony_ci obj->pd_dma = 0; 89262306a36Sopenharmony_ci obj->iopgd = NULL; 89362306a36Sopenharmony_ci iommu_disable(obj); 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ci spin_unlock(&obj->iommu_lock); 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci dev_dbg(obj->dev, "%s: %s\n", __func__, obj->name); 89862306a36Sopenharmony_ci} 89962306a36Sopenharmony_ci 90062306a36Sopenharmony_cistatic void omap_iommu_save_tlb_entries(struct omap_iommu *obj) 90162306a36Sopenharmony_ci{ 90262306a36Sopenharmony_ci struct iotlb_lock lock; 90362306a36Sopenharmony_ci struct cr_regs cr; 90462306a36Sopenharmony_ci struct cr_regs *tmp; 90562306a36Sopenharmony_ci int i; 90662306a36Sopenharmony_ci 90762306a36Sopenharmony_ci /* check if there are any locked tlbs to save */ 90862306a36Sopenharmony_ci iotlb_lock_get(obj, &lock); 90962306a36Sopenharmony_ci obj->num_cr_ctx = lock.base; 91062306a36Sopenharmony_ci if (!obj->num_cr_ctx) 91162306a36Sopenharmony_ci return; 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_ci tmp = obj->cr_ctx; 91462306a36Sopenharmony_ci for_each_iotlb_cr(obj, obj->num_cr_ctx, i, cr) 91562306a36Sopenharmony_ci * tmp++ = cr; 91662306a36Sopenharmony_ci} 91762306a36Sopenharmony_ci 91862306a36Sopenharmony_cistatic void omap_iommu_restore_tlb_entries(struct omap_iommu *obj) 91962306a36Sopenharmony_ci{ 92062306a36Sopenharmony_ci struct iotlb_lock l; 92162306a36Sopenharmony_ci struct cr_regs *tmp; 92262306a36Sopenharmony_ci int i; 92362306a36Sopenharmony_ci 92462306a36Sopenharmony_ci /* no locked tlbs to restore */ 92562306a36Sopenharmony_ci if (!obj->num_cr_ctx) 92662306a36Sopenharmony_ci return; 92762306a36Sopenharmony_ci 92862306a36Sopenharmony_ci l.base = 0; 92962306a36Sopenharmony_ci tmp = obj->cr_ctx; 93062306a36Sopenharmony_ci for (i = 0; i < obj->num_cr_ctx; i++, tmp++) { 93162306a36Sopenharmony_ci l.vict = i; 93262306a36Sopenharmony_ci iotlb_lock_set(obj, &l); 93362306a36Sopenharmony_ci iotlb_load_cr(obj, tmp); 93462306a36Sopenharmony_ci } 93562306a36Sopenharmony_ci l.base = obj->num_cr_ctx; 93662306a36Sopenharmony_ci l.vict = i; 93762306a36Sopenharmony_ci iotlb_lock_set(obj, &l); 93862306a36Sopenharmony_ci} 93962306a36Sopenharmony_ci 94062306a36Sopenharmony_ci/** 94162306a36Sopenharmony_ci * omap_iommu_domain_deactivate - deactivate attached iommu devices 94262306a36Sopenharmony_ci * @domain: iommu domain attached to the target iommu device 94362306a36Sopenharmony_ci * 94462306a36Sopenharmony_ci * This API allows the client devices of IOMMU devices to suspend 94562306a36Sopenharmony_ci * the IOMMUs they control at runtime, after they are idled and 94662306a36Sopenharmony_ci * suspended all activity. System Suspend will leverage the PM 94762306a36Sopenharmony_ci * driver late callbacks. 94862306a36Sopenharmony_ci **/ 94962306a36Sopenharmony_ciint omap_iommu_domain_deactivate(struct iommu_domain *domain) 95062306a36Sopenharmony_ci{ 95162306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 95262306a36Sopenharmony_ci struct omap_iommu_device *iommu; 95362306a36Sopenharmony_ci struct omap_iommu *oiommu; 95462306a36Sopenharmony_ci int i; 95562306a36Sopenharmony_ci 95662306a36Sopenharmony_ci if (!omap_domain->dev) 95762306a36Sopenharmony_ci return 0; 95862306a36Sopenharmony_ci 95962306a36Sopenharmony_ci iommu = omap_domain->iommus; 96062306a36Sopenharmony_ci iommu += (omap_domain->num_iommus - 1); 96162306a36Sopenharmony_ci for (i = 0; i < omap_domain->num_iommus; i++, iommu--) { 96262306a36Sopenharmony_ci oiommu = iommu->iommu_dev; 96362306a36Sopenharmony_ci pm_runtime_put_sync(oiommu->dev); 96462306a36Sopenharmony_ci } 96562306a36Sopenharmony_ci 96662306a36Sopenharmony_ci return 0; 96762306a36Sopenharmony_ci} 96862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(omap_iommu_domain_deactivate); 96962306a36Sopenharmony_ci 97062306a36Sopenharmony_ci/** 97162306a36Sopenharmony_ci * omap_iommu_domain_activate - activate attached iommu devices 97262306a36Sopenharmony_ci * @domain: iommu domain attached to the target iommu device 97362306a36Sopenharmony_ci * 97462306a36Sopenharmony_ci * This API allows the client devices of IOMMU devices to resume the 97562306a36Sopenharmony_ci * IOMMUs they control at runtime, before they can resume operations. 97662306a36Sopenharmony_ci * System Resume will leverage the PM driver late callbacks. 97762306a36Sopenharmony_ci **/ 97862306a36Sopenharmony_ciint omap_iommu_domain_activate(struct iommu_domain *domain) 97962306a36Sopenharmony_ci{ 98062306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 98162306a36Sopenharmony_ci struct omap_iommu_device *iommu; 98262306a36Sopenharmony_ci struct omap_iommu *oiommu; 98362306a36Sopenharmony_ci int i; 98462306a36Sopenharmony_ci 98562306a36Sopenharmony_ci if (!omap_domain->dev) 98662306a36Sopenharmony_ci return 0; 98762306a36Sopenharmony_ci 98862306a36Sopenharmony_ci iommu = omap_domain->iommus; 98962306a36Sopenharmony_ci for (i = 0; i < omap_domain->num_iommus; i++, iommu++) { 99062306a36Sopenharmony_ci oiommu = iommu->iommu_dev; 99162306a36Sopenharmony_ci pm_runtime_get_sync(oiommu->dev); 99262306a36Sopenharmony_ci } 99362306a36Sopenharmony_ci 99462306a36Sopenharmony_ci return 0; 99562306a36Sopenharmony_ci} 99662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(omap_iommu_domain_activate); 99762306a36Sopenharmony_ci 99862306a36Sopenharmony_ci/** 99962306a36Sopenharmony_ci * omap_iommu_runtime_suspend - disable an iommu device 100062306a36Sopenharmony_ci * @dev: iommu device 100162306a36Sopenharmony_ci * 100262306a36Sopenharmony_ci * This function performs all that is necessary to disable an 100362306a36Sopenharmony_ci * IOMMU device, either during final detachment from a client 100462306a36Sopenharmony_ci * device, or during system/runtime suspend of the device. This 100562306a36Sopenharmony_ci * includes programming all the appropriate IOMMU registers, and 100662306a36Sopenharmony_ci * managing the associated omap_hwmod's state and the device's 100762306a36Sopenharmony_ci * reset line. This function also saves the context of any 100862306a36Sopenharmony_ci * locked TLBs if suspending. 100962306a36Sopenharmony_ci **/ 101062306a36Sopenharmony_cistatic __maybe_unused int omap_iommu_runtime_suspend(struct device *dev) 101162306a36Sopenharmony_ci{ 101262306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 101362306a36Sopenharmony_ci struct iommu_platform_data *pdata = dev_get_platdata(dev); 101462306a36Sopenharmony_ci struct omap_iommu *obj = to_iommu(dev); 101562306a36Sopenharmony_ci int ret; 101662306a36Sopenharmony_ci 101762306a36Sopenharmony_ci /* save the TLBs only during suspend, and not for power down */ 101862306a36Sopenharmony_ci if (obj->domain && obj->iopgd) 101962306a36Sopenharmony_ci omap_iommu_save_tlb_entries(obj); 102062306a36Sopenharmony_ci 102162306a36Sopenharmony_ci omap2_iommu_disable(obj); 102262306a36Sopenharmony_ci 102362306a36Sopenharmony_ci if (pdata && pdata->device_idle) 102462306a36Sopenharmony_ci pdata->device_idle(pdev); 102562306a36Sopenharmony_ci 102662306a36Sopenharmony_ci if (pdata && pdata->assert_reset) 102762306a36Sopenharmony_ci pdata->assert_reset(pdev, pdata->reset_name); 102862306a36Sopenharmony_ci 102962306a36Sopenharmony_ci if (pdata && pdata->set_pwrdm_constraint) { 103062306a36Sopenharmony_ci ret = pdata->set_pwrdm_constraint(pdev, false, &obj->pwrst); 103162306a36Sopenharmony_ci if (ret) { 103262306a36Sopenharmony_ci dev_warn(obj->dev, "pwrdm_constraint failed to be reset, status = %d\n", 103362306a36Sopenharmony_ci ret); 103462306a36Sopenharmony_ci } 103562306a36Sopenharmony_ci } 103662306a36Sopenharmony_ci 103762306a36Sopenharmony_ci return 0; 103862306a36Sopenharmony_ci} 103962306a36Sopenharmony_ci 104062306a36Sopenharmony_ci/** 104162306a36Sopenharmony_ci * omap_iommu_runtime_resume - enable an iommu device 104262306a36Sopenharmony_ci * @dev: iommu device 104362306a36Sopenharmony_ci * 104462306a36Sopenharmony_ci * This function performs all that is necessary to enable an 104562306a36Sopenharmony_ci * IOMMU device, either during initial attachment to a client 104662306a36Sopenharmony_ci * device, or during system/runtime resume of the device. This 104762306a36Sopenharmony_ci * includes programming all the appropriate IOMMU registers, and 104862306a36Sopenharmony_ci * managing the associated omap_hwmod's state and the device's 104962306a36Sopenharmony_ci * reset line. The function also restores any locked TLBs if 105062306a36Sopenharmony_ci * resuming after a suspend. 105162306a36Sopenharmony_ci **/ 105262306a36Sopenharmony_cistatic __maybe_unused int omap_iommu_runtime_resume(struct device *dev) 105362306a36Sopenharmony_ci{ 105462306a36Sopenharmony_ci struct platform_device *pdev = to_platform_device(dev); 105562306a36Sopenharmony_ci struct iommu_platform_data *pdata = dev_get_platdata(dev); 105662306a36Sopenharmony_ci struct omap_iommu *obj = to_iommu(dev); 105762306a36Sopenharmony_ci int ret = 0; 105862306a36Sopenharmony_ci 105962306a36Sopenharmony_ci if (pdata && pdata->set_pwrdm_constraint) { 106062306a36Sopenharmony_ci ret = pdata->set_pwrdm_constraint(pdev, true, &obj->pwrst); 106162306a36Sopenharmony_ci if (ret) { 106262306a36Sopenharmony_ci dev_warn(obj->dev, "pwrdm_constraint failed to be set, status = %d\n", 106362306a36Sopenharmony_ci ret); 106462306a36Sopenharmony_ci } 106562306a36Sopenharmony_ci } 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci if (pdata && pdata->deassert_reset) { 106862306a36Sopenharmony_ci ret = pdata->deassert_reset(pdev, pdata->reset_name); 106962306a36Sopenharmony_ci if (ret) { 107062306a36Sopenharmony_ci dev_err(dev, "deassert_reset failed: %d\n", ret); 107162306a36Sopenharmony_ci return ret; 107262306a36Sopenharmony_ci } 107362306a36Sopenharmony_ci } 107462306a36Sopenharmony_ci 107562306a36Sopenharmony_ci if (pdata && pdata->device_enable) 107662306a36Sopenharmony_ci pdata->device_enable(pdev); 107762306a36Sopenharmony_ci 107862306a36Sopenharmony_ci /* restore the TLBs only during resume, and not for power up */ 107962306a36Sopenharmony_ci if (obj->domain) 108062306a36Sopenharmony_ci omap_iommu_restore_tlb_entries(obj); 108162306a36Sopenharmony_ci 108262306a36Sopenharmony_ci ret = omap2_iommu_enable(obj); 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_ci return ret; 108562306a36Sopenharmony_ci} 108662306a36Sopenharmony_ci 108762306a36Sopenharmony_ci/** 108862306a36Sopenharmony_ci * omap_iommu_prepare - prepare() dev_pm_ops implementation 108962306a36Sopenharmony_ci * @dev: iommu device 109062306a36Sopenharmony_ci * 109162306a36Sopenharmony_ci * This function performs the necessary checks to determine if the IOMMU 109262306a36Sopenharmony_ci * device needs suspending or not. The function checks if the runtime_pm 109362306a36Sopenharmony_ci * status of the device is suspended, and returns 1 in that case. This 109462306a36Sopenharmony_ci * results in the PM core to skip invoking any of the Sleep PM callbacks 109562306a36Sopenharmony_ci * (suspend, suspend_late, resume, resume_early etc). 109662306a36Sopenharmony_ci */ 109762306a36Sopenharmony_cistatic int omap_iommu_prepare(struct device *dev) 109862306a36Sopenharmony_ci{ 109962306a36Sopenharmony_ci if (pm_runtime_status_suspended(dev)) 110062306a36Sopenharmony_ci return 1; 110162306a36Sopenharmony_ci return 0; 110262306a36Sopenharmony_ci} 110362306a36Sopenharmony_ci 110462306a36Sopenharmony_cistatic bool omap_iommu_can_register(struct platform_device *pdev) 110562306a36Sopenharmony_ci{ 110662306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 110762306a36Sopenharmony_ci 110862306a36Sopenharmony_ci if (!of_device_is_compatible(np, "ti,dra7-dsp-iommu")) 110962306a36Sopenharmony_ci return true; 111062306a36Sopenharmony_ci 111162306a36Sopenharmony_ci /* 111262306a36Sopenharmony_ci * restrict IOMMU core registration only for processor-port MDMA MMUs 111362306a36Sopenharmony_ci * on DRA7 DSPs 111462306a36Sopenharmony_ci */ 111562306a36Sopenharmony_ci if ((!strcmp(dev_name(&pdev->dev), "40d01000.mmu")) || 111662306a36Sopenharmony_ci (!strcmp(dev_name(&pdev->dev), "41501000.mmu"))) 111762306a36Sopenharmony_ci return true; 111862306a36Sopenharmony_ci 111962306a36Sopenharmony_ci return false; 112062306a36Sopenharmony_ci} 112162306a36Sopenharmony_ci 112262306a36Sopenharmony_cistatic int omap_iommu_dra7_get_dsp_system_cfg(struct platform_device *pdev, 112362306a36Sopenharmony_ci struct omap_iommu *obj) 112462306a36Sopenharmony_ci{ 112562306a36Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 112662306a36Sopenharmony_ci int ret; 112762306a36Sopenharmony_ci 112862306a36Sopenharmony_ci if (!of_device_is_compatible(np, "ti,dra7-dsp-iommu")) 112962306a36Sopenharmony_ci return 0; 113062306a36Sopenharmony_ci 113162306a36Sopenharmony_ci if (!of_property_read_bool(np, "ti,syscon-mmuconfig")) { 113262306a36Sopenharmony_ci dev_err(&pdev->dev, "ti,syscon-mmuconfig property is missing\n"); 113362306a36Sopenharmony_ci return -EINVAL; 113462306a36Sopenharmony_ci } 113562306a36Sopenharmony_ci 113662306a36Sopenharmony_ci obj->syscfg = 113762306a36Sopenharmony_ci syscon_regmap_lookup_by_phandle(np, "ti,syscon-mmuconfig"); 113862306a36Sopenharmony_ci if (IS_ERR(obj->syscfg)) { 113962306a36Sopenharmony_ci /* can fail with -EPROBE_DEFER */ 114062306a36Sopenharmony_ci ret = PTR_ERR(obj->syscfg); 114162306a36Sopenharmony_ci return ret; 114262306a36Sopenharmony_ci } 114362306a36Sopenharmony_ci 114462306a36Sopenharmony_ci if (of_property_read_u32_index(np, "ti,syscon-mmuconfig", 1, 114562306a36Sopenharmony_ci &obj->id)) { 114662306a36Sopenharmony_ci dev_err(&pdev->dev, "couldn't get the IOMMU instance id within subsystem\n"); 114762306a36Sopenharmony_ci return -EINVAL; 114862306a36Sopenharmony_ci } 114962306a36Sopenharmony_ci 115062306a36Sopenharmony_ci if (obj->id != 0 && obj->id != 1) { 115162306a36Sopenharmony_ci dev_err(&pdev->dev, "invalid IOMMU instance id\n"); 115262306a36Sopenharmony_ci return -EINVAL; 115362306a36Sopenharmony_ci } 115462306a36Sopenharmony_ci 115562306a36Sopenharmony_ci return 0; 115662306a36Sopenharmony_ci} 115762306a36Sopenharmony_ci 115862306a36Sopenharmony_ci/* 115962306a36Sopenharmony_ci * OMAP Device MMU(IOMMU) detection 116062306a36Sopenharmony_ci */ 116162306a36Sopenharmony_cistatic int omap_iommu_probe(struct platform_device *pdev) 116262306a36Sopenharmony_ci{ 116362306a36Sopenharmony_ci int err = -ENODEV; 116462306a36Sopenharmony_ci int irq; 116562306a36Sopenharmony_ci struct omap_iommu *obj; 116662306a36Sopenharmony_ci struct resource *res; 116762306a36Sopenharmony_ci struct device_node *of = pdev->dev.of_node; 116862306a36Sopenharmony_ci 116962306a36Sopenharmony_ci if (!of) { 117062306a36Sopenharmony_ci pr_err("%s: only DT-based devices are supported\n", __func__); 117162306a36Sopenharmony_ci return -ENODEV; 117262306a36Sopenharmony_ci } 117362306a36Sopenharmony_ci 117462306a36Sopenharmony_ci obj = devm_kzalloc(&pdev->dev, sizeof(*obj) + MMU_REG_SIZE, GFP_KERNEL); 117562306a36Sopenharmony_ci if (!obj) 117662306a36Sopenharmony_ci return -ENOMEM; 117762306a36Sopenharmony_ci 117862306a36Sopenharmony_ci /* 117962306a36Sopenharmony_ci * self-manage the ordering dependencies between omap_device_enable/idle 118062306a36Sopenharmony_ci * and omap_device_assert/deassert_hardreset API 118162306a36Sopenharmony_ci */ 118262306a36Sopenharmony_ci if (pdev->dev.pm_domain) { 118362306a36Sopenharmony_ci dev_dbg(&pdev->dev, "device pm_domain is being reset\n"); 118462306a36Sopenharmony_ci pdev->dev.pm_domain = NULL; 118562306a36Sopenharmony_ci } 118662306a36Sopenharmony_ci 118762306a36Sopenharmony_ci obj->name = dev_name(&pdev->dev); 118862306a36Sopenharmony_ci obj->nr_tlb_entries = 32; 118962306a36Sopenharmony_ci err = of_property_read_u32(of, "ti,#tlb-entries", &obj->nr_tlb_entries); 119062306a36Sopenharmony_ci if (err && err != -EINVAL) 119162306a36Sopenharmony_ci return err; 119262306a36Sopenharmony_ci if (obj->nr_tlb_entries != 32 && obj->nr_tlb_entries != 8) 119362306a36Sopenharmony_ci return -EINVAL; 119462306a36Sopenharmony_ci if (of_property_read_bool(of, "ti,iommu-bus-err-back")) 119562306a36Sopenharmony_ci obj->has_bus_err_back = MMU_GP_REG_BUS_ERR_BACK_EN; 119662306a36Sopenharmony_ci 119762306a36Sopenharmony_ci obj->dev = &pdev->dev; 119862306a36Sopenharmony_ci obj->ctx = (void *)obj + sizeof(*obj); 119962306a36Sopenharmony_ci obj->cr_ctx = devm_kzalloc(&pdev->dev, 120062306a36Sopenharmony_ci sizeof(*obj->cr_ctx) * obj->nr_tlb_entries, 120162306a36Sopenharmony_ci GFP_KERNEL); 120262306a36Sopenharmony_ci if (!obj->cr_ctx) 120362306a36Sopenharmony_ci return -ENOMEM; 120462306a36Sopenharmony_ci 120562306a36Sopenharmony_ci spin_lock_init(&obj->iommu_lock); 120662306a36Sopenharmony_ci spin_lock_init(&obj->page_table_lock); 120762306a36Sopenharmony_ci 120862306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 120962306a36Sopenharmony_ci obj->regbase = devm_ioremap_resource(obj->dev, res); 121062306a36Sopenharmony_ci if (IS_ERR(obj->regbase)) 121162306a36Sopenharmony_ci return PTR_ERR(obj->regbase); 121262306a36Sopenharmony_ci 121362306a36Sopenharmony_ci err = omap_iommu_dra7_get_dsp_system_cfg(pdev, obj); 121462306a36Sopenharmony_ci if (err) 121562306a36Sopenharmony_ci return err; 121662306a36Sopenharmony_ci 121762306a36Sopenharmony_ci irq = platform_get_irq(pdev, 0); 121862306a36Sopenharmony_ci if (irq < 0) 121962306a36Sopenharmony_ci return -ENODEV; 122062306a36Sopenharmony_ci 122162306a36Sopenharmony_ci err = devm_request_irq(obj->dev, irq, iommu_fault_handler, IRQF_SHARED, 122262306a36Sopenharmony_ci dev_name(obj->dev), obj); 122362306a36Sopenharmony_ci if (err < 0) 122462306a36Sopenharmony_ci return err; 122562306a36Sopenharmony_ci platform_set_drvdata(pdev, obj); 122662306a36Sopenharmony_ci 122762306a36Sopenharmony_ci if (omap_iommu_can_register(pdev)) { 122862306a36Sopenharmony_ci obj->group = iommu_group_alloc(); 122962306a36Sopenharmony_ci if (IS_ERR(obj->group)) 123062306a36Sopenharmony_ci return PTR_ERR(obj->group); 123162306a36Sopenharmony_ci 123262306a36Sopenharmony_ci err = iommu_device_sysfs_add(&obj->iommu, obj->dev, NULL, 123362306a36Sopenharmony_ci obj->name); 123462306a36Sopenharmony_ci if (err) 123562306a36Sopenharmony_ci goto out_group; 123662306a36Sopenharmony_ci 123762306a36Sopenharmony_ci err = iommu_device_register(&obj->iommu, &omap_iommu_ops, &pdev->dev); 123862306a36Sopenharmony_ci if (err) 123962306a36Sopenharmony_ci goto out_sysfs; 124062306a36Sopenharmony_ci } 124162306a36Sopenharmony_ci 124262306a36Sopenharmony_ci pm_runtime_enable(obj->dev); 124362306a36Sopenharmony_ci 124462306a36Sopenharmony_ci omap_iommu_debugfs_add(obj); 124562306a36Sopenharmony_ci 124662306a36Sopenharmony_ci dev_info(&pdev->dev, "%s registered\n", obj->name); 124762306a36Sopenharmony_ci 124862306a36Sopenharmony_ci /* Re-probe bus to probe device attached to this IOMMU */ 124962306a36Sopenharmony_ci bus_iommu_probe(&platform_bus_type); 125062306a36Sopenharmony_ci 125162306a36Sopenharmony_ci return 0; 125262306a36Sopenharmony_ci 125362306a36Sopenharmony_ciout_sysfs: 125462306a36Sopenharmony_ci iommu_device_sysfs_remove(&obj->iommu); 125562306a36Sopenharmony_ciout_group: 125662306a36Sopenharmony_ci iommu_group_put(obj->group); 125762306a36Sopenharmony_ci return err; 125862306a36Sopenharmony_ci} 125962306a36Sopenharmony_ci 126062306a36Sopenharmony_cistatic void omap_iommu_remove(struct platform_device *pdev) 126162306a36Sopenharmony_ci{ 126262306a36Sopenharmony_ci struct omap_iommu *obj = platform_get_drvdata(pdev); 126362306a36Sopenharmony_ci 126462306a36Sopenharmony_ci if (obj->group) { 126562306a36Sopenharmony_ci iommu_group_put(obj->group); 126662306a36Sopenharmony_ci obj->group = NULL; 126762306a36Sopenharmony_ci 126862306a36Sopenharmony_ci iommu_device_sysfs_remove(&obj->iommu); 126962306a36Sopenharmony_ci iommu_device_unregister(&obj->iommu); 127062306a36Sopenharmony_ci } 127162306a36Sopenharmony_ci 127262306a36Sopenharmony_ci omap_iommu_debugfs_remove(obj); 127362306a36Sopenharmony_ci 127462306a36Sopenharmony_ci pm_runtime_disable(obj->dev); 127562306a36Sopenharmony_ci 127662306a36Sopenharmony_ci dev_info(&pdev->dev, "%s removed\n", obj->name); 127762306a36Sopenharmony_ci} 127862306a36Sopenharmony_ci 127962306a36Sopenharmony_cistatic const struct dev_pm_ops omap_iommu_pm_ops = { 128062306a36Sopenharmony_ci .prepare = omap_iommu_prepare, 128162306a36Sopenharmony_ci SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, 128262306a36Sopenharmony_ci pm_runtime_force_resume) 128362306a36Sopenharmony_ci SET_RUNTIME_PM_OPS(omap_iommu_runtime_suspend, 128462306a36Sopenharmony_ci omap_iommu_runtime_resume, NULL) 128562306a36Sopenharmony_ci}; 128662306a36Sopenharmony_ci 128762306a36Sopenharmony_cistatic const struct of_device_id omap_iommu_of_match[] = { 128862306a36Sopenharmony_ci { .compatible = "ti,omap2-iommu" }, 128962306a36Sopenharmony_ci { .compatible = "ti,omap4-iommu" }, 129062306a36Sopenharmony_ci { .compatible = "ti,dra7-iommu" }, 129162306a36Sopenharmony_ci { .compatible = "ti,dra7-dsp-iommu" }, 129262306a36Sopenharmony_ci {}, 129362306a36Sopenharmony_ci}; 129462306a36Sopenharmony_ci 129562306a36Sopenharmony_cistatic struct platform_driver omap_iommu_driver = { 129662306a36Sopenharmony_ci .probe = omap_iommu_probe, 129762306a36Sopenharmony_ci .remove_new = omap_iommu_remove, 129862306a36Sopenharmony_ci .driver = { 129962306a36Sopenharmony_ci .name = "omap-iommu", 130062306a36Sopenharmony_ci .pm = &omap_iommu_pm_ops, 130162306a36Sopenharmony_ci .of_match_table = of_match_ptr(omap_iommu_of_match), 130262306a36Sopenharmony_ci }, 130362306a36Sopenharmony_ci}; 130462306a36Sopenharmony_ci 130562306a36Sopenharmony_cistatic u32 iotlb_init_entry(struct iotlb_entry *e, u32 da, u32 pa, int pgsz) 130662306a36Sopenharmony_ci{ 130762306a36Sopenharmony_ci memset(e, 0, sizeof(*e)); 130862306a36Sopenharmony_ci 130962306a36Sopenharmony_ci e->da = da; 131062306a36Sopenharmony_ci e->pa = pa; 131162306a36Sopenharmony_ci e->valid = MMU_CAM_V; 131262306a36Sopenharmony_ci e->pgsz = pgsz; 131362306a36Sopenharmony_ci e->endian = MMU_RAM_ENDIAN_LITTLE; 131462306a36Sopenharmony_ci e->elsz = MMU_RAM_ELSZ_8; 131562306a36Sopenharmony_ci e->mixed = 0; 131662306a36Sopenharmony_ci 131762306a36Sopenharmony_ci return iopgsz_to_bytes(e->pgsz); 131862306a36Sopenharmony_ci} 131962306a36Sopenharmony_ci 132062306a36Sopenharmony_cistatic int omap_iommu_map(struct iommu_domain *domain, unsigned long da, 132162306a36Sopenharmony_ci phys_addr_t pa, size_t bytes, int prot, gfp_t gfp) 132262306a36Sopenharmony_ci{ 132362306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 132462306a36Sopenharmony_ci struct device *dev = omap_domain->dev; 132562306a36Sopenharmony_ci struct omap_iommu_device *iommu; 132662306a36Sopenharmony_ci struct omap_iommu *oiommu; 132762306a36Sopenharmony_ci struct iotlb_entry e; 132862306a36Sopenharmony_ci int omap_pgsz; 132962306a36Sopenharmony_ci u32 ret = -EINVAL; 133062306a36Sopenharmony_ci int i; 133162306a36Sopenharmony_ci 133262306a36Sopenharmony_ci omap_pgsz = bytes_to_iopgsz(bytes); 133362306a36Sopenharmony_ci if (omap_pgsz < 0) { 133462306a36Sopenharmony_ci dev_err(dev, "invalid size to map: %zu\n", bytes); 133562306a36Sopenharmony_ci return -EINVAL; 133662306a36Sopenharmony_ci } 133762306a36Sopenharmony_ci 133862306a36Sopenharmony_ci dev_dbg(dev, "mapping da 0x%lx to pa %pa size 0x%zx\n", da, &pa, bytes); 133962306a36Sopenharmony_ci 134062306a36Sopenharmony_ci iotlb_init_entry(&e, da, pa, omap_pgsz); 134162306a36Sopenharmony_ci 134262306a36Sopenharmony_ci iommu = omap_domain->iommus; 134362306a36Sopenharmony_ci for (i = 0; i < omap_domain->num_iommus; i++, iommu++) { 134462306a36Sopenharmony_ci oiommu = iommu->iommu_dev; 134562306a36Sopenharmony_ci ret = omap_iopgtable_store_entry(oiommu, &e); 134662306a36Sopenharmony_ci if (ret) { 134762306a36Sopenharmony_ci dev_err(dev, "omap_iopgtable_store_entry failed: %d\n", 134862306a36Sopenharmony_ci ret); 134962306a36Sopenharmony_ci break; 135062306a36Sopenharmony_ci } 135162306a36Sopenharmony_ci } 135262306a36Sopenharmony_ci 135362306a36Sopenharmony_ci if (ret) { 135462306a36Sopenharmony_ci while (i--) { 135562306a36Sopenharmony_ci iommu--; 135662306a36Sopenharmony_ci oiommu = iommu->iommu_dev; 135762306a36Sopenharmony_ci iopgtable_clear_entry(oiommu, da); 135862306a36Sopenharmony_ci } 135962306a36Sopenharmony_ci } 136062306a36Sopenharmony_ci 136162306a36Sopenharmony_ci return ret; 136262306a36Sopenharmony_ci} 136362306a36Sopenharmony_ci 136462306a36Sopenharmony_cistatic size_t omap_iommu_unmap(struct iommu_domain *domain, unsigned long da, 136562306a36Sopenharmony_ci size_t size, struct iommu_iotlb_gather *gather) 136662306a36Sopenharmony_ci{ 136762306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 136862306a36Sopenharmony_ci struct device *dev = omap_domain->dev; 136962306a36Sopenharmony_ci struct omap_iommu_device *iommu; 137062306a36Sopenharmony_ci struct omap_iommu *oiommu; 137162306a36Sopenharmony_ci bool error = false; 137262306a36Sopenharmony_ci size_t bytes = 0; 137362306a36Sopenharmony_ci int i; 137462306a36Sopenharmony_ci 137562306a36Sopenharmony_ci dev_dbg(dev, "unmapping da 0x%lx size %zu\n", da, size); 137662306a36Sopenharmony_ci 137762306a36Sopenharmony_ci iommu = omap_domain->iommus; 137862306a36Sopenharmony_ci for (i = 0; i < omap_domain->num_iommus; i++, iommu++) { 137962306a36Sopenharmony_ci oiommu = iommu->iommu_dev; 138062306a36Sopenharmony_ci bytes = iopgtable_clear_entry(oiommu, da); 138162306a36Sopenharmony_ci if (!bytes) 138262306a36Sopenharmony_ci error = true; 138362306a36Sopenharmony_ci } 138462306a36Sopenharmony_ci 138562306a36Sopenharmony_ci /* 138662306a36Sopenharmony_ci * simplify return - we are only checking if any of the iommus 138762306a36Sopenharmony_ci * reported an error, but not if all of them are unmapping the 138862306a36Sopenharmony_ci * same number of entries. This should not occur due to the 138962306a36Sopenharmony_ci * mirror programming. 139062306a36Sopenharmony_ci */ 139162306a36Sopenharmony_ci return error ? 0 : bytes; 139262306a36Sopenharmony_ci} 139362306a36Sopenharmony_ci 139462306a36Sopenharmony_cistatic int omap_iommu_count(struct device *dev) 139562306a36Sopenharmony_ci{ 139662306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data = dev_iommu_priv_get(dev); 139762306a36Sopenharmony_ci int count = 0; 139862306a36Sopenharmony_ci 139962306a36Sopenharmony_ci while (arch_data->iommu_dev) { 140062306a36Sopenharmony_ci count++; 140162306a36Sopenharmony_ci arch_data++; 140262306a36Sopenharmony_ci } 140362306a36Sopenharmony_ci 140462306a36Sopenharmony_ci return count; 140562306a36Sopenharmony_ci} 140662306a36Sopenharmony_ci 140762306a36Sopenharmony_ci/* caller should call cleanup if this function fails */ 140862306a36Sopenharmony_cistatic int omap_iommu_attach_init(struct device *dev, 140962306a36Sopenharmony_ci struct omap_iommu_domain *odomain) 141062306a36Sopenharmony_ci{ 141162306a36Sopenharmony_ci struct omap_iommu_device *iommu; 141262306a36Sopenharmony_ci int i; 141362306a36Sopenharmony_ci 141462306a36Sopenharmony_ci odomain->num_iommus = omap_iommu_count(dev); 141562306a36Sopenharmony_ci if (!odomain->num_iommus) 141662306a36Sopenharmony_ci return -ENODEV; 141762306a36Sopenharmony_ci 141862306a36Sopenharmony_ci odomain->iommus = kcalloc(odomain->num_iommus, sizeof(*iommu), 141962306a36Sopenharmony_ci GFP_ATOMIC); 142062306a36Sopenharmony_ci if (!odomain->iommus) 142162306a36Sopenharmony_ci return -ENOMEM; 142262306a36Sopenharmony_ci 142362306a36Sopenharmony_ci iommu = odomain->iommus; 142462306a36Sopenharmony_ci for (i = 0; i < odomain->num_iommus; i++, iommu++) { 142562306a36Sopenharmony_ci iommu->pgtable = kzalloc(IOPGD_TABLE_SIZE, GFP_ATOMIC); 142662306a36Sopenharmony_ci if (!iommu->pgtable) 142762306a36Sopenharmony_ci return -ENOMEM; 142862306a36Sopenharmony_ci 142962306a36Sopenharmony_ci /* 143062306a36Sopenharmony_ci * should never fail, but please keep this around to ensure 143162306a36Sopenharmony_ci * we keep the hardware happy 143262306a36Sopenharmony_ci */ 143362306a36Sopenharmony_ci if (WARN_ON(!IS_ALIGNED((long)iommu->pgtable, 143462306a36Sopenharmony_ci IOPGD_TABLE_SIZE))) 143562306a36Sopenharmony_ci return -EINVAL; 143662306a36Sopenharmony_ci } 143762306a36Sopenharmony_ci 143862306a36Sopenharmony_ci return 0; 143962306a36Sopenharmony_ci} 144062306a36Sopenharmony_ci 144162306a36Sopenharmony_cistatic void omap_iommu_detach_fini(struct omap_iommu_domain *odomain) 144262306a36Sopenharmony_ci{ 144362306a36Sopenharmony_ci int i; 144462306a36Sopenharmony_ci struct omap_iommu_device *iommu = odomain->iommus; 144562306a36Sopenharmony_ci 144662306a36Sopenharmony_ci for (i = 0; iommu && i < odomain->num_iommus; i++, iommu++) 144762306a36Sopenharmony_ci kfree(iommu->pgtable); 144862306a36Sopenharmony_ci 144962306a36Sopenharmony_ci kfree(odomain->iommus); 145062306a36Sopenharmony_ci odomain->num_iommus = 0; 145162306a36Sopenharmony_ci odomain->iommus = NULL; 145262306a36Sopenharmony_ci} 145362306a36Sopenharmony_ci 145462306a36Sopenharmony_cistatic int 145562306a36Sopenharmony_ciomap_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) 145662306a36Sopenharmony_ci{ 145762306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data = dev_iommu_priv_get(dev); 145862306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 145962306a36Sopenharmony_ci struct omap_iommu_device *iommu; 146062306a36Sopenharmony_ci struct omap_iommu *oiommu; 146162306a36Sopenharmony_ci int ret = 0; 146262306a36Sopenharmony_ci int i; 146362306a36Sopenharmony_ci 146462306a36Sopenharmony_ci if (!arch_data || !arch_data->iommu_dev) { 146562306a36Sopenharmony_ci dev_err(dev, "device doesn't have an associated iommu\n"); 146662306a36Sopenharmony_ci return -ENODEV; 146762306a36Sopenharmony_ci } 146862306a36Sopenharmony_ci 146962306a36Sopenharmony_ci spin_lock(&omap_domain->lock); 147062306a36Sopenharmony_ci 147162306a36Sopenharmony_ci /* only a single client device can be attached to a domain */ 147262306a36Sopenharmony_ci if (omap_domain->dev) { 147362306a36Sopenharmony_ci dev_err(dev, "iommu domain is already attached\n"); 147462306a36Sopenharmony_ci ret = -EINVAL; 147562306a36Sopenharmony_ci goto out; 147662306a36Sopenharmony_ci } 147762306a36Sopenharmony_ci 147862306a36Sopenharmony_ci ret = omap_iommu_attach_init(dev, omap_domain); 147962306a36Sopenharmony_ci if (ret) { 148062306a36Sopenharmony_ci dev_err(dev, "failed to allocate required iommu data %d\n", 148162306a36Sopenharmony_ci ret); 148262306a36Sopenharmony_ci goto init_fail; 148362306a36Sopenharmony_ci } 148462306a36Sopenharmony_ci 148562306a36Sopenharmony_ci iommu = omap_domain->iommus; 148662306a36Sopenharmony_ci for (i = 0; i < omap_domain->num_iommus; i++, iommu++, arch_data++) { 148762306a36Sopenharmony_ci /* configure and enable the omap iommu */ 148862306a36Sopenharmony_ci oiommu = arch_data->iommu_dev; 148962306a36Sopenharmony_ci ret = omap_iommu_attach(oiommu, iommu->pgtable); 149062306a36Sopenharmony_ci if (ret) { 149162306a36Sopenharmony_ci dev_err(dev, "can't get omap iommu: %d\n", ret); 149262306a36Sopenharmony_ci goto attach_fail; 149362306a36Sopenharmony_ci } 149462306a36Sopenharmony_ci 149562306a36Sopenharmony_ci oiommu->domain = domain; 149662306a36Sopenharmony_ci iommu->iommu_dev = oiommu; 149762306a36Sopenharmony_ci } 149862306a36Sopenharmony_ci 149962306a36Sopenharmony_ci omap_domain->dev = dev; 150062306a36Sopenharmony_ci 150162306a36Sopenharmony_ci goto out; 150262306a36Sopenharmony_ci 150362306a36Sopenharmony_ciattach_fail: 150462306a36Sopenharmony_ci while (i--) { 150562306a36Sopenharmony_ci iommu--; 150662306a36Sopenharmony_ci arch_data--; 150762306a36Sopenharmony_ci oiommu = iommu->iommu_dev; 150862306a36Sopenharmony_ci omap_iommu_detach(oiommu); 150962306a36Sopenharmony_ci iommu->iommu_dev = NULL; 151062306a36Sopenharmony_ci oiommu->domain = NULL; 151162306a36Sopenharmony_ci } 151262306a36Sopenharmony_ciinit_fail: 151362306a36Sopenharmony_ci omap_iommu_detach_fini(omap_domain); 151462306a36Sopenharmony_ciout: 151562306a36Sopenharmony_ci spin_unlock(&omap_domain->lock); 151662306a36Sopenharmony_ci return ret; 151762306a36Sopenharmony_ci} 151862306a36Sopenharmony_ci 151962306a36Sopenharmony_cistatic void _omap_iommu_detach_dev(struct omap_iommu_domain *omap_domain, 152062306a36Sopenharmony_ci struct device *dev) 152162306a36Sopenharmony_ci{ 152262306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data = dev_iommu_priv_get(dev); 152362306a36Sopenharmony_ci struct omap_iommu_device *iommu = omap_domain->iommus; 152462306a36Sopenharmony_ci struct omap_iommu *oiommu; 152562306a36Sopenharmony_ci int i; 152662306a36Sopenharmony_ci 152762306a36Sopenharmony_ci if (!omap_domain->dev) { 152862306a36Sopenharmony_ci dev_err(dev, "domain has no attached device\n"); 152962306a36Sopenharmony_ci return; 153062306a36Sopenharmony_ci } 153162306a36Sopenharmony_ci 153262306a36Sopenharmony_ci /* only a single device is supported per domain for now */ 153362306a36Sopenharmony_ci if (omap_domain->dev != dev) { 153462306a36Sopenharmony_ci dev_err(dev, "invalid attached device\n"); 153562306a36Sopenharmony_ci return; 153662306a36Sopenharmony_ci } 153762306a36Sopenharmony_ci 153862306a36Sopenharmony_ci /* 153962306a36Sopenharmony_ci * cleanup in the reverse order of attachment - this addresses 154062306a36Sopenharmony_ci * any h/w dependencies between multiple instances, if any 154162306a36Sopenharmony_ci */ 154262306a36Sopenharmony_ci iommu += (omap_domain->num_iommus - 1); 154362306a36Sopenharmony_ci arch_data += (omap_domain->num_iommus - 1); 154462306a36Sopenharmony_ci for (i = 0; i < omap_domain->num_iommus; i++, iommu--, arch_data--) { 154562306a36Sopenharmony_ci oiommu = iommu->iommu_dev; 154662306a36Sopenharmony_ci iopgtable_clear_entry_all(oiommu); 154762306a36Sopenharmony_ci 154862306a36Sopenharmony_ci omap_iommu_detach(oiommu); 154962306a36Sopenharmony_ci iommu->iommu_dev = NULL; 155062306a36Sopenharmony_ci oiommu->domain = NULL; 155162306a36Sopenharmony_ci } 155262306a36Sopenharmony_ci 155362306a36Sopenharmony_ci omap_iommu_detach_fini(omap_domain); 155462306a36Sopenharmony_ci 155562306a36Sopenharmony_ci omap_domain->dev = NULL; 155662306a36Sopenharmony_ci} 155762306a36Sopenharmony_ci 155862306a36Sopenharmony_cistatic void omap_iommu_set_platform_dma(struct device *dev) 155962306a36Sopenharmony_ci{ 156062306a36Sopenharmony_ci struct iommu_domain *domain = iommu_get_domain_for_dev(dev); 156162306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 156262306a36Sopenharmony_ci 156362306a36Sopenharmony_ci spin_lock(&omap_domain->lock); 156462306a36Sopenharmony_ci _omap_iommu_detach_dev(omap_domain, dev); 156562306a36Sopenharmony_ci spin_unlock(&omap_domain->lock); 156662306a36Sopenharmony_ci} 156762306a36Sopenharmony_ci 156862306a36Sopenharmony_cistatic struct iommu_domain *omap_iommu_domain_alloc(unsigned type) 156962306a36Sopenharmony_ci{ 157062306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain; 157162306a36Sopenharmony_ci 157262306a36Sopenharmony_ci if (type != IOMMU_DOMAIN_UNMANAGED) 157362306a36Sopenharmony_ci return NULL; 157462306a36Sopenharmony_ci 157562306a36Sopenharmony_ci omap_domain = kzalloc(sizeof(*omap_domain), GFP_KERNEL); 157662306a36Sopenharmony_ci if (!omap_domain) 157762306a36Sopenharmony_ci return NULL; 157862306a36Sopenharmony_ci 157962306a36Sopenharmony_ci spin_lock_init(&omap_domain->lock); 158062306a36Sopenharmony_ci 158162306a36Sopenharmony_ci omap_domain->domain.geometry.aperture_start = 0; 158262306a36Sopenharmony_ci omap_domain->domain.geometry.aperture_end = (1ULL << 32) - 1; 158362306a36Sopenharmony_ci omap_domain->domain.geometry.force_aperture = true; 158462306a36Sopenharmony_ci 158562306a36Sopenharmony_ci return &omap_domain->domain; 158662306a36Sopenharmony_ci} 158762306a36Sopenharmony_ci 158862306a36Sopenharmony_cistatic void omap_iommu_domain_free(struct iommu_domain *domain) 158962306a36Sopenharmony_ci{ 159062306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 159162306a36Sopenharmony_ci 159262306a36Sopenharmony_ci /* 159362306a36Sopenharmony_ci * An iommu device is still attached 159462306a36Sopenharmony_ci * (currently, only one device can be attached) ? 159562306a36Sopenharmony_ci */ 159662306a36Sopenharmony_ci if (omap_domain->dev) 159762306a36Sopenharmony_ci _omap_iommu_detach_dev(omap_domain, omap_domain->dev); 159862306a36Sopenharmony_ci 159962306a36Sopenharmony_ci kfree(omap_domain); 160062306a36Sopenharmony_ci} 160162306a36Sopenharmony_ci 160262306a36Sopenharmony_cistatic phys_addr_t omap_iommu_iova_to_phys(struct iommu_domain *domain, 160362306a36Sopenharmony_ci dma_addr_t da) 160462306a36Sopenharmony_ci{ 160562306a36Sopenharmony_ci struct omap_iommu_domain *omap_domain = to_omap_domain(domain); 160662306a36Sopenharmony_ci struct omap_iommu_device *iommu = omap_domain->iommus; 160762306a36Sopenharmony_ci struct omap_iommu *oiommu = iommu->iommu_dev; 160862306a36Sopenharmony_ci struct device *dev = oiommu->dev; 160962306a36Sopenharmony_ci u32 *pgd, *pte; 161062306a36Sopenharmony_ci phys_addr_t ret = 0; 161162306a36Sopenharmony_ci 161262306a36Sopenharmony_ci /* 161362306a36Sopenharmony_ci * all the iommus within the domain will have identical programming, 161462306a36Sopenharmony_ci * so perform the lookup using just the first iommu 161562306a36Sopenharmony_ci */ 161662306a36Sopenharmony_ci iopgtable_lookup_entry(oiommu, da, &pgd, &pte); 161762306a36Sopenharmony_ci 161862306a36Sopenharmony_ci if (pte) { 161962306a36Sopenharmony_ci if (iopte_is_small(*pte)) 162062306a36Sopenharmony_ci ret = omap_iommu_translate(*pte, da, IOPTE_MASK); 162162306a36Sopenharmony_ci else if (iopte_is_large(*pte)) 162262306a36Sopenharmony_ci ret = omap_iommu_translate(*pte, da, IOLARGE_MASK); 162362306a36Sopenharmony_ci else 162462306a36Sopenharmony_ci dev_err(dev, "bogus pte 0x%x, da 0x%llx", *pte, 162562306a36Sopenharmony_ci (unsigned long long)da); 162662306a36Sopenharmony_ci } else { 162762306a36Sopenharmony_ci if (iopgd_is_section(*pgd)) 162862306a36Sopenharmony_ci ret = omap_iommu_translate(*pgd, da, IOSECTION_MASK); 162962306a36Sopenharmony_ci else if (iopgd_is_super(*pgd)) 163062306a36Sopenharmony_ci ret = omap_iommu_translate(*pgd, da, IOSUPER_MASK); 163162306a36Sopenharmony_ci else 163262306a36Sopenharmony_ci dev_err(dev, "bogus pgd 0x%x, da 0x%llx", *pgd, 163362306a36Sopenharmony_ci (unsigned long long)da); 163462306a36Sopenharmony_ci } 163562306a36Sopenharmony_ci 163662306a36Sopenharmony_ci return ret; 163762306a36Sopenharmony_ci} 163862306a36Sopenharmony_ci 163962306a36Sopenharmony_cistatic struct iommu_device *omap_iommu_probe_device(struct device *dev) 164062306a36Sopenharmony_ci{ 164162306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data, *tmp; 164262306a36Sopenharmony_ci struct platform_device *pdev; 164362306a36Sopenharmony_ci struct omap_iommu *oiommu; 164462306a36Sopenharmony_ci struct device_node *np; 164562306a36Sopenharmony_ci int num_iommus, i; 164662306a36Sopenharmony_ci 164762306a36Sopenharmony_ci /* 164862306a36Sopenharmony_ci * Allocate the per-device iommu structure for DT-based devices. 164962306a36Sopenharmony_ci * 165062306a36Sopenharmony_ci * TODO: Simplify this when removing non-DT support completely from the 165162306a36Sopenharmony_ci * IOMMU users. 165262306a36Sopenharmony_ci */ 165362306a36Sopenharmony_ci if (!dev->of_node) 165462306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 165562306a36Sopenharmony_ci 165662306a36Sopenharmony_ci /* 165762306a36Sopenharmony_ci * retrieve the count of IOMMU nodes using phandle size as element size 165862306a36Sopenharmony_ci * since #iommu-cells = 0 for OMAP 165962306a36Sopenharmony_ci */ 166062306a36Sopenharmony_ci num_iommus = of_property_count_elems_of_size(dev->of_node, "iommus", 166162306a36Sopenharmony_ci sizeof(phandle)); 166262306a36Sopenharmony_ci if (num_iommus < 0) 166362306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 166462306a36Sopenharmony_ci 166562306a36Sopenharmony_ci arch_data = kcalloc(num_iommus + 1, sizeof(*arch_data), GFP_KERNEL); 166662306a36Sopenharmony_ci if (!arch_data) 166762306a36Sopenharmony_ci return ERR_PTR(-ENOMEM); 166862306a36Sopenharmony_ci 166962306a36Sopenharmony_ci for (i = 0, tmp = arch_data; i < num_iommus; i++, tmp++) { 167062306a36Sopenharmony_ci np = of_parse_phandle(dev->of_node, "iommus", i); 167162306a36Sopenharmony_ci if (!np) { 167262306a36Sopenharmony_ci kfree(arch_data); 167362306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 167462306a36Sopenharmony_ci } 167562306a36Sopenharmony_ci 167662306a36Sopenharmony_ci pdev = of_find_device_by_node(np); 167762306a36Sopenharmony_ci if (!pdev) { 167862306a36Sopenharmony_ci of_node_put(np); 167962306a36Sopenharmony_ci kfree(arch_data); 168062306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 168162306a36Sopenharmony_ci } 168262306a36Sopenharmony_ci 168362306a36Sopenharmony_ci oiommu = platform_get_drvdata(pdev); 168462306a36Sopenharmony_ci if (!oiommu) { 168562306a36Sopenharmony_ci of_node_put(np); 168662306a36Sopenharmony_ci kfree(arch_data); 168762306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 168862306a36Sopenharmony_ci } 168962306a36Sopenharmony_ci 169062306a36Sopenharmony_ci tmp->iommu_dev = oiommu; 169162306a36Sopenharmony_ci tmp->dev = &pdev->dev; 169262306a36Sopenharmony_ci 169362306a36Sopenharmony_ci of_node_put(np); 169462306a36Sopenharmony_ci } 169562306a36Sopenharmony_ci 169662306a36Sopenharmony_ci dev_iommu_priv_set(dev, arch_data); 169762306a36Sopenharmony_ci 169862306a36Sopenharmony_ci /* 169962306a36Sopenharmony_ci * use the first IOMMU alone for the sysfs device linking. 170062306a36Sopenharmony_ci * TODO: Evaluate if a single iommu_group needs to be 170162306a36Sopenharmony_ci * maintained for both IOMMUs 170262306a36Sopenharmony_ci */ 170362306a36Sopenharmony_ci oiommu = arch_data->iommu_dev; 170462306a36Sopenharmony_ci 170562306a36Sopenharmony_ci return &oiommu->iommu; 170662306a36Sopenharmony_ci} 170762306a36Sopenharmony_ci 170862306a36Sopenharmony_cistatic void omap_iommu_release_device(struct device *dev) 170962306a36Sopenharmony_ci{ 171062306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data = dev_iommu_priv_get(dev); 171162306a36Sopenharmony_ci 171262306a36Sopenharmony_ci if (!dev->of_node || !arch_data) 171362306a36Sopenharmony_ci return; 171462306a36Sopenharmony_ci 171562306a36Sopenharmony_ci dev_iommu_priv_set(dev, NULL); 171662306a36Sopenharmony_ci kfree(arch_data); 171762306a36Sopenharmony_ci 171862306a36Sopenharmony_ci} 171962306a36Sopenharmony_ci 172062306a36Sopenharmony_cistatic struct iommu_group *omap_iommu_device_group(struct device *dev) 172162306a36Sopenharmony_ci{ 172262306a36Sopenharmony_ci struct omap_iommu_arch_data *arch_data = dev_iommu_priv_get(dev); 172362306a36Sopenharmony_ci struct iommu_group *group = ERR_PTR(-EINVAL); 172462306a36Sopenharmony_ci 172562306a36Sopenharmony_ci if (!arch_data) 172662306a36Sopenharmony_ci return ERR_PTR(-ENODEV); 172762306a36Sopenharmony_ci 172862306a36Sopenharmony_ci if (arch_data->iommu_dev) 172962306a36Sopenharmony_ci group = iommu_group_ref_get(arch_data->iommu_dev->group); 173062306a36Sopenharmony_ci 173162306a36Sopenharmony_ci return group; 173262306a36Sopenharmony_ci} 173362306a36Sopenharmony_ci 173462306a36Sopenharmony_cistatic const struct iommu_ops omap_iommu_ops = { 173562306a36Sopenharmony_ci .domain_alloc = omap_iommu_domain_alloc, 173662306a36Sopenharmony_ci .probe_device = omap_iommu_probe_device, 173762306a36Sopenharmony_ci .release_device = omap_iommu_release_device, 173862306a36Sopenharmony_ci .device_group = omap_iommu_device_group, 173962306a36Sopenharmony_ci .set_platform_dma_ops = omap_iommu_set_platform_dma, 174062306a36Sopenharmony_ci .pgsize_bitmap = OMAP_IOMMU_PGSIZES, 174162306a36Sopenharmony_ci .default_domain_ops = &(const struct iommu_domain_ops) { 174262306a36Sopenharmony_ci .attach_dev = omap_iommu_attach_dev, 174362306a36Sopenharmony_ci .map = omap_iommu_map, 174462306a36Sopenharmony_ci .unmap = omap_iommu_unmap, 174562306a36Sopenharmony_ci .iova_to_phys = omap_iommu_iova_to_phys, 174662306a36Sopenharmony_ci .free = omap_iommu_domain_free, 174762306a36Sopenharmony_ci } 174862306a36Sopenharmony_ci}; 174962306a36Sopenharmony_ci 175062306a36Sopenharmony_cistatic int __init omap_iommu_init(void) 175162306a36Sopenharmony_ci{ 175262306a36Sopenharmony_ci struct kmem_cache *p; 175362306a36Sopenharmony_ci const slab_flags_t flags = SLAB_HWCACHE_ALIGN; 175462306a36Sopenharmony_ci size_t align = 1 << 10; /* L2 pagetable alignement */ 175562306a36Sopenharmony_ci struct device_node *np; 175662306a36Sopenharmony_ci int ret; 175762306a36Sopenharmony_ci 175862306a36Sopenharmony_ci np = of_find_matching_node(NULL, omap_iommu_of_match); 175962306a36Sopenharmony_ci if (!np) 176062306a36Sopenharmony_ci return 0; 176162306a36Sopenharmony_ci 176262306a36Sopenharmony_ci of_node_put(np); 176362306a36Sopenharmony_ci 176462306a36Sopenharmony_ci p = kmem_cache_create("iopte_cache", IOPTE_TABLE_SIZE, align, flags, 176562306a36Sopenharmony_ci NULL); 176662306a36Sopenharmony_ci if (!p) 176762306a36Sopenharmony_ci return -ENOMEM; 176862306a36Sopenharmony_ci iopte_cachep = p; 176962306a36Sopenharmony_ci 177062306a36Sopenharmony_ci omap_iommu_debugfs_init(); 177162306a36Sopenharmony_ci 177262306a36Sopenharmony_ci ret = platform_driver_register(&omap_iommu_driver); 177362306a36Sopenharmony_ci if (ret) { 177462306a36Sopenharmony_ci pr_err("%s: failed to register driver\n", __func__); 177562306a36Sopenharmony_ci goto fail_driver; 177662306a36Sopenharmony_ci } 177762306a36Sopenharmony_ci 177862306a36Sopenharmony_ci return 0; 177962306a36Sopenharmony_ci 178062306a36Sopenharmony_cifail_driver: 178162306a36Sopenharmony_ci kmem_cache_destroy(iopte_cachep); 178262306a36Sopenharmony_ci return ret; 178362306a36Sopenharmony_ci} 178462306a36Sopenharmony_cisubsys_initcall(omap_iommu_init); 178562306a36Sopenharmony_ci/* must be ready before omap3isp is probed */ 1786