162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/* Copyright (C) 2014-2018 Broadcom */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci/**
562306a36Sopenharmony_ci * DOC: Interrupt management for the V3D engine
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * When we take a bin, render, TFU done, or CSD done interrupt, we
862306a36Sopenharmony_ci * need to signal the fence for that job so that the scheduler can
962306a36Sopenharmony_ci * queue up the next one and unblock any waiters.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * When we take the binner out of memory interrupt, we need to
1262306a36Sopenharmony_ci * allocate some new memory and pass it to the binner so that the
1362306a36Sopenharmony_ci * current job can make progress.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "v3d_drv.h"
1962306a36Sopenharmony_ci#include "v3d_regs.h"
2062306a36Sopenharmony_ci#include "v3d_trace.h"
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#define V3D_CORE_IRQS ((u32)(V3D_INT_OUTOMEM |	\
2362306a36Sopenharmony_ci			     V3D_INT_FLDONE |	\
2462306a36Sopenharmony_ci			     V3D_INT_FRDONE |	\
2562306a36Sopenharmony_ci			     V3D_INT_CSDDONE |	\
2662306a36Sopenharmony_ci			     V3D_INT_GMPV))
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define V3D_HUB_IRQS ((u32)(V3D_HUB_INT_MMU_WRV |	\
2962306a36Sopenharmony_ci			    V3D_HUB_INT_MMU_PTI |	\
3062306a36Sopenharmony_ci			    V3D_HUB_INT_MMU_CAP |	\
3162306a36Sopenharmony_ci			    V3D_HUB_INT_TFUC))
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic irqreturn_t
3462306a36Sopenharmony_civ3d_hub_irq(int irq, void *arg);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic void
3762306a36Sopenharmony_civ3d_overflow_mem_work(struct work_struct *work)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	struct v3d_dev *v3d =
4062306a36Sopenharmony_ci		container_of(work, struct v3d_dev, overflow_mem_work);
4162306a36Sopenharmony_ci	struct drm_device *dev = &v3d->drm;
4262306a36Sopenharmony_ci	struct v3d_bo *bo = v3d_bo_create(dev, NULL /* XXX: GMP */, 256 * 1024);
4362306a36Sopenharmony_ci	struct drm_gem_object *obj;
4462306a36Sopenharmony_ci	unsigned long irqflags;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	if (IS_ERR(bo)) {
4762306a36Sopenharmony_ci		DRM_ERROR("Couldn't allocate binner overflow mem\n");
4862306a36Sopenharmony_ci		return;
4962306a36Sopenharmony_ci	}
5062306a36Sopenharmony_ci	obj = &bo->base.base;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	/* We lost a race, and our work task came in after the bin job
5362306a36Sopenharmony_ci	 * completed and exited.  This can happen because the HW
5462306a36Sopenharmony_ci	 * signals OOM before it's fully OOM, so the binner might just
5562306a36Sopenharmony_ci	 * barely complete.
5662306a36Sopenharmony_ci	 *
5762306a36Sopenharmony_ci	 * If we lose the race and our work task comes in after a new
5862306a36Sopenharmony_ci	 * bin job got scheduled, that's fine.  We'll just give them
5962306a36Sopenharmony_ci	 * some binner pool anyway.
6062306a36Sopenharmony_ci	 */
6162306a36Sopenharmony_ci	spin_lock_irqsave(&v3d->job_lock, irqflags);
6262306a36Sopenharmony_ci	if (!v3d->bin_job) {
6362306a36Sopenharmony_ci		spin_unlock_irqrestore(&v3d->job_lock, irqflags);
6462306a36Sopenharmony_ci		goto out;
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	drm_gem_object_get(obj);
6862306a36Sopenharmony_ci	list_add_tail(&bo->unref_head, &v3d->bin_job->render->unref_list);
6962306a36Sopenharmony_ci	spin_unlock_irqrestore(&v3d->job_lock, irqflags);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	V3D_CORE_WRITE(0, V3D_PTB_BPOA, bo->node.start << PAGE_SHIFT);
7262306a36Sopenharmony_ci	V3D_CORE_WRITE(0, V3D_PTB_BPOS, obj->size);
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ciout:
7562306a36Sopenharmony_ci	drm_gem_object_put(obj);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic irqreturn_t
7962306a36Sopenharmony_civ3d_irq(int irq, void *arg)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	struct v3d_dev *v3d = arg;
8262306a36Sopenharmony_ci	u32 intsts;
8362306a36Sopenharmony_ci	irqreturn_t status = IRQ_NONE;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	intsts = V3D_CORE_READ(0, V3D_CTL_INT_STS);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	/* Acknowledge the interrupts we're handling here. */
8862306a36Sopenharmony_ci	V3D_CORE_WRITE(0, V3D_CTL_INT_CLR, intsts);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (intsts & V3D_INT_OUTOMEM) {
9162306a36Sopenharmony_ci		/* Note that the OOM status is edge signaled, so the
9262306a36Sopenharmony_ci		 * interrupt won't happen again until the we actually
9362306a36Sopenharmony_ci		 * add more memory.  Also, as of V3D 4.1, FLDONE won't
9462306a36Sopenharmony_ci		 * be reported until any OOM state has been cleared.
9562306a36Sopenharmony_ci		 */
9662306a36Sopenharmony_ci		schedule_work(&v3d->overflow_mem_work);
9762306a36Sopenharmony_ci		status = IRQ_HANDLED;
9862306a36Sopenharmony_ci	}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	if (intsts & V3D_INT_FLDONE) {
10162306a36Sopenharmony_ci		struct v3d_fence *fence =
10262306a36Sopenharmony_ci			to_v3d_fence(v3d->bin_job->base.irq_fence);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci		trace_v3d_bcl_irq(&v3d->drm, fence->seqno);
10562306a36Sopenharmony_ci		dma_fence_signal(&fence->base);
10662306a36Sopenharmony_ci		status = IRQ_HANDLED;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (intsts & V3D_INT_FRDONE) {
11062306a36Sopenharmony_ci		struct v3d_fence *fence =
11162306a36Sopenharmony_ci			to_v3d_fence(v3d->render_job->base.irq_fence);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		trace_v3d_rcl_irq(&v3d->drm, fence->seqno);
11462306a36Sopenharmony_ci		dma_fence_signal(&fence->base);
11562306a36Sopenharmony_ci		status = IRQ_HANDLED;
11662306a36Sopenharmony_ci	}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	if (intsts & V3D_INT_CSDDONE) {
11962306a36Sopenharmony_ci		struct v3d_fence *fence =
12062306a36Sopenharmony_ci			to_v3d_fence(v3d->csd_job->base.irq_fence);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		trace_v3d_csd_irq(&v3d->drm, fence->seqno);
12362306a36Sopenharmony_ci		dma_fence_signal(&fence->base);
12462306a36Sopenharmony_ci		status = IRQ_HANDLED;
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	/* We shouldn't be triggering these if we have GMP in
12862306a36Sopenharmony_ci	 * always-allowed mode.
12962306a36Sopenharmony_ci	 */
13062306a36Sopenharmony_ci	if (intsts & V3D_INT_GMPV)
13162306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "GMP violation\n");
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	/* V3D 4.2 wires the hub and core IRQs together, so if we &
13462306a36Sopenharmony_ci	 * didn't see the common one then check hub for MMU IRQs.
13562306a36Sopenharmony_ci	 */
13662306a36Sopenharmony_ci	if (v3d->single_irq_line && status == IRQ_NONE)
13762306a36Sopenharmony_ci		return v3d_hub_irq(irq, arg);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return status;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic irqreturn_t
14362306a36Sopenharmony_civ3d_hub_irq(int irq, void *arg)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct v3d_dev *v3d = arg;
14662306a36Sopenharmony_ci	u32 intsts;
14762306a36Sopenharmony_ci	irqreturn_t status = IRQ_NONE;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	intsts = V3D_READ(V3D_HUB_INT_STS);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/* Acknowledge the interrupts we're handling here. */
15262306a36Sopenharmony_ci	V3D_WRITE(V3D_HUB_INT_CLR, intsts);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (intsts & V3D_HUB_INT_TFUC) {
15562306a36Sopenharmony_ci		struct v3d_fence *fence =
15662306a36Sopenharmony_ci			to_v3d_fence(v3d->tfu_job->base.irq_fence);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		trace_v3d_tfu_irq(&v3d->drm, fence->seqno);
15962306a36Sopenharmony_ci		dma_fence_signal(&fence->base);
16062306a36Sopenharmony_ci		status = IRQ_HANDLED;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	if (intsts & (V3D_HUB_INT_MMU_WRV |
16462306a36Sopenharmony_ci		      V3D_HUB_INT_MMU_PTI |
16562306a36Sopenharmony_ci		      V3D_HUB_INT_MMU_CAP)) {
16662306a36Sopenharmony_ci		u32 axi_id = V3D_READ(V3D_MMU_VIO_ID);
16762306a36Sopenharmony_ci		u64 vio_addr = ((u64)V3D_READ(V3D_MMU_VIO_ADDR) <<
16862306a36Sopenharmony_ci				(v3d->va_width - 32));
16962306a36Sopenharmony_ci		static const char *const v3d41_axi_ids[] = {
17062306a36Sopenharmony_ci			"L2T",
17162306a36Sopenharmony_ci			"PTB",
17262306a36Sopenharmony_ci			"PSE",
17362306a36Sopenharmony_ci			"TLB",
17462306a36Sopenharmony_ci			"CLE",
17562306a36Sopenharmony_ci			"TFU",
17662306a36Sopenharmony_ci			"MMU",
17762306a36Sopenharmony_ci			"GMP",
17862306a36Sopenharmony_ci		};
17962306a36Sopenharmony_ci		const char *client = "?";
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci		V3D_WRITE(V3D_MMU_CTL, V3D_READ(V3D_MMU_CTL));
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci		if (v3d->ver >= 41) {
18462306a36Sopenharmony_ci			axi_id = axi_id >> 5;
18562306a36Sopenharmony_ci			if (axi_id < ARRAY_SIZE(v3d41_axi_ids))
18662306a36Sopenharmony_ci				client = v3d41_axi_ids[axi_id];
18762306a36Sopenharmony_ci		}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "MMU error from client %s (%d) at 0x%llx%s%s%s\n",
19062306a36Sopenharmony_ci			client, axi_id, (long long)vio_addr,
19162306a36Sopenharmony_ci			((intsts & V3D_HUB_INT_MMU_WRV) ?
19262306a36Sopenharmony_ci			 ", write violation" : ""),
19362306a36Sopenharmony_ci			((intsts & V3D_HUB_INT_MMU_PTI) ?
19462306a36Sopenharmony_ci			 ", pte invalid" : ""),
19562306a36Sopenharmony_ci			((intsts & V3D_HUB_INT_MMU_CAP) ?
19662306a36Sopenharmony_ci			 ", cap exceeded" : ""));
19762306a36Sopenharmony_ci		status = IRQ_HANDLED;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return status;
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ciint
20462306a36Sopenharmony_civ3d_irq_init(struct v3d_dev *v3d)
20562306a36Sopenharmony_ci{
20662306a36Sopenharmony_ci	int irq1, ret, core;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	INIT_WORK(&v3d->overflow_mem_work, v3d_overflow_mem_work);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	/* Clear any pending interrupts someone might have left around
21162306a36Sopenharmony_ci	 * for us.
21262306a36Sopenharmony_ci	 */
21362306a36Sopenharmony_ci	for (core = 0; core < v3d->cores; core++)
21462306a36Sopenharmony_ci		V3D_CORE_WRITE(core, V3D_CTL_INT_CLR, V3D_CORE_IRQS);
21562306a36Sopenharmony_ci	V3D_WRITE(V3D_HUB_INT_CLR, V3D_HUB_IRQS);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	irq1 = platform_get_irq_optional(v3d_to_pdev(v3d), 1);
21862306a36Sopenharmony_ci	if (irq1 == -EPROBE_DEFER)
21962306a36Sopenharmony_ci		return irq1;
22062306a36Sopenharmony_ci	if (irq1 > 0) {
22162306a36Sopenharmony_ci		ret = devm_request_irq(v3d->drm.dev, irq1,
22262306a36Sopenharmony_ci				       v3d_irq, IRQF_SHARED,
22362306a36Sopenharmony_ci				       "v3d_core0", v3d);
22462306a36Sopenharmony_ci		if (ret)
22562306a36Sopenharmony_ci			goto fail;
22662306a36Sopenharmony_ci		ret = devm_request_irq(v3d->drm.dev,
22762306a36Sopenharmony_ci				       platform_get_irq(v3d_to_pdev(v3d), 0),
22862306a36Sopenharmony_ci				       v3d_hub_irq, IRQF_SHARED,
22962306a36Sopenharmony_ci				       "v3d_hub", v3d);
23062306a36Sopenharmony_ci		if (ret)
23162306a36Sopenharmony_ci			goto fail;
23262306a36Sopenharmony_ci	} else {
23362306a36Sopenharmony_ci		v3d->single_irq_line = true;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci		ret = devm_request_irq(v3d->drm.dev,
23662306a36Sopenharmony_ci				       platform_get_irq(v3d_to_pdev(v3d), 0),
23762306a36Sopenharmony_ci				       v3d_irq, IRQF_SHARED,
23862306a36Sopenharmony_ci				       "v3d", v3d);
23962306a36Sopenharmony_ci		if (ret)
24062306a36Sopenharmony_ci			goto fail;
24162306a36Sopenharmony_ci	}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	v3d_irq_enable(v3d);
24462306a36Sopenharmony_ci	return 0;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cifail:
24762306a36Sopenharmony_ci	if (ret != -EPROBE_DEFER)
24862306a36Sopenharmony_ci		dev_err(v3d->drm.dev, "IRQ setup failed: %d\n", ret);
24962306a36Sopenharmony_ci	return ret;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_civoid
25362306a36Sopenharmony_civ3d_irq_enable(struct v3d_dev *v3d)
25462306a36Sopenharmony_ci{
25562306a36Sopenharmony_ci	int core;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	/* Enable our set of interrupts, masking out any others. */
25862306a36Sopenharmony_ci	for (core = 0; core < v3d->cores; core++) {
25962306a36Sopenharmony_ci		V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_SET, ~V3D_CORE_IRQS);
26062306a36Sopenharmony_ci		V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_CLR, V3D_CORE_IRQS);
26162306a36Sopenharmony_ci	}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	V3D_WRITE(V3D_HUB_INT_MSK_SET, ~V3D_HUB_IRQS);
26462306a36Sopenharmony_ci	V3D_WRITE(V3D_HUB_INT_MSK_CLR, V3D_HUB_IRQS);
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_civoid
26862306a36Sopenharmony_civ3d_irq_disable(struct v3d_dev *v3d)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	int core;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	/* Disable all interrupts. */
27362306a36Sopenharmony_ci	for (core = 0; core < v3d->cores; core++)
27462306a36Sopenharmony_ci		V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_SET, ~0);
27562306a36Sopenharmony_ci	V3D_WRITE(V3D_HUB_INT_MSK_SET, ~0);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	/* Clear any pending interrupts we might have left. */
27862306a36Sopenharmony_ci	for (core = 0; core < v3d->cores; core++)
27962306a36Sopenharmony_ci		V3D_CORE_WRITE(core, V3D_CTL_INT_CLR, V3D_CORE_IRQS);
28062306a36Sopenharmony_ci	V3D_WRITE(V3D_HUB_INT_CLR, V3D_HUB_IRQS);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	cancel_work_sync(&v3d->overflow_mem_work);
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci/** Reinitializes interrupt registers when a GPU reset is performed. */
28662306a36Sopenharmony_civoid v3d_irq_reset(struct v3d_dev *v3d)
28762306a36Sopenharmony_ci{
28862306a36Sopenharmony_ci	v3d_irq_enable(v3d);
28962306a36Sopenharmony_ci}
290