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