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