xref: /linux/drivers/media/pci/hws/hws_irq.c (revision 3d5e48944e824bddc20d7b874e784f7b279636fe)
1*ba07fd2fSBen Hoff // SPDX-License-Identifier: GPL-2.0-only
2*ba07fd2fSBen Hoff #include <linux/compiler.h>
3*ba07fd2fSBen Hoff #include <linux/moduleparam.h>
4*ba07fd2fSBen Hoff #include <linux/io.h>
5*ba07fd2fSBen Hoff #include <linux/dma-mapping.h>
6*ba07fd2fSBen Hoff #include <linux/interrupt.h>
7*ba07fd2fSBen Hoff #include <linux/minmax.h>
8*ba07fd2fSBen Hoff #include <linux/string.h>
9*ba07fd2fSBen Hoff 
10*ba07fd2fSBen Hoff #include <media/videobuf2-dma-contig.h>
11*ba07fd2fSBen Hoff 
12*ba07fd2fSBen Hoff #include "hws_irq.h"
13*ba07fd2fSBen Hoff #include "hws_reg.h"
14*ba07fd2fSBen Hoff #include "hws_video.h"
15*ba07fd2fSBen Hoff #include "hws.h"
16*ba07fd2fSBen Hoff 
17*ba07fd2fSBen Hoff #define MAX_INT_LOOPS 100
18*ba07fd2fSBen Hoff 
19*ba07fd2fSBen Hoff static bool hws_toggle_debug;
20*ba07fd2fSBen Hoff module_param_named(toggle_debug, hws_toggle_debug, bool, 0644);
21*ba07fd2fSBen Hoff MODULE_PARM_DESC(toggle_debug,
22*ba07fd2fSBen Hoff 		 "Read toggle registers in IRQ handler for debug logging");
23*ba07fd2fSBen Hoff 
24*ba07fd2fSBen Hoff static int hws_arm_next(struct hws_pcie_dev *hws, u32 ch)
25*ba07fd2fSBen Hoff {
26*ba07fd2fSBen Hoff 	struct hws_video *v = &hws->video[ch];
27*ba07fd2fSBen Hoff 	unsigned long flags;
28*ba07fd2fSBen Hoff 	struct hwsvideo_buffer *buf;
29*ba07fd2fSBen Hoff 
30*ba07fd2fSBen Hoff 	dev_dbg(&hws->pdev->dev,
31*ba07fd2fSBen Hoff 		"arm_next(ch=%u): stop=%d cap=%d queued=%d\n",
32*ba07fd2fSBen Hoff 		ch, READ_ONCE(v->stop_requested), READ_ONCE(v->cap_active),
33*ba07fd2fSBen Hoff 		!list_empty(&v->capture_queue));
34*ba07fd2fSBen Hoff 
35*ba07fd2fSBen Hoff 	if (READ_ONCE(hws->suspended)) {
36*ba07fd2fSBen Hoff 		dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): suspended\n", ch);
37*ba07fd2fSBen Hoff 		return -EBUSY;
38*ba07fd2fSBen Hoff 	}
39*ba07fd2fSBen Hoff 
40*ba07fd2fSBen Hoff 	if (READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active)) {
41*ba07fd2fSBen Hoff 		dev_dbg(&hws->pdev->dev,
42*ba07fd2fSBen Hoff 			"arm_next(ch=%u): stop=%d cap=%d -> cancel\n", ch,
43*ba07fd2fSBen Hoff 			v->stop_requested, v->cap_active);
44*ba07fd2fSBen Hoff 		return -ECANCELED;
45*ba07fd2fSBen Hoff 	}
46*ba07fd2fSBen Hoff 
47*ba07fd2fSBen Hoff 	spin_lock_irqsave(&v->irq_lock, flags);
48*ba07fd2fSBen Hoff 	if (list_empty(&v->capture_queue)) {
49*ba07fd2fSBen Hoff 		spin_unlock_irqrestore(&v->irq_lock, flags);
50*ba07fd2fSBen Hoff 		dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): queue empty\n", ch);
51*ba07fd2fSBen Hoff 		return -EAGAIN;
52*ba07fd2fSBen Hoff 	}
53*ba07fd2fSBen Hoff 
54*ba07fd2fSBen Hoff 	buf = list_first_entry(&v->capture_queue, struct hwsvideo_buffer, list);
55*ba07fd2fSBen Hoff 	list_del_init(&buf->list);	/* keep buffer safe for later cleanup */
56*ba07fd2fSBen Hoff 	if (v->queued_count)
57*ba07fd2fSBen Hoff 		v->queued_count--;
58*ba07fd2fSBen Hoff 	v->active = buf;
59*ba07fd2fSBen Hoff 	spin_unlock_irqrestore(&v->irq_lock, flags);
60*ba07fd2fSBen Hoff 	dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): picked buffer %p\n", ch,
61*ba07fd2fSBen Hoff 		buf);
62*ba07fd2fSBen Hoff 
63*ba07fd2fSBen Hoff 	/* Publish descriptor(s) before doorbell/MMIO kicks. */
64*ba07fd2fSBen Hoff 	wmb();
65*ba07fd2fSBen Hoff 
66*ba07fd2fSBen Hoff 	/* Avoid MMIO during suspend */
67*ba07fd2fSBen Hoff 	if (READ_ONCE(hws->suspended)) {
68*ba07fd2fSBen Hoff 		unsigned long f;
69*ba07fd2fSBen Hoff 
70*ba07fd2fSBen Hoff 		dev_dbg(&hws->pdev->dev,
71*ba07fd2fSBen Hoff 			"arm_next(ch=%u): suspended after pick\n", ch);
72*ba07fd2fSBen Hoff 		spin_lock_irqsave(&v->irq_lock, f);
73*ba07fd2fSBen Hoff 		if (v->active) {
74*ba07fd2fSBen Hoff 			list_add(&buf->list, &v->capture_queue);
75*ba07fd2fSBen Hoff 			v->queued_count++;
76*ba07fd2fSBen Hoff 			v->active = NULL;
77*ba07fd2fSBen Hoff 		}
78*ba07fd2fSBen Hoff 		spin_unlock_irqrestore(&v->irq_lock, f);
79*ba07fd2fSBen Hoff 		return -EBUSY;
80*ba07fd2fSBen Hoff 	}
81*ba07fd2fSBen Hoff 
82*ba07fd2fSBen Hoff 	/* Also program the DMA address register directly */
83*ba07fd2fSBen Hoff 	{
84*ba07fd2fSBen Hoff 		dma_addr_t dma_addr =
85*ba07fd2fSBen Hoff 		    vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
86*ba07fd2fSBen Hoff 		hws_program_dma_for_addr(hws, ch, dma_addr);
87*ba07fd2fSBen Hoff 		iowrite32(lower_32_bits(dma_addr),
88*ba07fd2fSBen Hoff 			  hws->bar0_base + HWS_REG_DMA_ADDR(ch));
89*ba07fd2fSBen Hoff 	}
90*ba07fd2fSBen Hoff 
91*ba07fd2fSBen Hoff 	dev_dbg(&hws->pdev->dev, "arm_next(ch=%u): programmed buffer %p\n", ch,
92*ba07fd2fSBen Hoff 		buf);
93*ba07fd2fSBen Hoff 	spin_lock_irqsave(&v->irq_lock, flags);
94*ba07fd2fSBen Hoff 	hws_prime_next_locked(v);
95*ba07fd2fSBen Hoff 	spin_unlock_irqrestore(&v->irq_lock, flags);
96*ba07fd2fSBen Hoff 	return 0;
97*ba07fd2fSBen Hoff }
98*ba07fd2fSBen Hoff 
99*ba07fd2fSBen Hoff static void hws_video_handle_vdone(struct hws_video *v)
100*ba07fd2fSBen Hoff {
101*ba07fd2fSBen Hoff 	struct hws_pcie_dev *hws = v->parent;
102*ba07fd2fSBen Hoff 	unsigned int ch = v->channel_index;
103*ba07fd2fSBen Hoff 	struct hwsvideo_buffer *done;
104*ba07fd2fSBen Hoff 	unsigned long flags;
105*ba07fd2fSBen Hoff 	bool promoted = false;
106*ba07fd2fSBen Hoff 
107*ba07fd2fSBen Hoff 	dev_dbg(&hws->pdev->dev,
108*ba07fd2fSBen Hoff 		"bh_video(ch=%u): stop=%d cap=%d active=%p\n",
109*ba07fd2fSBen Hoff 		ch, READ_ONCE(v->stop_requested), READ_ONCE(v->cap_active),
110*ba07fd2fSBen Hoff 		v->active);
111*ba07fd2fSBen Hoff 
112*ba07fd2fSBen Hoff 	int ret;
113*ba07fd2fSBen Hoff 
114*ba07fd2fSBen Hoff 	dev_dbg(&hws->pdev->dev,
115*ba07fd2fSBen Hoff 		"bh_video(ch=%u): entry stop=%d cap=%d\n", ch,
116*ba07fd2fSBen Hoff 		v->stop_requested, v->cap_active);
117*ba07fd2fSBen Hoff 	if (READ_ONCE(hws->suspended))
118*ba07fd2fSBen Hoff 		return;
119*ba07fd2fSBen Hoff 
120*ba07fd2fSBen Hoff 	if (READ_ONCE(v->stop_requested) || !READ_ONCE(v->cap_active))
121*ba07fd2fSBen Hoff 		return;
122*ba07fd2fSBen Hoff 
123*ba07fd2fSBen Hoff 	spin_lock_irqsave(&v->irq_lock, flags);
124*ba07fd2fSBen Hoff 	done = v->active;
125*ba07fd2fSBen Hoff 	if (done && v->next_prepared) {
126*ba07fd2fSBen Hoff 		v->active = v->next_prepared;
127*ba07fd2fSBen Hoff 		v->next_prepared = NULL;
128*ba07fd2fSBen Hoff 		promoted = true;
129*ba07fd2fSBen Hoff 	}
130*ba07fd2fSBen Hoff 	spin_unlock_irqrestore(&v->irq_lock, flags);
131*ba07fd2fSBen Hoff 
132*ba07fd2fSBen Hoff 	/* 1) Complete the buffer the HW just finished (if any) */
133*ba07fd2fSBen Hoff 	if (done) {
134*ba07fd2fSBen Hoff 		struct vb2_v4l2_buffer *vb2v = &done->vb;
135*ba07fd2fSBen Hoff 		size_t expected = v->pix.sizeimage;
136*ba07fd2fSBen Hoff 		size_t plane_size = vb2_plane_size(&vb2v->vb2_buf, 0);
137*ba07fd2fSBen Hoff 
138*ba07fd2fSBen Hoff 		if (expected > plane_size) {
139*ba07fd2fSBen Hoff 			dev_warn_ratelimited(&hws->pdev->dev,
140*ba07fd2fSBen Hoff 					     "bh_video(ch=%u): sizeimage %zu > plane %zu, dropping seq=%u\n",
141*ba07fd2fSBen Hoff 					     ch, expected, plane_size,
142*ba07fd2fSBen Hoff 					     (u32)atomic_read(&v->sequence_number) + 1);
143*ba07fd2fSBen Hoff 			vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_ERROR);
144*ba07fd2fSBen Hoff 			goto arm_next;
145*ba07fd2fSBen Hoff 		}
146*ba07fd2fSBen Hoff 		vb2_set_plane_payload(&vb2v->vb2_buf, 0, expected);
147*ba07fd2fSBen Hoff 
148*ba07fd2fSBen Hoff 		dma_rmb();	/* device writes visible before userspace sees it */
149*ba07fd2fSBen Hoff 
150*ba07fd2fSBen Hoff 		vb2v->sequence = (u32)atomic_inc_return(&v->sequence_number);
151*ba07fd2fSBen Hoff 		vb2v->vb2_buf.timestamp = ktime_get_ns();
152*ba07fd2fSBen Hoff 		dev_dbg(&hws->pdev->dev,
153*ba07fd2fSBen Hoff 			"bh_video(ch=%u): DONE buf=%p seq=%u half_seen=%d toggle=%u\n",
154*ba07fd2fSBen Hoff 			ch, done, vb2v->sequence, v->half_seen,
155*ba07fd2fSBen Hoff 			v->last_buf_half_toggle);
156*ba07fd2fSBen Hoff 
157*ba07fd2fSBen Hoff 		if (!promoted)
158*ba07fd2fSBen Hoff 			v->active = NULL;	/* channel no longer owns this buffer */
159*ba07fd2fSBen Hoff 		vb2_buffer_done(&vb2v->vb2_buf, VB2_BUF_STATE_DONE);
160*ba07fd2fSBen Hoff 	}
161*ba07fd2fSBen Hoff 
162*ba07fd2fSBen Hoff 	if (READ_ONCE(hws->suspended))
163*ba07fd2fSBen Hoff 		return;
164*ba07fd2fSBen Hoff 
165*ba07fd2fSBen Hoff 	if (promoted) {
166*ba07fd2fSBen Hoff 		dev_dbg(&hws->pdev->dev,
167*ba07fd2fSBen Hoff 			"bh_video(ch=%u): promoted pre-armed buffer active=%p\n",
168*ba07fd2fSBen Hoff 			ch, v->active);
169*ba07fd2fSBen Hoff 		spin_lock_irqsave(&v->irq_lock, flags);
170*ba07fd2fSBen Hoff 		hws_prime_next_locked(v);
171*ba07fd2fSBen Hoff 		spin_unlock_irqrestore(&v->irq_lock, flags);
172*ba07fd2fSBen Hoff 		return;
173*ba07fd2fSBen Hoff 	}
174*ba07fd2fSBen Hoff 
175*ba07fd2fSBen Hoff arm_next:
176*ba07fd2fSBen Hoff 	/* 2) Immediately arm the next queued buffer (if present) */
177*ba07fd2fSBen Hoff 	ret = hws_arm_next(hws, ch);
178*ba07fd2fSBen Hoff 	if (ret == -EAGAIN) {
179*ba07fd2fSBen Hoff 		dev_dbg(&hws->pdev->dev,
180*ba07fd2fSBen Hoff 			"bh_video(ch=%u): no queued buffer to arm\n", ch);
181*ba07fd2fSBen Hoff 		return;
182*ba07fd2fSBen Hoff 	}
183*ba07fd2fSBen Hoff 	dev_dbg(&hws->pdev->dev,
184*ba07fd2fSBen Hoff 		"bh_video(ch=%u): armed next buffer, active=%p\n", ch,
185*ba07fd2fSBen Hoff 		v->active);
186*ba07fd2fSBen Hoff 	/* On success the engine now points at v->active's DMA address */
187*ba07fd2fSBen Hoff }
188*ba07fd2fSBen Hoff 
189*ba07fd2fSBen Hoff irqreturn_t hws_irq_handler(int irq, void *info)
190*ba07fd2fSBen Hoff {
191*ba07fd2fSBen Hoff 	struct hws_pcie_dev *pdx = info;
192*ba07fd2fSBen Hoff 	u32 int_state;
193*ba07fd2fSBen Hoff 
194*ba07fd2fSBen Hoff 	dev_dbg(&pdx->pdev->dev, "irq: entry\n");
195*ba07fd2fSBen Hoff 	if (pdx->bar0_base) {
196*ba07fd2fSBen Hoff 		dev_dbg(&pdx->pdev->dev,
197*ba07fd2fSBen Hoff 			"irq: INT_EN=0x%08x INT_STATUS=0x%08x\n",
198*ba07fd2fSBen Hoff 			readl(pdx->bar0_base + INT_EN_REG_BASE),
199*ba07fd2fSBen Hoff 			readl(pdx->bar0_base + HWS_REG_INT_STATUS));
200*ba07fd2fSBen Hoff 	}
201*ba07fd2fSBen Hoff 
202*ba07fd2fSBen Hoff 	/* Fast path: if suspended, quietly ack and exit */
203*ba07fd2fSBen Hoff 	if (READ_ONCE(pdx->suspended)) {
204*ba07fd2fSBen Hoff 		int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
205*ba07fd2fSBen Hoff 		if (int_state) {
206*ba07fd2fSBen Hoff 			writel(int_state, pdx->bar0_base + HWS_REG_INT_STATUS);
207*ba07fd2fSBen Hoff 			(void)readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
208*ba07fd2fSBen Hoff 		}
209*ba07fd2fSBen Hoff 		return int_state ? IRQ_HANDLED : IRQ_NONE;
210*ba07fd2fSBen Hoff 	}
211*ba07fd2fSBen Hoff 	int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
212*ba07fd2fSBen Hoff 	if (!int_state || int_state == 0xFFFFFFFF) {
213*ba07fd2fSBen Hoff 		dev_dbg(&pdx->pdev->dev,
214*ba07fd2fSBen Hoff 			"irq: spurious or device-gone int_state=0x%08x\n",
215*ba07fd2fSBen Hoff 			int_state);
216*ba07fd2fSBen Hoff 		return IRQ_NONE;
217*ba07fd2fSBen Hoff 	}
218*ba07fd2fSBen Hoff 	dev_dbg(&pdx->pdev->dev, "irq: entry INT_STATUS=0x%08x\n", int_state);
219*ba07fd2fSBen Hoff 
220*ba07fd2fSBen Hoff 	/* Loop until all pending bits are serviced (max 100 iterations) */
221*ba07fd2fSBen Hoff 	for (u32 cnt = 0; int_state && cnt < MAX_INT_LOOPS; ++cnt) {
222*ba07fd2fSBen Hoff 		for (unsigned int ch = 0; ch < pdx->cur_max_video_ch; ++ch) {
223*ba07fd2fSBen Hoff 			u32 vbit = HWS_INT_VDONE_BIT(ch);
224*ba07fd2fSBen Hoff 
225*ba07fd2fSBen Hoff 			if (!(int_state & vbit))
226*ba07fd2fSBen Hoff 				continue;
227*ba07fd2fSBen Hoff 
228*ba07fd2fSBen Hoff 			if (READ_ONCE(pdx->video[ch].cap_active) &&
229*ba07fd2fSBen Hoff 			    !READ_ONCE(pdx->video[ch].stop_requested)) {
230*ba07fd2fSBen Hoff 				if (hws_toggle_debug) {
231*ba07fd2fSBen Hoff 					u32 toggle =
232*ba07fd2fSBen Hoff 					    readl_relaxed(pdx->bar0_base +
233*ba07fd2fSBen Hoff 						  HWS_REG_VBUF_TOGGLE(ch)) & 0x01;
234*ba07fd2fSBen Hoff 					WRITE_ONCE(pdx->video[ch].last_buf_half_toggle,
235*ba07fd2fSBen Hoff 						   toggle);
236*ba07fd2fSBen Hoff 				}
237*ba07fd2fSBen Hoff 				dma_rmb();
238*ba07fd2fSBen Hoff 				WRITE_ONCE(pdx->video[ch].half_seen, true);
239*ba07fd2fSBen Hoff 				dev_dbg(&pdx->pdev->dev,
240*ba07fd2fSBen Hoff 					"irq: VDONE ch=%u toggle=%u handling inline (cap=%d)\n",
241*ba07fd2fSBen Hoff 					ch,
242*ba07fd2fSBen Hoff 					READ_ONCE(pdx->video[ch].last_buf_half_toggle),
243*ba07fd2fSBen Hoff 					READ_ONCE(pdx->video[ch].cap_active));
244*ba07fd2fSBen Hoff 				hws_video_handle_vdone(&pdx->video[ch]);
245*ba07fd2fSBen Hoff 			} else {
246*ba07fd2fSBen Hoff 				dev_dbg(&pdx->pdev->dev,
247*ba07fd2fSBen Hoff 					"irq: VDONE ch=%u ignored (cap=%d stop=%d)\n",
248*ba07fd2fSBen Hoff 					ch,
249*ba07fd2fSBen Hoff 					READ_ONCE(pdx->video[ch].cap_active),
250*ba07fd2fSBen Hoff 					READ_ONCE(pdx->video[ch].stop_requested));
251*ba07fd2fSBen Hoff 			}
252*ba07fd2fSBen Hoff 
253*ba07fd2fSBen Hoff 			writel(vbit, pdx->bar0_base + HWS_REG_INT_STATUS);
254*ba07fd2fSBen Hoff 			(void)readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
255*ba07fd2fSBen Hoff 		}
256*ba07fd2fSBen Hoff 
257*ba07fd2fSBen Hoff 		/* Re-read in case new interrupt bits popped while processing */
258*ba07fd2fSBen Hoff 		int_state = readl_relaxed(pdx->bar0_base + HWS_REG_INT_STATUS);
259*ba07fd2fSBen Hoff 		dev_dbg(&pdx->pdev->dev,
260*ba07fd2fSBen Hoff 			"irq: loop cnt=%u new INT_STATUS=0x%08x\n", cnt,
261*ba07fd2fSBen Hoff 			int_state);
262*ba07fd2fSBen Hoff 		if (cnt + 1 == MAX_INT_LOOPS)
263*ba07fd2fSBen Hoff 			dev_warn_ratelimited(&pdx->pdev->dev,
264*ba07fd2fSBen Hoff 					     "IRQ storm? status=0x%08x\n",
265*ba07fd2fSBen Hoff 					     int_state);
266*ba07fd2fSBen Hoff 	}
267*ba07fd2fSBen Hoff 
268*ba07fd2fSBen Hoff 	return IRQ_HANDLED;
269*ba07fd2fSBen Hoff }
270