xref: /OK3568_Linux_fs/kernel/drivers/gpu/drm/v3d/v3d_irq.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun /* Copyright (C) 2014-2018 Broadcom */
3*4882a593Smuzhiyun 
4*4882a593Smuzhiyun /**
5*4882a593Smuzhiyun  * DOC: Interrupt management for the V3D engine
6*4882a593Smuzhiyun  *
7*4882a593Smuzhiyun  * When we take a bin, render, TFU done, or CSD done interrupt, we
8*4882a593Smuzhiyun  * need to signal the fence for that job so that the scheduler can
9*4882a593Smuzhiyun  * queue up the next one and unblock any waiters.
10*4882a593Smuzhiyun  *
11*4882a593Smuzhiyun  * When we take the binner out of memory interrupt, we need to
12*4882a593Smuzhiyun  * allocate some new memory and pass it to the binner so that the
13*4882a593Smuzhiyun  * current job can make progress.
14*4882a593Smuzhiyun  */
15*4882a593Smuzhiyun 
16*4882a593Smuzhiyun #include <linux/platform_device.h>
17*4882a593Smuzhiyun 
18*4882a593Smuzhiyun #include "v3d_drv.h"
19*4882a593Smuzhiyun #include "v3d_regs.h"
20*4882a593Smuzhiyun #include "v3d_trace.h"
21*4882a593Smuzhiyun 
22*4882a593Smuzhiyun #define V3D_CORE_IRQS ((u32)(V3D_INT_OUTOMEM |	\
23*4882a593Smuzhiyun 			     V3D_INT_FLDONE |	\
24*4882a593Smuzhiyun 			     V3D_INT_FRDONE |	\
25*4882a593Smuzhiyun 			     V3D_INT_CSDDONE |	\
26*4882a593Smuzhiyun 			     V3D_INT_GMPV))
27*4882a593Smuzhiyun 
28*4882a593Smuzhiyun #define V3D_HUB_IRQS ((u32)(V3D_HUB_INT_MMU_WRV |	\
29*4882a593Smuzhiyun 			    V3D_HUB_INT_MMU_PTI |	\
30*4882a593Smuzhiyun 			    V3D_HUB_INT_MMU_CAP |	\
31*4882a593Smuzhiyun 			    V3D_HUB_INT_TFUC))
32*4882a593Smuzhiyun 
33*4882a593Smuzhiyun static irqreturn_t
34*4882a593Smuzhiyun v3d_hub_irq(int irq, void *arg);
35*4882a593Smuzhiyun 
36*4882a593Smuzhiyun static void
v3d_overflow_mem_work(struct work_struct * work)37*4882a593Smuzhiyun v3d_overflow_mem_work(struct work_struct *work)
38*4882a593Smuzhiyun {
39*4882a593Smuzhiyun 	struct v3d_dev *v3d =
40*4882a593Smuzhiyun 		container_of(work, struct v3d_dev, overflow_mem_work);
41*4882a593Smuzhiyun 	struct drm_device *dev = &v3d->drm;
42*4882a593Smuzhiyun 	struct v3d_bo *bo = v3d_bo_create(dev, NULL /* XXX: GMP */, 256 * 1024);
43*4882a593Smuzhiyun 	struct drm_gem_object *obj;
44*4882a593Smuzhiyun 	unsigned long irqflags;
45*4882a593Smuzhiyun 
46*4882a593Smuzhiyun 	if (IS_ERR(bo)) {
47*4882a593Smuzhiyun 		DRM_ERROR("Couldn't allocate binner overflow mem\n");
48*4882a593Smuzhiyun 		return;
49*4882a593Smuzhiyun 	}
50*4882a593Smuzhiyun 	obj = &bo->base.base;
51*4882a593Smuzhiyun 
52*4882a593Smuzhiyun 	/* We lost a race, and our work task came in after the bin job
53*4882a593Smuzhiyun 	 * completed and exited.  This can happen because the HW
54*4882a593Smuzhiyun 	 * signals OOM before it's fully OOM, so the binner might just
55*4882a593Smuzhiyun 	 * barely complete.
56*4882a593Smuzhiyun 	 *
57*4882a593Smuzhiyun 	 * If we lose the race and our work task comes in after a new
58*4882a593Smuzhiyun 	 * bin job got scheduled, that's fine.  We'll just give them
59*4882a593Smuzhiyun 	 * some binner pool anyway.
60*4882a593Smuzhiyun 	 */
61*4882a593Smuzhiyun 	spin_lock_irqsave(&v3d->job_lock, irqflags);
62*4882a593Smuzhiyun 	if (!v3d->bin_job) {
63*4882a593Smuzhiyun 		spin_unlock_irqrestore(&v3d->job_lock, irqflags);
64*4882a593Smuzhiyun 		goto out;
65*4882a593Smuzhiyun 	}
66*4882a593Smuzhiyun 
67*4882a593Smuzhiyun 	drm_gem_object_get(obj);
68*4882a593Smuzhiyun 	list_add_tail(&bo->unref_head, &v3d->bin_job->render->unref_list);
69*4882a593Smuzhiyun 	spin_unlock_irqrestore(&v3d->job_lock, irqflags);
70*4882a593Smuzhiyun 
71*4882a593Smuzhiyun 	V3D_CORE_WRITE(0, V3D_PTB_BPOA, bo->node.start << PAGE_SHIFT);
72*4882a593Smuzhiyun 	V3D_CORE_WRITE(0, V3D_PTB_BPOS, obj->size);
73*4882a593Smuzhiyun 
74*4882a593Smuzhiyun out:
75*4882a593Smuzhiyun 	drm_gem_object_put(obj);
76*4882a593Smuzhiyun }
77*4882a593Smuzhiyun 
78*4882a593Smuzhiyun static irqreturn_t
v3d_irq(int irq,void * arg)79*4882a593Smuzhiyun v3d_irq(int irq, void *arg)
80*4882a593Smuzhiyun {
81*4882a593Smuzhiyun 	struct v3d_dev *v3d = arg;
82*4882a593Smuzhiyun 	u32 intsts;
83*4882a593Smuzhiyun 	irqreturn_t status = IRQ_NONE;
84*4882a593Smuzhiyun 
85*4882a593Smuzhiyun 	intsts = V3D_CORE_READ(0, V3D_CTL_INT_STS);
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	/* Acknowledge the interrupts we're handling here. */
88*4882a593Smuzhiyun 	V3D_CORE_WRITE(0, V3D_CTL_INT_CLR, intsts);
89*4882a593Smuzhiyun 
90*4882a593Smuzhiyun 	if (intsts & V3D_INT_OUTOMEM) {
91*4882a593Smuzhiyun 		/* Note that the OOM status is edge signaled, so the
92*4882a593Smuzhiyun 		 * interrupt won't happen again until the we actually
93*4882a593Smuzhiyun 		 * add more memory.  Also, as of V3D 4.1, FLDONE won't
94*4882a593Smuzhiyun 		 * be reported until any OOM state has been cleared.
95*4882a593Smuzhiyun 		 */
96*4882a593Smuzhiyun 		schedule_work(&v3d->overflow_mem_work);
97*4882a593Smuzhiyun 		status = IRQ_HANDLED;
98*4882a593Smuzhiyun 	}
99*4882a593Smuzhiyun 
100*4882a593Smuzhiyun 	if (intsts & V3D_INT_FLDONE) {
101*4882a593Smuzhiyun 		struct v3d_fence *fence =
102*4882a593Smuzhiyun 			to_v3d_fence(v3d->bin_job->base.irq_fence);
103*4882a593Smuzhiyun 
104*4882a593Smuzhiyun 		trace_v3d_bcl_irq(&v3d->drm, fence->seqno);
105*4882a593Smuzhiyun 		dma_fence_signal(&fence->base);
106*4882a593Smuzhiyun 		status = IRQ_HANDLED;
107*4882a593Smuzhiyun 	}
108*4882a593Smuzhiyun 
109*4882a593Smuzhiyun 	if (intsts & V3D_INT_FRDONE) {
110*4882a593Smuzhiyun 		struct v3d_fence *fence =
111*4882a593Smuzhiyun 			to_v3d_fence(v3d->render_job->base.irq_fence);
112*4882a593Smuzhiyun 
113*4882a593Smuzhiyun 		trace_v3d_rcl_irq(&v3d->drm, fence->seqno);
114*4882a593Smuzhiyun 		dma_fence_signal(&fence->base);
115*4882a593Smuzhiyun 		status = IRQ_HANDLED;
116*4882a593Smuzhiyun 	}
117*4882a593Smuzhiyun 
118*4882a593Smuzhiyun 	if (intsts & V3D_INT_CSDDONE) {
119*4882a593Smuzhiyun 		struct v3d_fence *fence =
120*4882a593Smuzhiyun 			to_v3d_fence(v3d->csd_job->base.irq_fence);
121*4882a593Smuzhiyun 
122*4882a593Smuzhiyun 		trace_v3d_csd_irq(&v3d->drm, fence->seqno);
123*4882a593Smuzhiyun 		dma_fence_signal(&fence->base);
124*4882a593Smuzhiyun 		status = IRQ_HANDLED;
125*4882a593Smuzhiyun 	}
126*4882a593Smuzhiyun 
127*4882a593Smuzhiyun 	/* We shouldn't be triggering these if we have GMP in
128*4882a593Smuzhiyun 	 * always-allowed mode.
129*4882a593Smuzhiyun 	 */
130*4882a593Smuzhiyun 	if (intsts & V3D_INT_GMPV)
131*4882a593Smuzhiyun 		dev_err(v3d->drm.dev, "GMP violation\n");
132*4882a593Smuzhiyun 
133*4882a593Smuzhiyun 	/* V3D 4.2 wires the hub and core IRQs together, so if we &
134*4882a593Smuzhiyun 	 * didn't see the common one then check hub for MMU IRQs.
135*4882a593Smuzhiyun 	 */
136*4882a593Smuzhiyun 	if (v3d->single_irq_line && status == IRQ_NONE)
137*4882a593Smuzhiyun 		return v3d_hub_irq(irq, arg);
138*4882a593Smuzhiyun 
139*4882a593Smuzhiyun 	return status;
140*4882a593Smuzhiyun }
141*4882a593Smuzhiyun 
142*4882a593Smuzhiyun static irqreturn_t
v3d_hub_irq(int irq,void * arg)143*4882a593Smuzhiyun v3d_hub_irq(int irq, void *arg)
144*4882a593Smuzhiyun {
145*4882a593Smuzhiyun 	struct v3d_dev *v3d = arg;
146*4882a593Smuzhiyun 	u32 intsts;
147*4882a593Smuzhiyun 	irqreturn_t status = IRQ_NONE;
148*4882a593Smuzhiyun 
149*4882a593Smuzhiyun 	intsts = V3D_READ(V3D_HUB_INT_STS);
150*4882a593Smuzhiyun 
151*4882a593Smuzhiyun 	/* Acknowledge the interrupts we're handling here. */
152*4882a593Smuzhiyun 	V3D_WRITE(V3D_HUB_INT_CLR, intsts);
153*4882a593Smuzhiyun 
154*4882a593Smuzhiyun 	if (intsts & V3D_HUB_INT_TFUC) {
155*4882a593Smuzhiyun 		struct v3d_fence *fence =
156*4882a593Smuzhiyun 			to_v3d_fence(v3d->tfu_job->base.irq_fence);
157*4882a593Smuzhiyun 
158*4882a593Smuzhiyun 		trace_v3d_tfu_irq(&v3d->drm, fence->seqno);
159*4882a593Smuzhiyun 		dma_fence_signal(&fence->base);
160*4882a593Smuzhiyun 		status = IRQ_HANDLED;
161*4882a593Smuzhiyun 	}
162*4882a593Smuzhiyun 
163*4882a593Smuzhiyun 	if (intsts & (V3D_HUB_INT_MMU_WRV |
164*4882a593Smuzhiyun 		      V3D_HUB_INT_MMU_PTI |
165*4882a593Smuzhiyun 		      V3D_HUB_INT_MMU_CAP)) {
166*4882a593Smuzhiyun 		u32 axi_id = V3D_READ(V3D_MMU_VIO_ID);
167*4882a593Smuzhiyun 		u64 vio_addr = ((u64)V3D_READ(V3D_MMU_VIO_ADDR) <<
168*4882a593Smuzhiyun 				(v3d->va_width - 32));
169*4882a593Smuzhiyun 		static const char *const v3d41_axi_ids[] = {
170*4882a593Smuzhiyun 			"L2T",
171*4882a593Smuzhiyun 			"PTB",
172*4882a593Smuzhiyun 			"PSE",
173*4882a593Smuzhiyun 			"TLB",
174*4882a593Smuzhiyun 			"CLE",
175*4882a593Smuzhiyun 			"TFU",
176*4882a593Smuzhiyun 			"MMU",
177*4882a593Smuzhiyun 			"GMP",
178*4882a593Smuzhiyun 		};
179*4882a593Smuzhiyun 		const char *client = "?";
180*4882a593Smuzhiyun 
181*4882a593Smuzhiyun 		V3D_WRITE(V3D_MMU_CTL,
182*4882a593Smuzhiyun 			  V3D_READ(V3D_MMU_CTL) & (V3D_MMU_CTL_CAP_EXCEEDED |
183*4882a593Smuzhiyun 						   V3D_MMU_CTL_PT_INVALID |
184*4882a593Smuzhiyun 						   V3D_MMU_CTL_WRITE_VIOLATION));
185*4882a593Smuzhiyun 
186*4882a593Smuzhiyun 		if (v3d->ver >= 41) {
187*4882a593Smuzhiyun 			axi_id = axi_id >> 5;
188*4882a593Smuzhiyun 			if (axi_id < ARRAY_SIZE(v3d41_axi_ids))
189*4882a593Smuzhiyun 				client = v3d41_axi_ids[axi_id];
190*4882a593Smuzhiyun 		}
191*4882a593Smuzhiyun 
192*4882a593Smuzhiyun 		dev_err(v3d->drm.dev, "MMU error from client %s (%d) at 0x%llx%s%s%s\n",
193*4882a593Smuzhiyun 			client, axi_id, (long long)vio_addr,
194*4882a593Smuzhiyun 			((intsts & V3D_HUB_INT_MMU_WRV) ?
195*4882a593Smuzhiyun 			 ", write violation" : ""),
196*4882a593Smuzhiyun 			((intsts & V3D_HUB_INT_MMU_PTI) ?
197*4882a593Smuzhiyun 			 ", pte invalid" : ""),
198*4882a593Smuzhiyun 			((intsts & V3D_HUB_INT_MMU_CAP) ?
199*4882a593Smuzhiyun 			 ", cap exceeded" : ""));
200*4882a593Smuzhiyun 		status = IRQ_HANDLED;
201*4882a593Smuzhiyun 	}
202*4882a593Smuzhiyun 
203*4882a593Smuzhiyun 	return status;
204*4882a593Smuzhiyun }
205*4882a593Smuzhiyun 
206*4882a593Smuzhiyun int
v3d_irq_init(struct v3d_dev * v3d)207*4882a593Smuzhiyun v3d_irq_init(struct v3d_dev *v3d)
208*4882a593Smuzhiyun {
209*4882a593Smuzhiyun 	int irq1, ret, core;
210*4882a593Smuzhiyun 
211*4882a593Smuzhiyun 	INIT_WORK(&v3d->overflow_mem_work, v3d_overflow_mem_work);
212*4882a593Smuzhiyun 
213*4882a593Smuzhiyun 	/* Clear any pending interrupts someone might have left around
214*4882a593Smuzhiyun 	 * for us.
215*4882a593Smuzhiyun 	 */
216*4882a593Smuzhiyun 	for (core = 0; core < v3d->cores; core++)
217*4882a593Smuzhiyun 		V3D_CORE_WRITE(core, V3D_CTL_INT_CLR, V3D_CORE_IRQS);
218*4882a593Smuzhiyun 	V3D_WRITE(V3D_HUB_INT_CLR, V3D_HUB_IRQS);
219*4882a593Smuzhiyun 
220*4882a593Smuzhiyun 	irq1 = platform_get_irq(v3d_to_pdev(v3d), 1);
221*4882a593Smuzhiyun 	if (irq1 == -EPROBE_DEFER)
222*4882a593Smuzhiyun 		return irq1;
223*4882a593Smuzhiyun 	if (irq1 > 0) {
224*4882a593Smuzhiyun 		ret = devm_request_irq(v3d->drm.dev, irq1,
225*4882a593Smuzhiyun 				       v3d_irq, IRQF_SHARED,
226*4882a593Smuzhiyun 				       "v3d_core0", v3d);
227*4882a593Smuzhiyun 		if (ret)
228*4882a593Smuzhiyun 			goto fail;
229*4882a593Smuzhiyun 		ret = devm_request_irq(v3d->drm.dev,
230*4882a593Smuzhiyun 				       platform_get_irq(v3d_to_pdev(v3d), 0),
231*4882a593Smuzhiyun 				       v3d_hub_irq, IRQF_SHARED,
232*4882a593Smuzhiyun 				       "v3d_hub", v3d);
233*4882a593Smuzhiyun 		if (ret)
234*4882a593Smuzhiyun 			goto fail;
235*4882a593Smuzhiyun 	} else {
236*4882a593Smuzhiyun 		v3d->single_irq_line = true;
237*4882a593Smuzhiyun 
238*4882a593Smuzhiyun 		ret = devm_request_irq(v3d->drm.dev,
239*4882a593Smuzhiyun 				       platform_get_irq(v3d_to_pdev(v3d), 0),
240*4882a593Smuzhiyun 				       v3d_irq, IRQF_SHARED,
241*4882a593Smuzhiyun 				       "v3d", v3d);
242*4882a593Smuzhiyun 		if (ret)
243*4882a593Smuzhiyun 			goto fail;
244*4882a593Smuzhiyun 	}
245*4882a593Smuzhiyun 
246*4882a593Smuzhiyun 	v3d_irq_enable(v3d);
247*4882a593Smuzhiyun 	return 0;
248*4882a593Smuzhiyun 
249*4882a593Smuzhiyun fail:
250*4882a593Smuzhiyun 	if (ret != -EPROBE_DEFER)
251*4882a593Smuzhiyun 		dev_err(v3d->drm.dev, "IRQ setup failed: %d\n", ret);
252*4882a593Smuzhiyun 	return ret;
253*4882a593Smuzhiyun }
254*4882a593Smuzhiyun 
255*4882a593Smuzhiyun void
v3d_irq_enable(struct v3d_dev * v3d)256*4882a593Smuzhiyun v3d_irq_enable(struct v3d_dev *v3d)
257*4882a593Smuzhiyun {
258*4882a593Smuzhiyun 	int core;
259*4882a593Smuzhiyun 
260*4882a593Smuzhiyun 	/* Enable our set of interrupts, masking out any others. */
261*4882a593Smuzhiyun 	for (core = 0; core < v3d->cores; core++) {
262*4882a593Smuzhiyun 		V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_SET, ~V3D_CORE_IRQS);
263*4882a593Smuzhiyun 		V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_CLR, V3D_CORE_IRQS);
264*4882a593Smuzhiyun 	}
265*4882a593Smuzhiyun 
266*4882a593Smuzhiyun 	V3D_WRITE(V3D_HUB_INT_MSK_SET, ~V3D_HUB_IRQS);
267*4882a593Smuzhiyun 	V3D_WRITE(V3D_HUB_INT_MSK_CLR, V3D_HUB_IRQS);
268*4882a593Smuzhiyun }
269*4882a593Smuzhiyun 
270*4882a593Smuzhiyun void
v3d_irq_disable(struct v3d_dev * v3d)271*4882a593Smuzhiyun v3d_irq_disable(struct v3d_dev *v3d)
272*4882a593Smuzhiyun {
273*4882a593Smuzhiyun 	int core;
274*4882a593Smuzhiyun 
275*4882a593Smuzhiyun 	/* Disable all interrupts. */
276*4882a593Smuzhiyun 	for (core = 0; core < v3d->cores; core++)
277*4882a593Smuzhiyun 		V3D_CORE_WRITE(core, V3D_CTL_INT_MSK_SET, ~0);
278*4882a593Smuzhiyun 	V3D_WRITE(V3D_HUB_INT_MSK_SET, ~0);
279*4882a593Smuzhiyun 
280*4882a593Smuzhiyun 	/* Clear any pending interrupts we might have left. */
281*4882a593Smuzhiyun 	for (core = 0; core < v3d->cores; core++)
282*4882a593Smuzhiyun 		V3D_CORE_WRITE(core, V3D_CTL_INT_CLR, V3D_CORE_IRQS);
283*4882a593Smuzhiyun 	V3D_WRITE(V3D_HUB_INT_CLR, V3D_HUB_IRQS);
284*4882a593Smuzhiyun 
285*4882a593Smuzhiyun 	cancel_work_sync(&v3d->overflow_mem_work);
286*4882a593Smuzhiyun }
287*4882a593Smuzhiyun 
288*4882a593Smuzhiyun /** Reinitializes interrupt registers when a GPU reset is performed. */
v3d_irq_reset(struct v3d_dev * v3d)289*4882a593Smuzhiyun void v3d_irq_reset(struct v3d_dev *v3d)
290*4882a593Smuzhiyun {
291*4882a593Smuzhiyun 	v3d_irq_enable(v3d);
292*4882a593Smuzhiyun }
293