| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (C) 2025 Collabora Ltd. |
| * |
| * IOMMU API for Verisilicon |
| * |
| * Module Authors: Yandong Lin <yandong.lin@rock-chips.com> |
| * Simon Xue <xxm@rock-chips.com> |
| * Benjamin Gaignard <benjamin.gaignard@collabora.com> |
| * |
| * This hardware block is using a 2 pages tables allocation structure. |
| * That make very similar to Rockhip iommu hardware blocks but it has |
| * it own driver because the registers offset and configuration bits |
| * are completely different. An additional reason is that this hardware |
| * has been developed by Verisilicon to be used by their hardware video |
| * decoders and not for a general purpose like Rockchip iommus. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/compiler.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/errno.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/iommu.h> |
| #include <linux/list.h> |
| #include <linux/mm.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_iommu.h> |
| #include <linux/of_platform.h> |
| #include <linux/platform_device.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| |
| #include "iommu-pages.h" |
| |
| struct vsi_iommu { |
| struct device *dev; |
| void __iomem *regs; |
| struct clk_bulk_data *clocks; |
| int num_clocks; |
| struct iommu_device iommu; |
| struct list_head node; /* entry in vsi_iommu_domain.iommus */ |
| struct iommu_domain *domain; /* domain to which iommu is attached */ |
| spinlock_t lock; /* lock to protect vsi_iommu fields */ |
| int irq; |
| bool enable; |
| }; |
| |
| struct vsi_iommu_domain { |
| struct list_head iommus; |
| struct device *dev; |
| u32 *dt; |
| dma_addr_t dt_dma; |
| struct iommu_domain domain; |
| u64 *pta; |
| dma_addr_t pta_dma; |
| spinlock_t lock; /* lock to protect vsi_iommu_domain fields */ |
| }; |
| |
| static struct iommu_domain vsi_identity_domain; |
| |
| #define NUM_DT_ENTRIES 1024 |
| #define NUM_PT_ENTRIES 1024 |
| |
| #define SPAGE_SIZE BIT(12) |
| |
| /* vsi iommu regs address */ |
| #define VSI_MMU_CONFIG1_BASE 0x1ac |
| #define VSI_MMU_AHB_EXCEPTION_BASE 0x380 |
| #define VSI_MMU_AHB_CONTROL_BASE 0x388 |
| #define VSI_MMU_AHB_TLB_ARRAY_BASE_L_BASE 0x38C |
| |
| /* MMU register offsets */ |
| #define VSI_MMU_FLUSH_BASE 0x184 |
| #define VSI_MMU_BIT_FLUSH BIT(4) |
| |
| #define VSI_MMU_PAGE_FAULT_ADDR 0x380 |
| #define VSI_MMU_STATUS_BASE 0x384 /* IRQ status */ |
| |
| #define VSI_MMU_BIT_ENABLE BIT(0) |
| |
| #define VSI_MMU_OUT_OF_BOUND BIT(28) |
| /* Irq mask */ |
| #define VSI_MMU_IRQ_MASK 0x7 |
| |
| #define VSI_DTE_PT_ADDRESS_MASK 0xffffffc0 |
| #define VSI_DTE_PT_VALID BIT(0) |
| |
| #define VSI_PAGE_DESC_LO_MASK 0xfffff000 |
| #define VSI_PAGE_DESC_HI_MASK GENMASK_ULL(39, 32) |
| #define VSI_PAGE_DESC_HI_SHIFT (32 - 4) |
| |
| static inline phys_addr_t vsi_dte_pt_address(u32 dte) |
| { |
| return (phys_addr_t)dte & VSI_DTE_PT_ADDRESS_MASK; |
| } |
| |
| static inline u32 vsi_mk_dte(u32 dte) |
| { |
| return (phys_addr_t)dte | VSI_DTE_PT_VALID; |
| } |
| |
| #define VSI_PTE_PAGE_WRITABLE BIT(2) |
| #define VSI_PTE_PAGE_VALID BIT(0) |
| |
| static inline phys_addr_t vsi_pte_page_address(u64 pte) |
| { |
| return ((pte << VSI_PAGE_DESC_HI_SHIFT) & VSI_PAGE_DESC_HI_MASK) | |
| (pte & VSI_PAGE_DESC_LO_MASK); |
| } |
| |
| static u32 vsi_mk_pte(phys_addr_t page, int prot) |
| { |
| u32 flags = 0; |
| |
| flags |= (prot & IOMMU_WRITE) ? VSI_PTE_PAGE_WRITABLE : 0; |
| |
| page = (page & VSI_PAGE_DESC_LO_MASK) | |
| ((page & VSI_PAGE_DESC_HI_MASK) >> VSI_PAGE_DESC_HI_SHIFT); |
| |
| return page | flags | VSI_PTE_PAGE_VALID; |
| } |
| |
| #define VSI_DTE_PT_VALID BIT(0) |
| |
| static inline bool vsi_dte_is_pt_valid(u32 dte) |
| { |
| return dte & VSI_DTE_PT_VALID; |
| } |
| |
| static inline bool vsi_pte_is_page_valid(u32 pte) |
| { |
| return pte & VSI_PTE_PAGE_VALID; |
| } |
| |
| static u32 vsi_mk_pte_invalid(u32 pte) |
| { |
| return pte & ~VSI_PTE_PAGE_VALID; |
| } |
| |
| #define VSI_MASTER_TLB_MASK GENMASK_ULL(31, 10) |
| /* mode 0 : 4k */ |
| #define VSI_PTA_4K_MODE 0 |
| |
| static u64 vsi_mk_pta(dma_addr_t dt_dma) |
| { |
| u64 val = (dt_dma & VSI_MASTER_TLB_MASK) | VSI_PTA_4K_MODE; |
| |
| return val; |
| } |
| |
| static struct vsi_iommu_domain *to_vsi_domain(struct iommu_domain *dom) |
| { |
| return container_of(dom, struct vsi_iommu_domain, domain); |
| } |
| |
| static inline void vsi_table_flush(struct vsi_iommu_domain *vsi_domain, dma_addr_t dma, |
| unsigned int count) |
| { |
| size_t size = count * sizeof(u32); /* count of u32 entry */ |
| |
| dma_sync_single_for_device(vsi_domain->dev, dma, size, DMA_TO_DEVICE); |
| } |
| |
| #define VSI_IOVA_DTE_MASK 0xffc00000 |
| #define VSI_IOVA_DTE_SHIFT 22 |
| #define VSI_IOVA_PTE_MASK 0x003ff000 |
| #define VSI_IOVA_PTE_SHIFT 12 |
| #define VSI_IOVA_PAGE_MASK 0x00000fff |
| #define VSI_IOVA_PAGE_SHIFT 0 |
| |
| static u32 vsi_iova_dte_index(u32 iova) |
| { |
| return (iova & VSI_IOVA_DTE_MASK) >> VSI_IOVA_DTE_SHIFT; |
| } |
| |
| static u32 vsi_iova_pte_index(u32 iova) |
| { |
| return (iova & VSI_IOVA_PTE_MASK) >> VSI_IOVA_PTE_SHIFT; |
| } |
| |
| static u32 vsi_iova_page_offset(u32 iova) |
| { |
| return (iova & VSI_IOVA_PAGE_MASK) >> VSI_IOVA_PAGE_SHIFT; |
| } |
| |
| static irqreturn_t vsi_iommu_irq(int irq, void *dev_id) |
| { |
| struct vsi_iommu *iommu = dev_id; |
| unsigned long flags; |
| dma_addr_t iova; |
| u32 status; |
| |
| if (pm_runtime_resume_and_get(iommu->dev) < 0) |
| return IRQ_NONE; |
| |
| spin_lock_irqsave(&iommu->lock, flags); |
| |
| status = readl(iommu->regs + VSI_MMU_STATUS_BASE); |
| if (status & VSI_MMU_IRQ_MASK) { |
| dev_err(iommu->dev, "unexpected int_status=%08x\n", status); |
| iova = readl(iommu->regs + VSI_MMU_PAGE_FAULT_ADDR); |
| report_iommu_fault(iommu->domain, iommu->dev, iova, status); |
| } |
| writel(0, iommu->regs + VSI_MMU_STATUS_BASE); |
| |
| spin_unlock_irqrestore(&iommu->lock, flags); |
| pm_runtime_put_autosuspend(iommu->dev); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static struct vsi_iommu *vsi_iommu_get_from_dev(struct device *dev) |
| { |
| struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev); |
| struct device *iommu_dev = bus_find_device_by_fwnode(&platform_bus_type, |
| fwspec->iommu_fwnode); |
| |
| put_device(iommu_dev); |
| |
| return iommu_dev ? dev_get_drvdata(iommu_dev) : NULL; |
| } |
| |
| static struct iommu_domain *vsi_iommu_domain_alloc_paging(struct device *dev) |
| { |
| struct vsi_iommu *iommu = dev_iommu_priv_get(dev); |
| struct vsi_iommu_domain *vsi_domain; |
| |
| vsi_domain = kzalloc(sizeof(*vsi_domain), GFP_KERNEL); |
| if (!vsi_domain) |
| return NULL; |
| |
| vsi_domain->dev = iommu->dev; |
| spin_lock_init(&vsi_domain->lock); |
| |
| /* |
| * iommu use a 2 level pagetable. |
| * Each level1 (dt) and level2 (pt) table has 1024 4-byte entries. |
| * Allocate one 4 KiB page for each table. |
| */ |
| vsi_domain->dt = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32, |
| SPAGE_SIZE); |
| if (!vsi_domain->dt) |
| goto err_free_domain; |
| |
| vsi_domain->dt_dma = dma_map_single(vsi_domain->dev, vsi_domain->dt, |
| SPAGE_SIZE, DMA_TO_DEVICE); |
| if (dma_mapping_error(vsi_domain->dev, vsi_domain->dt_dma)) { |
| dev_err(dev, "DMA map error for DT\n"); |
| goto err_free_dt; |
| } |
| |
| vsi_domain->pta = iommu_alloc_pages_sz(GFP_KERNEL | GFP_DMA32, |
| SPAGE_SIZE); |
| if (!vsi_domain->pta) |
| goto err_unmap_dt; |
| |
| vsi_domain->pta[0] = vsi_mk_pta(vsi_domain->dt_dma); |
| vsi_domain->pta_dma = dma_map_single(vsi_domain->dev, vsi_domain->pta, |
| SPAGE_SIZE, DMA_TO_DEVICE); |
| if (dma_mapping_error(vsi_domain->dev, vsi_domain->pta_dma)) { |
| dev_err(dev, "DMA map error for PTA\n"); |
| goto err_free_pta; |
| } |
| |
| INIT_LIST_HEAD(&vsi_domain->iommus); |
| |
| vsi_domain->domain.geometry.aperture_start = 0; |
| vsi_domain->domain.geometry.aperture_end = DMA_BIT_MASK(32); |
| vsi_domain->domain.geometry.force_aperture = true; |
| vsi_domain->domain.pgsize_bitmap = SZ_4K; |
| |
| return &vsi_domain->domain; |
| |
| err_free_pta: |
| iommu_free_pages(vsi_domain->pta); |
| err_unmap_dt: |
| dma_unmap_single(vsi_domain->dev, vsi_domain->dt_dma, |
| SPAGE_SIZE, DMA_TO_DEVICE); |
| err_free_dt: |
| iommu_free_pages(vsi_domain->dt); |
| err_free_domain: |
| kfree(vsi_domain); |
| |
| return NULL; |
| } |
| |
| static phys_addr_t vsi_iommu_iova_to_phys(struct iommu_domain *domain, |
| dma_addr_t iova) |
| { |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| phys_addr_t pt_phys, phys = 0; |
| unsigned long flags; |
| u32 dte, pte; |
| u32 *page_table; |
| |
| spin_lock_irqsave(&vsi_domain->lock, flags); |
| dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; |
| if (!vsi_dte_is_pt_valid(dte)) |
| goto unlock; |
| |
| pt_phys = vsi_dte_pt_address(dte); |
| page_table = (u32 *)phys_to_virt(pt_phys); |
| pte = page_table[vsi_iova_pte_index(iova)]; |
| if (!vsi_pte_is_page_valid(pte)) |
| goto unlock; |
| |
| phys = vsi_pte_page_address(pte) + vsi_iova_page_offset(iova); |
| |
| unlock: |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| return phys; |
| } |
| |
| static size_t vsi_iommu_unmap_iova(struct vsi_iommu_domain *vsi_domain, |
| u32 *pte_addr, dma_addr_t pte_dma, |
| size_t size) |
| { |
| unsigned int pte_count; |
| unsigned int pte_total = size / SPAGE_SIZE; |
| |
| for (pte_count = 0; |
| pte_count < pte_total && pte_count < NUM_PT_ENTRIES; pte_count++) { |
| u32 pte = pte_addr[pte_count]; |
| |
| if (!vsi_pte_is_page_valid(pte)) |
| break; |
| |
| pte_addr[pte_count] = vsi_mk_pte_invalid(pte); |
| } |
| |
| vsi_table_flush(vsi_domain, pte_dma, pte_total); |
| |
| return pte_count * SPAGE_SIZE; |
| } |
| |
| static int vsi_iommu_map_iova(struct vsi_iommu_domain *vsi_domain, u32 *pte_addr, |
| dma_addr_t pte_dma, dma_addr_t iova, |
| phys_addr_t paddr, size_t size, int prot) |
| { |
| unsigned int pte_count; |
| unsigned int pte_total = size / SPAGE_SIZE; |
| |
| for (pte_count = 0; |
| pte_count < pte_total && pte_count < NUM_PT_ENTRIES; pte_count++) { |
| u32 pte = pte_addr[pte_count]; |
| |
| if (vsi_pte_is_page_valid(pte)) |
| return (pte_count - 1) * SPAGE_SIZE; |
| |
| pte_addr[pte_count] = vsi_mk_pte(paddr, prot); |
| |
| paddr += SPAGE_SIZE; |
| } |
| |
| vsi_table_flush(vsi_domain, pte_dma, pte_total); |
| |
| return 0; |
| } |
| |
| static void vsi_iommu_flush_tlb(struct iommu_domain *domain) |
| { |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| struct vsi_iommu *iommu; |
| |
| list_for_each_entry(iommu, &vsi_domain->iommus, node) { |
| if (pm_runtime_get(iommu->dev) < 0) |
| continue; |
| |
| spin_lock(&iommu->lock); |
| |
| if (iommu->enable) { |
| writel(VSI_MMU_BIT_FLUSH, iommu->regs + VSI_MMU_FLUSH_BASE); |
| writel(0, iommu->regs + VSI_MMU_FLUSH_BASE); |
| } |
| |
| spin_unlock(&iommu->lock); |
| |
| pm_runtime_put_autosuspend(iommu->dev); |
| } |
| } |
| |
| static size_t vsi_iommu_unmap(struct iommu_domain *domain, unsigned long _iova, |
| size_t size, size_t count, struct iommu_iotlb_gather *gather) |
| { |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| dma_addr_t pte_dma, iova = (dma_addr_t)_iova; |
| unsigned long flags; |
| phys_addr_t pt_phys; |
| u32 dte; |
| u32 *pte_addr; |
| size_t unmap_size = 0; |
| |
| spin_lock_irqsave(&vsi_domain->lock, flags); |
| |
| dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; |
| /* Just return 0 if iova is unmapped */ |
| if (!vsi_dte_is_pt_valid(dte)) |
| goto unlock; |
| |
| pt_phys = vsi_dte_pt_address(dte); |
| pte_addr = (u32 *)phys_to_virt(pt_phys) + vsi_iova_pte_index(iova); |
| pte_dma = pt_phys + vsi_iova_pte_index(iova) * sizeof(u32); |
| unmap_size = vsi_iommu_unmap_iova(vsi_domain, pte_addr, pte_dma, size); |
| if (!unmap_size) |
| goto unlock; |
| |
| vsi_iommu_flush_tlb(domain); |
| unlock: |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| |
| return unmap_size; |
| } |
| |
| static u32 *vsi_dte_get_page_table(struct vsi_iommu_domain *vsi_domain, |
| dma_addr_t iova, gfp_t gfp) |
| { |
| u32 *page_table, *dte_addr; |
| u32 dte_index, dte; |
| phys_addr_t pt_phys; |
| dma_addr_t pt_dma; |
| gfp_t flags; |
| |
| dte_index = vsi_iova_dte_index(iova); |
| dte_addr = &vsi_domain->dt[dte_index]; |
| dte = *dte_addr; |
| if (vsi_dte_is_pt_valid(dte)) |
| goto done; |
| |
| /* Do not allow to sleep while allocating the buffer */ |
| flags = (gfp & ~GFP_KERNEL) | GFP_ATOMIC | GFP_DMA32; |
| page_table = iommu_alloc_pages_sz(flags, PAGE_SIZE); |
| if (!page_table) |
| return ERR_PTR(-ENOMEM); |
| |
| pt_dma = dma_map_single(vsi_domain->dev, page_table, PAGE_SIZE, DMA_TO_DEVICE); |
| if (dma_mapping_error(vsi_domain->dev, pt_dma)) { |
| dev_err(vsi_domain->dev, "DMA mapping error while allocating page table\n"); |
| iommu_free_pages(page_table); |
| return ERR_PTR(-ENOMEM); |
| } |
| |
| dte = vsi_mk_dte(pt_dma); |
| *dte_addr = dte; |
| |
| vsi_table_flush(vsi_domain, |
| vsi_domain->dt_dma + dte_index * sizeof(u32), 1); |
| done: |
| pt_phys = vsi_dte_pt_address(dte); |
| return (u32 *)phys_to_virt(pt_phys); |
| } |
| |
| static int vsi_iommu_map(struct iommu_domain *domain, unsigned long _iova, |
| phys_addr_t paddr, size_t size, size_t count, |
| int prot, gfp_t gfp, size_t *mapped) |
| { |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| dma_addr_t pte_dma, iova = (dma_addr_t)_iova; |
| u32 *page_table, *pte_addr; |
| u32 dte, pte_index; |
| unsigned long flags; |
| int ret; |
| |
| spin_lock_irqsave(&vsi_domain->lock, flags); |
| |
| page_table = vsi_dte_get_page_table(vsi_domain, iova, gfp); |
| if (IS_ERR(page_table)) { |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| return PTR_ERR(page_table); |
| } |
| |
| dte = vsi_domain->dt[vsi_iova_dte_index(iova)]; |
| pte_index = vsi_iova_pte_index(iova); |
| pte_addr = &page_table[pte_index]; |
| pte_dma = vsi_dte_pt_address(dte) + pte_index * sizeof(u32); |
| ret = vsi_iommu_map_iova(vsi_domain, pte_addr, pte_dma, iova, |
| paddr, size, prot); |
| if (!ret) |
| *mapped = size; |
| |
| vsi_iommu_flush_tlb(domain); |
| |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| |
| return ret; |
| } |
| |
| static void vsi_iommu_disable(struct vsi_iommu *iommu) |
| { |
| writel(0, iommu->regs + VSI_MMU_AHB_CONTROL_BASE); |
| iommu->enable = false; |
| } |
| |
| static int vsi_iommu_identity_attach(struct iommu_domain *domain, |
| struct device *dev, struct iommu_domain *old) |
| { |
| struct vsi_iommu *iommu = dev_iommu_priv_get(dev); |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| unsigned long flags; |
| int ret; |
| |
| ret = pm_runtime_resume_and_get(iommu->dev); |
| if (ret < 0) |
| return ret; |
| |
| spin_lock_irqsave(&vsi_domain->lock, flags); |
| spin_lock(&iommu->lock); |
| if (iommu->domain == domain) |
| goto unlock; |
| |
| vsi_iommu_disable(iommu); |
| list_del_init(&iommu->node); |
| |
| iommu->domain = domain; |
| |
| unlock: |
| spin_unlock(&iommu->lock); |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| pm_runtime_put_autosuspend(iommu->dev); |
| return 0; |
| } |
| |
| static const struct iommu_domain_ops vsi_identity_ops = { |
| .attach_dev = vsi_iommu_identity_attach, |
| }; |
| |
| static struct iommu_domain vsi_identity_domain = { |
| .type = IOMMU_DOMAIN_IDENTITY, |
| .ops = &vsi_identity_ops, |
| }; |
| |
| static void vsi_iommu_enable(struct vsi_iommu *iommu, struct iommu_domain *domain) |
| { |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| |
| if (domain == &vsi_identity_domain) |
| return; |
| |
| writel(vsi_domain->pta_dma, iommu->regs + VSI_MMU_AHB_TLB_ARRAY_BASE_L_BASE); |
| writel(VSI_MMU_OUT_OF_BOUND, iommu->regs + VSI_MMU_CONFIG1_BASE); |
| writel(VSI_MMU_BIT_ENABLE, iommu->regs + VSI_MMU_AHB_EXCEPTION_BASE); |
| writel(VSI_MMU_BIT_ENABLE, iommu->regs + VSI_MMU_AHB_CONTROL_BASE); |
| iommu->enable = true; |
| } |
| |
| static int vsi_iommu_attach_device(struct iommu_domain *domain, |
| struct device *dev, struct iommu_domain *old) |
| { |
| struct vsi_iommu *iommu = dev_iommu_priv_get(dev); |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| unsigned long flags; |
| int ret = 0; |
| |
| ret = pm_runtime_resume_and_get(iommu->dev); |
| if (ret < 0) |
| return ret; |
| |
| spin_lock_irqsave(&vsi_domain->lock, flags); |
| spin_lock(&iommu->lock); |
| |
| vsi_iommu_enable(iommu, domain); |
| writel(VSI_MMU_BIT_FLUSH, iommu->regs + VSI_MMU_FLUSH_BASE); |
| writel(0, iommu->regs + VSI_MMU_FLUSH_BASE); |
| |
| list_del_init(&iommu->node); |
| list_add_tail(&iommu->node, &vsi_domain->iommus); |
| |
| iommu->domain = domain; |
| |
| spin_unlock(&iommu->lock); |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| pm_runtime_put_autosuspend(iommu->dev); |
| return ret; |
| } |
| |
| static void vsi_iommu_domain_free(struct iommu_domain *domain) |
| { |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(domain); |
| unsigned long flags; |
| int i; |
| |
| spin_lock_irqsave(&vsi_domain->lock, flags); |
| |
| WARN_ON(!list_empty(&vsi_domain->iommus)); |
| |
| for (i = 0; i < NUM_DT_ENTRIES; i++) { |
| u32 dte = vsi_domain->dt[i]; |
| |
| if (vsi_dte_is_pt_valid(dte)) { |
| phys_addr_t pt_phys = vsi_dte_pt_address(dte); |
| u32 *page_table = phys_to_virt(pt_phys); |
| |
| dma_unmap_single(vsi_domain->dev, pt_phys, |
| SPAGE_SIZE, DMA_TO_DEVICE); |
| iommu_free_pages(page_table); |
| } |
| } |
| |
| dma_unmap_single(vsi_domain->dev, vsi_domain->dt_dma, |
| SPAGE_SIZE, DMA_TO_DEVICE); |
| iommu_free_pages(vsi_domain->dt); |
| |
| dma_unmap_single(vsi_domain->dev, vsi_domain->pta_dma, |
| SPAGE_SIZE, DMA_TO_DEVICE); |
| iommu_free_pages(vsi_domain->pta); |
| |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| |
| kfree(vsi_domain); |
| } |
| |
| static struct iommu_device *vsi_iommu_probe_device(struct device *dev) |
| { |
| struct vsi_iommu *iommu = vsi_iommu_get_from_dev(dev); |
| struct device_link *link; |
| |
| link = device_link_add(dev, iommu->dev, |
| DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME); |
| if (!link) |
| dev_err(dev, "Unable to link %s\n", dev_name(iommu->dev)); |
| |
| dev_iommu_priv_set(dev, iommu); |
| return &iommu->iommu; |
| } |
| |
| static void vsi_iommu_release_device(struct device *dev) |
| { |
| struct vsi_iommu *iommu = dev_iommu_priv_get(dev); |
| |
| device_link_remove(dev, iommu->dev); |
| } |
| |
| static int vsi_iommu_of_xlate(struct device *dev, const struct of_phandle_args *args) |
| { |
| return iommu_fwspec_add_ids(dev, args->args, 1); |
| } |
| |
| static const struct iommu_ops vsi_iommu_ops = { |
| .identity_domain = &vsi_identity_domain, |
| .release_domain = &vsi_identity_domain, |
| .domain_alloc_paging = vsi_iommu_domain_alloc_paging, |
| .of_xlate = vsi_iommu_of_xlate, |
| .probe_device = vsi_iommu_probe_device, |
| .release_device = vsi_iommu_release_device, |
| .device_group = generic_single_device_group, |
| .owner = THIS_MODULE, |
| .default_domain_ops = &(const struct iommu_domain_ops) { |
| .attach_dev = vsi_iommu_attach_device, |
| .map_pages = vsi_iommu_map, |
| .unmap_pages = vsi_iommu_unmap, |
| .iova_to_phys = vsi_iommu_iova_to_phys, |
| .free = vsi_iommu_domain_free, |
| } |
| }; |
| |
| static const struct of_device_id vsi_iommu_dt_ids[] = { |
| { |
| .compatible = "verisilicon,iommu-1.2", |
| }, |
| { /* sentinel */ } |
| }; |
| MODULE_DEVICE_TABLE(of, vsi_iommu_dt_ids); |
| |
| static int vsi_iommu_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct vsi_iommu *iommu; |
| int err; |
| |
| iommu = devm_kzalloc(dev, sizeof(*iommu), GFP_KERNEL); |
| if (!iommu) |
| return -ENOMEM; |
| |
| iommu->dev = dev; |
| spin_lock_init(&iommu->lock); |
| INIT_LIST_HEAD(&iommu->node); |
| |
| iommu->regs = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(iommu->regs)) |
| return -ENOMEM; |
| |
| iommu->num_clocks = devm_clk_bulk_get_all(dev, &iommu->clocks); |
| if (iommu->num_clocks < 0) |
| return iommu->num_clocks; |
| |
| err = clk_bulk_prepare(iommu->num_clocks, iommu->clocks); |
| if (err) |
| return err; |
| |
| iommu->irq = platform_get_irq(pdev, 0); |
| if (iommu->irq < 0) |
| return iommu->irq; |
| |
| err = devm_request_irq(iommu->dev, iommu->irq, vsi_iommu_irq, |
| IRQF_SHARED, dev_name(dev), iommu); |
| if (err) |
| goto err_unprepare_clocks; |
| |
| dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); |
| platform_set_drvdata(pdev, iommu); |
| |
| pm_runtime_set_autosuspend_delay(dev, 100); |
| pm_runtime_use_autosuspend(dev); |
| pm_runtime_enable(dev); |
| |
| err = iommu_device_sysfs_add(&iommu->iommu, dev, NULL, "%s", |
| dev_name(dev)); |
| if (err) |
| goto err_runtime_disable; |
| |
| err = iommu_device_register(&iommu->iommu, &vsi_iommu_ops, dev); |
| if (err) |
| goto err_remove_sysfs; |
| |
| return 0; |
| |
| err_remove_sysfs: |
| iommu_device_sysfs_remove(&iommu->iommu); |
| err_runtime_disable: |
| pm_runtime_disable(dev); |
| err_unprepare_clocks: |
| clk_bulk_unprepare(iommu->num_clocks, iommu->clocks); |
| return err; |
| } |
| |
| static void vsi_iommu_shutdown(struct platform_device *pdev) |
| { |
| struct vsi_iommu *iommu = platform_get_drvdata(pdev); |
| |
| disable_irq(iommu->irq); |
| pm_runtime_force_suspend(&pdev->dev); |
| } |
| |
| static int __maybe_unused vsi_iommu_suspend(struct device *dev) |
| { |
| struct vsi_iommu *iommu = dev_get_drvdata(dev); |
| |
| vsi_iommu_disable(iommu); |
| |
| clk_bulk_disable(iommu->num_clocks, iommu->clocks); |
| |
| return 0; |
| } |
| |
| static int __maybe_unused vsi_iommu_resume(struct device *dev) |
| { |
| struct vsi_iommu *iommu = dev_get_drvdata(dev); |
| unsigned long flags; |
| int ret; |
| |
| ret = clk_bulk_enable(iommu->num_clocks, iommu->clocks); |
| if (ret) |
| return ret; |
| |
| if (iommu->domain) { |
| struct vsi_iommu_domain *vsi_domain = to_vsi_domain(iommu->domain); |
| |
| spin_lock_irqsave(&vsi_domain->lock, flags); |
| spin_lock(&iommu->lock); |
| vsi_iommu_enable(iommu, iommu->domain); |
| spin_unlock(&iommu->lock); |
| spin_unlock_irqrestore(&vsi_domain->lock, flags); |
| } |
| |
| return 0; |
| } |
| |
| static DEFINE_RUNTIME_DEV_PM_OPS(vsi_iommu_pm_ops, |
| vsi_iommu_suspend, vsi_iommu_resume, |
| NULL); |
| |
| static struct platform_driver rockchip_vsi_iommu_driver = { |
| .probe = vsi_iommu_probe, |
| .shutdown = vsi_iommu_shutdown, |
| .driver = { |
| .name = "vsi_iommu", |
| .of_match_table = vsi_iommu_dt_ids, |
| .pm = pm_sleep_ptr(&vsi_iommu_pm_ops), |
| .suppress_bind_attrs = true, |
| }, |
| }; |
| module_platform_driver(rockchip_vsi_iommu_driver); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Benjamin Gaignard <benjamin.gaignard@collabora.com>"); |
| MODULE_DESCRIPTION("Verisilicon IOMMU driver"); |