162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/* Copyright (C) 2017-2018 Broadcom */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci/**
562306a36Sopenharmony_ci * DOC: Broadcom V3D MMU
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * The V3D 3.x hardware (compared to VC4) now includes an MMU.  It has
862306a36Sopenharmony_ci * a single level of page tables for the V3D's 4GB address space to
962306a36Sopenharmony_ci * map to AXI bus addresses, thus it could need up to 4MB of
1062306a36Sopenharmony_ci * physically contiguous memory to store the PTEs.
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * Because the 4MB of contiguous memory for page tables is precious,
1362306a36Sopenharmony_ci * and switching between them is expensive, we load all BOs into the
1462306a36Sopenharmony_ci * same 4GB address space.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * To protect clients from each other, we should use the GMP to
1762306a36Sopenharmony_ci * quickly mask out (at 128kb granularity) what pages are available to
1862306a36Sopenharmony_ci * each client.  This is not yet implemented.
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include "v3d_drv.h"
2262306a36Sopenharmony_ci#include "v3d_regs.h"
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define V3D_MMU_PAGE_SHIFT 12
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* Note: All PTEs for the 1MB superpage must be filled with the
2762306a36Sopenharmony_ci * superpage bit set.
2862306a36Sopenharmony_ci */
2962306a36Sopenharmony_ci#define V3D_PTE_SUPERPAGE BIT(31)
3062306a36Sopenharmony_ci#define V3D_PTE_WRITEABLE BIT(29)
3162306a36Sopenharmony_ci#define V3D_PTE_VALID BIT(28)
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic int v3d_mmu_flush_all(struct v3d_dev *v3d)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	int ret;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	/* Make sure that another flush isn't already running when we
3862306a36Sopenharmony_ci	 * start this one.
3962306a36Sopenharmony_ci	 */
4062306a36Sopenharmony_ci	ret = wait_for(!(V3D_READ(V3D_MMU_CTL) &
4162306a36Sopenharmony_ci			 V3D_MMU_CTL_TLB_CLEARING), 100);
4262306a36Sopenharmony_ci	if (ret)
4362306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "TLB clear wait idle pre-wait failed\n");
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	V3D_WRITE(V3D_MMU_CTL, V3D_READ(V3D_MMU_CTL) |
4662306a36Sopenharmony_ci		  V3D_MMU_CTL_TLB_CLEAR);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	V3D_WRITE(V3D_MMUC_CONTROL,
4962306a36Sopenharmony_ci		  V3D_MMUC_CONTROL_FLUSH |
5062306a36Sopenharmony_ci		  V3D_MMUC_CONTROL_ENABLE);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	ret = wait_for(!(V3D_READ(V3D_MMU_CTL) &
5362306a36Sopenharmony_ci			 V3D_MMU_CTL_TLB_CLEARING), 100);
5462306a36Sopenharmony_ci	if (ret) {
5562306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "TLB clear wait idle failed\n");
5662306a36Sopenharmony_ci		return ret;
5762306a36Sopenharmony_ci	}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci	ret = wait_for(!(V3D_READ(V3D_MMUC_CONTROL) &
6062306a36Sopenharmony_ci			 V3D_MMUC_CONTROL_FLUSHING), 100);
6162306a36Sopenharmony_ci	if (ret)
6262306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "MMUC flush wait idle failed\n");
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return ret;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ciint v3d_mmu_set_page_table(struct v3d_dev *v3d)
6862306a36Sopenharmony_ci{
6962306a36Sopenharmony_ci	V3D_WRITE(V3D_MMU_PT_PA_BASE, v3d->pt_paddr >> V3D_MMU_PAGE_SHIFT);
7062306a36Sopenharmony_ci	V3D_WRITE(V3D_MMU_CTL,
7162306a36Sopenharmony_ci		  V3D_MMU_CTL_ENABLE |
7262306a36Sopenharmony_ci		  V3D_MMU_CTL_PT_INVALID_ENABLE |
7362306a36Sopenharmony_ci		  V3D_MMU_CTL_PT_INVALID_ABORT |
7462306a36Sopenharmony_ci		  V3D_MMU_CTL_PT_INVALID_INT |
7562306a36Sopenharmony_ci		  V3D_MMU_CTL_WRITE_VIOLATION_ABORT |
7662306a36Sopenharmony_ci		  V3D_MMU_CTL_WRITE_VIOLATION_INT |
7762306a36Sopenharmony_ci		  V3D_MMU_CTL_CAP_EXCEEDED_ABORT |
7862306a36Sopenharmony_ci		  V3D_MMU_CTL_CAP_EXCEEDED_INT);
7962306a36Sopenharmony_ci	V3D_WRITE(V3D_MMU_ILLEGAL_ADDR,
8062306a36Sopenharmony_ci		  (v3d->mmu_scratch_paddr >> V3D_MMU_PAGE_SHIFT) |
8162306a36Sopenharmony_ci		  V3D_MMU_ILLEGAL_ADDR_ENABLE);
8262306a36Sopenharmony_ci	V3D_WRITE(V3D_MMUC_CONTROL, V3D_MMUC_CONTROL_ENABLE);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	return v3d_mmu_flush_all(v3d);
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_civoid v3d_mmu_insert_ptes(struct v3d_bo *bo)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	struct drm_gem_shmem_object *shmem_obj = &bo->base;
9062306a36Sopenharmony_ci	struct v3d_dev *v3d = to_v3d_dev(shmem_obj->base.dev);
9162306a36Sopenharmony_ci	u32 page = bo->node.start;
9262306a36Sopenharmony_ci	u32 page_prot = V3D_PTE_WRITEABLE | V3D_PTE_VALID;
9362306a36Sopenharmony_ci	struct sg_dma_page_iter dma_iter;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	for_each_sgtable_dma_page(shmem_obj->sgt, &dma_iter, 0) {
9662306a36Sopenharmony_ci		dma_addr_t dma_addr = sg_page_iter_dma_address(&dma_iter);
9762306a36Sopenharmony_ci		u32 page_address = dma_addr >> V3D_MMU_PAGE_SHIFT;
9862306a36Sopenharmony_ci		u32 pte = page_prot | page_address;
9962306a36Sopenharmony_ci		u32 i;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci		BUG_ON(page_address + (PAGE_SIZE >> V3D_MMU_PAGE_SHIFT) >=
10262306a36Sopenharmony_ci		       BIT(24));
10362306a36Sopenharmony_ci		for (i = 0; i < PAGE_SIZE >> V3D_MMU_PAGE_SHIFT; i++)
10462306a36Sopenharmony_ci			v3d->pt[page++] = pte + i;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	WARN_ON_ONCE(page - bo->node.start !=
10862306a36Sopenharmony_ci		     shmem_obj->base.size >> V3D_MMU_PAGE_SHIFT);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (v3d_mmu_flush_all(v3d))
11162306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "MMU flush timeout\n");
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_civoid v3d_mmu_remove_ptes(struct v3d_bo *bo)
11562306a36Sopenharmony_ci{
11662306a36Sopenharmony_ci	struct v3d_dev *v3d = to_v3d_dev(bo->base.base.dev);
11762306a36Sopenharmony_ci	u32 npages = bo->base.base.size >> V3D_MMU_PAGE_SHIFT;
11862306a36Sopenharmony_ci	u32 page;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	for (page = bo->node.start; page < bo->node.start + npages; page++)
12162306a36Sopenharmony_ci		v3d->pt[page] = 0;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	if (v3d_mmu_flush_all(v3d))
12462306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "MMU flush timeout\n");
12562306a36Sopenharmony_ci}
126