V4L/DVB (5675): Move big PIO accesses from the interrupt handler to a workhandler

Sliced VBI transfers use PIO instead of DMA. This was done inside the
interrupt handler, but since PIO accesses are very slow this meant that
a lot of time was spent inside the interrupt handler. All PIO copies are
now moved to a workqueue. This should fix various issues with missing time
ticks and remote key hits.

Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
diff --git a/drivers/media/video/ivtv/ivtv-driver.c b/drivers/media/video/ivtv/ivtv-driver.c
index e29f949..efc6635 100644
--- a/drivers/media/video/ivtv/ivtv-driver.c
+++ b/drivers/media/video/ivtv/ivtv-driver.c
@@ -652,6 +652,7 @@
 	itv->dma_timer.data = (unsigned long)itv;
 
 	itv->cur_dma_stream = -1;
+	itv->cur_pio_stream = -1;
 	itv->audio_stereo_mode = AUDIO_STEREO;
 	itv->audio_bilingual_mode = AUDIO_MONO_LEFT;
 
diff --git a/drivers/media/video/ivtv/ivtv-driver.h b/drivers/media/video/ivtv/ivtv-driver.h
index 552f045..e6e56f1 100644
--- a/drivers/media/video/ivtv/ivtv-driver.h
+++ b/drivers/media/video/ivtv/ivtv-driver.h
@@ -237,6 +237,7 @@
 #define IVTV_IRQ_ENC_VBI_CAP		(0x1 << 29)
 #define IVTV_IRQ_ENC_VIM_RST		(0x1 << 28)
 #define IVTV_IRQ_ENC_DMA_COMPLETE	(0x1 << 27)
+#define IVTV_IRQ_ENC_PIO_COMPLETE	(0x1 << 25)
 #define IVTV_IRQ_DEC_AUD_MODE_CHG	(0x1 << 24)
 #define IVTV_IRQ_DEC_DATA_REQ		(0x1 << 22)
 #define IVTV_IRQ_DEC_DMA_COMPLETE	(0x1 << 20)
@@ -247,7 +248,8 @@
 #define IVTV_IRQ_DEC_VSYNC		(0x1 << 10)
 
 /* IRQ Masks */
-#define IVTV_IRQ_MASK_INIT (IVTV_IRQ_DMA_ERR|IVTV_IRQ_ENC_DMA_COMPLETE|IVTV_IRQ_DMA_READ)
+#define IVTV_IRQ_MASK_INIT (IVTV_IRQ_DMA_ERR|IVTV_IRQ_ENC_DMA_COMPLETE|\
+		IVTV_IRQ_DMA_READ|IVTV_IRQ_ENC_PIO_COMPLETE)
 
 #define IVTV_IRQ_MASK_CAPTURE (IVTV_IRQ_ENC_START_CAP | IVTV_IRQ_ENC_EOS)
 #define IVTV_IRQ_MASK_DECODE  (IVTV_IRQ_DEC_DATA_REQ|IVTV_IRQ_DEC_AUD_MODE_CHG)
@@ -374,6 +376,9 @@
 #define IVTV_F_S_STREAMOFF	7	/* signal end of stream EOS */
 #define IVTV_F_S_APPL_IO        8	/* this stream is used read/written by an application */
 
+#define IVTV_F_S_PIO_PENDING	9	/* this stream has pending PIO */
+#define IVTV_F_S_PIO_HAS_VBI	1       /* the current PIO request also requests VBI data */
+
 /* per-ivtv, i_flags */
 #define IVTV_F_I_DMA		   0 	/* DMA in progress */
 #define IVTV_F_I_UDMA		   1 	/* UDMA in progress */
@@ -390,8 +395,11 @@
 #define IVTV_F_I_DECODING_YUV	   12 	/* this stream is YUV frame decoding */
 #define IVTV_F_I_ENC_PAUSED	   13 	/* the encoder is paused */
 #define IVTV_F_I_VALID_DEC_TIMINGS 14 	/* last_dec_timing is valid */
-#define IVTV_F_I_WORK_HANDLER_VBI  15	/* there is work to be done for VBI */
-#define IVTV_F_I_WORK_HANDLER_YUV  16	/* there is work to be done for YUV */
+#define IVTV_F_I_HAVE_WORK  	   15	/* Used in the interrupt handler: there is work to be done */
+#define IVTV_F_I_WORK_HANDLER_VBI  16	/* there is work to be done for VBI */
+#define IVTV_F_I_WORK_HANDLER_YUV  17	/* there is work to be done for YUV */
+#define IVTV_F_I_WORK_HANDLER_PIO  18	/* there is work to be done for PIO */
+#define IVTV_F_I_PIO		   19	/* PIO in progress */
 
 /* Event notifications */
 #define IVTV_F_I_EV_DEC_STOPPED	   28	/* decoder stopped event */
@@ -484,6 +492,7 @@
 
 	/* Base Dev SG Array for cx23415/6 */
 	struct ivtv_SG_element *SGarray;
+	struct ivtv_SG_element *PIOarray;
 	dma_addr_t SG_handle;
 	int SG_length;
 
@@ -706,6 +715,7 @@
 	atomic_t decoding;	/* count number of active decoding streams */
 	u32 irq_rr_idx; /* Round-robin stream index */
 	int cur_dma_stream;	/* index of stream doing DMA */
+	int cur_pio_stream;	/* index of stream doing PIO */
 	u32 dma_data_req_offset;
 	u32 dma_data_req_size;
 	int output_mode;        /* NONE, MPG, YUV, UDMA YUV, passthrough */
diff --git a/drivers/media/video/ivtv/ivtv-irq.c b/drivers/media/video/ivtv/ivtv-irq.c
index c3a047b..ba98bf0 100644
--- a/drivers/media/video/ivtv/ivtv-irq.c
+++ b/drivers/media/video/ivtv/ivtv-irq.c
@@ -31,8 +31,6 @@
 
 #define DMA_MAGIC_COOKIE 0x000001fe
 
-#define SLICED_VBI_PIO 1
-
 static void ivtv_dma_dec_start(struct ivtv_stream *s);
 
 static const int ivtv_stream_map[] = {
@@ -42,12 +40,40 @@
 	IVTV_ENC_STREAM_TYPE_VBI,
 };
 
-static inline int ivtv_use_pio(struct ivtv_stream *s)
-{
-	struct ivtv *itv = s->itv;
 
-	return s->dma == PCI_DMA_NONE ||
-	    (SLICED_VBI_PIO && s->type == IVTV_ENC_STREAM_TYPE_VBI && itv->vbi.sliced_in->service_set);
+static void ivtv_pio_work_handler(struct ivtv *itv)
+{
+	struct ivtv_stream *s = &itv->streams[itv->cur_pio_stream];
+	struct ivtv_buffer *buf;
+	struct list_head *p;
+	int i = 0;
+
+	IVTV_DEBUG_DMA("ivtv_pio_work_handler\n");
+	if (itv->cur_pio_stream < 0 || itv->cur_pio_stream >= IVTV_MAX_STREAMS ||
+			s->v4l2dev == NULL || !ivtv_use_pio(s)) {
+		itv->cur_pio_stream = -1;
+		/* trigger PIO complete user interrupt */
+		write_reg(IVTV_IRQ_ENC_PIO_COMPLETE, 0x44);
+		return;
+	}
+	IVTV_DEBUG_DMA("Process PIO %s\n", s->name);
+	buf = list_entry(s->q_dma.list.next, struct ivtv_buffer, list);
+	list_for_each(p, &s->q_dma.list) {
+		struct ivtv_buffer *buf = list_entry(p, struct ivtv_buffer, list);
+		u32 size = s->PIOarray[i].size & 0x3ffff;
+
+		/* Copy the data from the card to the buffer */
+		if (s->type == IVTV_DEC_STREAM_TYPE_VBI) {
+			memcpy_fromio(buf->buf, itv->dec_mem + s->PIOarray[i].src - IVTV_DECODER_OFFSET, size);
+		}
+		else {
+			memcpy_fromio(buf->buf, itv->enc_mem + s->PIOarray[i].src, size);
+		}
+		if (s->PIOarray[i].size & 0x80000000)
+			break;
+		i++;
+	}
+	write_reg(IVTV_IRQ_ENC_PIO_COMPLETE, 0x44);
 }
 
 void ivtv_irq_work_handler(struct work_struct *work)
@@ -56,8 +82,11 @@
 
 	DEFINE_WAIT(wait);
 
+	if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_PIO, &itv->i_flags))
+		ivtv_pio_work_handler(itv);
+
 	if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags))
-		vbi_work_handler(itv);
+		ivtv_vbi_work_handler(itv);
 
 	if (test_and_clear_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags))
 		ivtv_yuv_work_handler(itv);
@@ -173,8 +202,7 @@
 	}
 	s->buffers_stolen = rc;
 
-	/* got the buffers, now fill in SGarray (DMA) or copy the data from the card
-	   to the buffers (PIO). */
+	/* got the buffers, now fill in SGarray (DMA) */
 	buf = list_entry(s->q_predma.list.next, struct ivtv_buffer, list);
 	memset(buf->buf, 0, 128);
 	list_for_each(p, &s->q_predma.list) {
@@ -182,21 +210,11 @@
 
 		if (skip_bufs-- > 0)
 			continue;
-		if (!ivtv_use_pio(s)) {
-			s->SGarray[idx].dst = cpu_to_le32(buf->dma_handle);
-			s->SGarray[idx].src = cpu_to_le32(offset);
-			s->SGarray[idx].size = cpu_to_le32(s->buf_size);
-		}
+		s->SGarray[idx].dst = cpu_to_le32(buf->dma_handle);
+		s->SGarray[idx].src = cpu_to_le32(offset);
+		s->SGarray[idx].size = cpu_to_le32(s->buf_size);
 		buf->bytesused = (size < s->buf_size) ? size : s->buf_size;
 
-		/* If PIO, then copy the data from the card to the buffer */
-		if (s->type == IVTV_DEC_STREAM_TYPE_VBI) {
-			memcpy_fromio(buf->buf, itv->dec_mem + offset - IVTV_DECODER_OFFSET, buf->bytesused);
-		}
-		else if (ivtv_use_pio(s)) {
-			memcpy_fromio(buf->buf, itv->enc_mem + offset, buf->bytesused);
-		}
-
 		s->q_predma.bytesused += buf->bytesused;
 		size -= buf->bytesused;
 		offset += s->buf_size;
@@ -224,11 +242,6 @@
 	u32 *u32buf;
 	int x = 0;
 
-	if (ivtv_use_pio(s)) {
-		if (s->q_predma.bytesused)
-			ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused);
-		s->SG_length = 0;
-	}
 	IVTV_DEBUG_DMA("%s %s completed (%x)\n", ivtv_use_pio(s) ? "PIO" : "DMA",
 			s->name, s->dma_offset);
 	list_for_each(p, &s->q_dma.list) {
@@ -278,10 +291,14 @@
 	if (buf)
 		buf->bytesused += s->dma_last_offset;
 	if (buf && s->type == IVTV_DEC_STREAM_TYPE_VBI) {
-		/* Parse and Groom VBI Data */
-		s->q_dma.bytesused -= buf->bytesused;
-		ivtv_process_vbi_data(itv, buf, 0, s->type);
-		s->q_dma.bytesused += buf->bytesused;
+		list_for_each(p, &s->q_dma.list) {
+			buf = list_entry(p, struct ivtv_buffer, list);
+
+			/* Parse and Groom VBI Data */
+			s->q_dma.bytesused -= buf->bytesused;
+			ivtv_process_vbi_data(itv, buf, 0, s->type);
+			s->q_dma.bytesused += buf->bytesused;
+		}
 		if (s->id == -1) {
 			ivtv_queue_move(s, &s->q_dma, NULL, &s->q_free, 0);
 			return;
@@ -351,10 +368,14 @@
 	struct ivtv_stream *s_vbi = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI];
 	int i;
 
+	IVTV_DEBUG_DMA("start %s for %s\n", ivtv_use_dma(s) ? "DMA" : "PIO", s->name);
+
 	if (s->q_predma.bytesused)
 		ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused);
-	IVTV_DEBUG_DMA("start DMA for %s\n", s->name);
-	s->SGarray[s->SG_length - 1].size = cpu_to_le32(le32_to_cpu(s->SGarray[s->SG_length - 1].size) + 256);
+
+	if (ivtv_use_dma(s))
+		s->SGarray[s->SG_length - 1].size =
+			cpu_to_le32(le32_to_cpu(s->SGarray[s->SG_length - 1].size) + 256);
 
 	/* If this is an MPEG stream, and VBI data is also pending, then append the
 	   VBI DMA to the MPEG DMA and transfer both sets of data at once.
@@ -368,7 +389,8 @@
 	if (s->type == IVTV_ENC_STREAM_TYPE_MPG && s_vbi->SG_length &&
 			s->SG_length + s_vbi->SG_length <= s->buffers) {
 		ivtv_queue_move(s_vbi, &s_vbi->q_predma, NULL, &s_vbi->q_dma, s_vbi->q_predma.bytesused);
-		s_vbi->SGarray[s_vbi->SG_length - 1].size = cpu_to_le32(le32_to_cpu(s_vbi->SGarray[s->SG_length - 1].size) + 256);
+		if (ivtv_use_dma(s_vbi))
+			s_vbi->SGarray[s_vbi->SG_length - 1].size = cpu_to_le32(le32_to_cpu(s_vbi->SGarray[s->SG_length - 1].size) + 256);
 		for (i = 0; i < s_vbi->SG_length; i++) {
 			s->SGarray[s->SG_length++] = s_vbi->SGarray[i];
 		}
@@ -381,14 +403,26 @@
 	/* Mark last buffer size for Interrupt flag */
 	s->SGarray[s->SG_length - 1].size |= cpu_to_le32(0x80000000);
 
-	/* Sync Hardware SG List of buffers */
-	ivtv_stream_sync_for_device(s);
-	write_reg(s->SG_handle, IVTV_REG_ENCDMAADDR);
-	write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x02, IVTV_REG_DMAXFER);
-	set_bit(IVTV_F_I_DMA, &itv->i_flags);
-	itv->cur_dma_stream = s->type;
-	itv->dma_timer.expires = jiffies + HZ / 10;
-	add_timer(&itv->dma_timer);
+	if (ivtv_use_pio(s)) {
+		for (i = 0; i < s->SG_length; i++) {
+			s->PIOarray[i].src = le32_to_cpu(s->SGarray[i].src);
+			s->PIOarray[i].size = le32_to_cpu(s->SGarray[i].size);
+		}
+		set_bit(IVTV_F_I_WORK_HANDLER_PIO, &itv->i_flags);
+		set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags);
+		set_bit(IVTV_F_I_PIO, &itv->i_flags);
+		itv->cur_pio_stream = s->type;
+	}
+	else {
+		/* Sync Hardware SG List of buffers */
+		ivtv_stream_sync_for_device(s);
+		write_reg(s->SG_handle, IVTV_REG_ENCDMAADDR);
+		write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x02, IVTV_REG_DMAXFER);
+		set_bit(IVTV_F_I_DMA, &itv->i_flags);
+		itv->cur_dma_stream = s->type;
+		itv->dma_timer.expires = jiffies + HZ / 10;
+		add_timer(&itv->dma_timer);
+	}
 }
 
 static void ivtv_dma_dec_start(struct ivtv_stream *s)
@@ -489,6 +523,40 @@
 	wake_up(&itv->dma_waitq);
 }
 
+static void ivtv_irq_enc_pio_complete(struct ivtv *itv)
+{
+	struct ivtv_stream *s;
+
+	if (itv->cur_pio_stream < 0 || itv->cur_pio_stream >= IVTV_MAX_STREAMS) {
+		itv->cur_pio_stream = -1;
+		return;
+	}
+	s = &itv->streams[itv->cur_pio_stream];
+	IVTV_DEBUG_IRQ("ENC PIO COMPLETE %s\n", s->name);
+	s->SG_length = 0;
+	clear_bit(IVTV_F_I_ENC_VBI, &itv->i_flags);
+	clear_bit(IVTV_F_I_PIO, &itv->i_flags);
+	itv->cur_pio_stream = -1;
+	dma_post(s);
+	if (s->type == IVTV_ENC_STREAM_TYPE_MPG)
+		ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 0);
+	else if (s->type == IVTV_ENC_STREAM_TYPE_YUV)
+		ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 1);
+	else if (s->type == IVTV_ENC_STREAM_TYPE_PCM)
+		ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, 2);
+	clear_bit(IVTV_F_I_PIO, &itv->i_flags);
+	if (test_and_clear_bit(IVTV_F_S_DMA_HAS_VBI, &s->s_flags)) {
+		u32 tmp;
+
+		s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI];
+		tmp = s->dma_offset;
+		s->dma_offset = itv->vbi.dma_offset;
+		dma_post(s);
+		s->dma_offset = tmp;
+	}
+	wake_up(&itv->dma_waitq);
+}
+
 static void ivtv_irq_dma_err(struct ivtv *itv)
 {
 	u32 data[CX2341X_MBOX_MAX_DATA];
@@ -532,13 +600,7 @@
 	clear_bit(IVTV_F_I_ENC_VBI, &itv->i_flags);
 	s = &itv->streams[ivtv_stream_map[data[0]]];
 	if (!stream_enc_dma_append(s, data)) {
-		if (ivtv_use_pio(s)) {
-			dma_post(s);
-			ivtv_vapi(itv, CX2341X_ENC_SCHED_DMA_TO_HOST, 3, 0, 0, data[0]);
-		}
-		else {
-			set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags);
-		}
+		set_bit(ivtv_use_pio(s) ? IVTV_F_S_PIO_PENDING : IVTV_F_S_DMA_PENDING, &s->s_flags);
 	}
 }
 
@@ -551,15 +613,6 @@
 	IVTV_DEBUG_IRQ("ENC START VBI CAP\n");
 	s = &itv->streams[IVTV_ENC_STREAM_TYPE_VBI];
 
-	if (ivtv_use_pio(s)) {
-		if (stream_enc_dma_append(s, data))
-			return;
-		if (s->q_predma.bytesused)
-			ivtv_queue_move(s, &s->q_predma, NULL, &s->q_dma, s->q_predma.bytesused);
-		s->SG_length = 0;
-		dma_post(s);
-		return;
-	}
 	/* If more than two VBI buffers are pending, then
 	   clear the old ones and start with this new one.
 	   This can happen during transition stages when MPEG capturing is
@@ -582,11 +635,11 @@
 	if (!stream_enc_dma_append(s, data) &&
 			!test_bit(IVTV_F_S_STREAMING, &s_mpg->s_flags)) {
 		set_bit(IVTV_F_I_ENC_VBI, &itv->i_flags);
-		set_bit(IVTV_F_S_DMA_PENDING, &s->s_flags);
+		set_bit(ivtv_use_pio(s) ? IVTV_F_S_PIO_PENDING : IVTV_F_S_DMA_PENDING, &s->s_flags);
 	}
 }
 
-static void ivtv_irq_dev_vbi_reinsert(struct ivtv *itv)
+static void ivtv_irq_dec_vbi_reinsert(struct ivtv *itv)
 {
 	u32 data[CX2341X_MBOX_MAX_DATA];
 	struct ivtv_stream *s = &itv->streams[IVTV_DEC_STREAM_TYPE_VBI];
@@ -594,7 +647,7 @@
 	IVTV_DEBUG_IRQ("DEC VBI REINSERT\n");
 	if (test_bit(IVTV_F_S_CLAIMED, &s->s_flags) &&
 			!stream_enc_dma_append(s, data)) {
-		dma_post(s);
+		set_bit(IVTV_F_S_PIO_PENDING, &s->s_flags);
 	}
 }
 
@@ -657,7 +710,6 @@
 	}
 	if (frame != (itv->lastVsyncFrame & 1)) {
 		struct ivtv_stream *s = ivtv_get_output_stream(itv);
-		int work = 0;
 
 		itv->lastVsyncFrame += 1;
 		if (frame == 0) {
@@ -678,7 +730,7 @@
 		/* Send VBI to saa7127 */
 		if (frame) {
 			set_bit(IVTV_F_I_WORK_HANDLER_VBI, &itv->i_flags);
-			work = 1;
+			set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags);
 		}
 
 		/* Check if we need to update the yuv registers */
@@ -691,11 +743,9 @@
 				itv->yuv_info.new_frame_info[last_dma_frame].update = 0;
 				itv->yuv_info.yuv_forced_update = 0;
 				set_bit(IVTV_F_I_WORK_HANDLER_YUV, &itv->i_flags);
-				work = 1;
+				set_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags);
 			}
 		}
-		if (work)
-			queue_work(itv->irq_work_queues, &itv->irq_work_queue);
 	}
 }
 
@@ -755,6 +805,10 @@
 		ivtv_irq_enc_dma_complete(itv);
 	}
 
+	if (combo & IVTV_IRQ_ENC_PIO_COMPLETE) {
+		ivtv_irq_enc_pio_complete(itv);
+	}
+
 	if (combo & IVTV_IRQ_DMA_ERR) {
 		ivtv_irq_dma_err(itv);
 	}
@@ -768,7 +822,7 @@
 	}
 
 	if (combo & IVTV_IRQ_DEC_VBI_RE_INSERT) {
-		ivtv_irq_dev_vbi_reinsert(itv);
+		ivtv_irq_dec_vbi_reinsert(itv);
 	}
 
 	if (combo & IVTV_IRQ_ENC_EOS) {
@@ -813,6 +867,22 @@
 		}
 	}
 
+	if ((combo & IVTV_IRQ_DMA) && !test_bit(IVTV_F_I_PIO, &itv->i_flags)) {
+		for (i = 0; i < IVTV_MAX_STREAMS; i++) {
+			int idx = (i + itv->irq_rr_idx++) % IVTV_MAX_STREAMS;
+			struct ivtv_stream *s = &itv->streams[idx];
+
+			if (!test_and_clear_bit(IVTV_F_S_PIO_PENDING, &s->s_flags))
+				continue;
+			if (s->type == IVTV_DEC_STREAM_TYPE_VBI || s->type < IVTV_DEC_STREAM_TYPE_MPG)
+				ivtv_dma_enc_start(s);
+			break;
+		}
+	}
+
+	if (test_and_clear_bit(IVTV_F_I_HAVE_WORK, &itv->i_flags))
+		queue_work(itv->irq_work_queues, &itv->irq_work_queue);
+
 	spin_unlock(&itv->dma_reg_lock);
 
 	/* If we've just handled a 'forced' vsync, it's safest to say it
diff --git a/drivers/media/video/ivtv/ivtv-queue.c b/drivers/media/video/ivtv/ivtv-queue.c
index ccfcef1..a04f938 100644
--- a/drivers/media/video/ivtv/ivtv-queue.c
+++ b/drivers/media/video/ivtv/ivtv-queue.c
@@ -195,14 +195,26 @@
 		s->dma != PCI_DMA_NONE ? "DMA " : "",
 		s->name, s->buffers, s->buf_size, s->buffers * s->buf_size / 1024);
 
-	/* Allocate DMA SG Arrays */
-	if (s->dma != PCI_DMA_NONE) {
-		s->SGarray = (struct ivtv_SG_element *)kzalloc(SGsize, GFP_KERNEL);
-		if (s->SGarray == NULL) {
-			IVTV_ERR("Could not allocate SGarray for %s stream\n", s->name);
+	if (ivtv_might_use_pio(s)) {
+		s->PIOarray = (struct ivtv_SG_element *)kzalloc(SGsize, GFP_KERNEL);
+		if (s->PIOarray == NULL) {
+			IVTV_ERR("Could not allocate PIOarray for %s stream\n", s->name);
 			return -ENOMEM;
 		}
-		s->SG_length = 0;
+	}
+
+	/* Allocate DMA SG Arrays */
+	s->SGarray = (struct ivtv_SG_element *)kzalloc(SGsize, GFP_KERNEL);
+	if (s->SGarray == NULL) {
+		IVTV_ERR("Could not allocate SGarray for %s stream\n", s->name);
+		if (ivtv_might_use_pio(s)) {
+			kfree(s->PIOarray);
+			s->PIOarray = NULL;
+		}
+		return -ENOMEM;
+	}
+	s->SG_length = 0;
+	if (ivtv_might_use_dma(s)) {
 		s->SG_handle = pci_map_single(itv->dev, s->SGarray, SGsize, s->dma);
 		ivtv_stream_sync_for_cpu(s);
 	}
@@ -219,7 +231,7 @@
 			break;
 		}
 		INIT_LIST_HEAD(&buf->list);
-		if (s->dma != PCI_DMA_NONE) {
+		if (ivtv_might_use_dma(s)) {
 			buf->dma_handle = pci_map_single(s->itv->dev,
 				buf->buf, s->buf_size + 256, s->dma);
 			ivtv_buf_sync_for_cpu(s, buf);
@@ -242,7 +254,7 @@
 
 	/* empty q_free */
 	while ((buf = ivtv_dequeue(s, &s->q_free))) {
-		if (s->dma != PCI_DMA_NONE)
+		if (ivtv_might_use_dma(s))
 			pci_unmap_single(s->itv->dev, buf->dma_handle,
 				s->buf_size + 256, s->dma);
 		kfree(buf->buf);
@@ -256,6 +268,9 @@
 				 sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE);
 			s->SG_handle = IVTV_DMA_UNMAPPED;
 		}
+		kfree(s->SGarray);
+		kfree(s->PIOarray);
+		s->PIOarray = NULL;
 		s->SGarray = NULL;
 		s->SG_length = 0;
 	}
diff --git a/drivers/media/video/ivtv/ivtv-queue.h b/drivers/media/video/ivtv/ivtv-queue.h
index 903edd4..2ed8d54 100644
--- a/drivers/media/video/ivtv/ivtv-queue.h
+++ b/drivers/media/video/ivtv/ivtv-queue.h
@@ -20,18 +20,43 @@
  */
 
 #define IVTV_DMA_UNMAPPED	((u32) -1)
+#define SLICED_VBI_PIO 1
 
 /* ivtv_buffer utility functions */
+
+static inline int ivtv_might_use_pio(struct ivtv_stream *s)
+{
+	return s->dma == PCI_DMA_NONE || (SLICED_VBI_PIO && s->type == IVTV_ENC_STREAM_TYPE_VBI);
+}
+
+static inline int ivtv_use_pio(struct ivtv_stream *s)
+{
+	struct ivtv *itv = s->itv;
+
+	return s->dma == PCI_DMA_NONE ||
+	    (SLICED_VBI_PIO && s->type == IVTV_ENC_STREAM_TYPE_VBI && itv->vbi.sliced_in->service_set);
+}
+
+static inline int ivtv_might_use_dma(struct ivtv_stream *s)
+{
+	return s->dma != PCI_DMA_NONE;
+}
+
+static inline int ivtv_use_dma(struct ivtv_stream *s)
+{
+	return !ivtv_use_pio(s);
+}
+
 static inline void ivtv_buf_sync_for_cpu(struct ivtv_stream *s, struct ivtv_buffer *buf)
 {
-	if (s->dma != PCI_DMA_NONE)
+	if (ivtv_use_dma(s))
 		pci_dma_sync_single_for_cpu(s->itv->dev, buf->dma_handle,
 				s->buf_size + 256, s->dma);
 }
 
 static inline void ivtv_buf_sync_for_device(struct ivtv_stream *s, struct ivtv_buffer *buf)
 {
-	if (s->dma != PCI_DMA_NONE)
+	if (ivtv_use_dma(s))
 		pci_dma_sync_single_for_device(s->itv->dev, buf->dma_handle,
 				s->buf_size + 256, s->dma);
 }
@@ -53,12 +78,14 @@
 
 static inline void ivtv_stream_sync_for_cpu(struct ivtv_stream *s)
 {
-	pci_dma_sync_single_for_cpu(s->itv->dev, s->SG_handle,
-		sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE);
+	if (ivtv_use_dma(s))
+		pci_dma_sync_single_for_cpu(s->itv->dev, s->SG_handle,
+			sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE);
 }
 
 static inline void ivtv_stream_sync_for_device(struct ivtv_stream *s)
 {
-	pci_dma_sync_single_for_device(s->itv->dev, s->SG_handle,
-		sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE);
+	if (ivtv_use_dma(s))
+		pci_dma_sync_single_for_device(s->itv->dev, s->SG_handle,
+			sizeof(struct ivtv_SG_element) * s->buffers, PCI_DMA_TODEVICE);
 }
diff --git a/drivers/media/video/ivtv/ivtv-vbi.c b/drivers/media/video/ivtv/ivtv-vbi.c
index 5efa5a8..3ba46e0 100644
--- a/drivers/media/video/ivtv/ivtv-vbi.c
+++ b/drivers/media/video/ivtv/ivtv-vbi.c
@@ -450,7 +450,7 @@
 }
 
 
-void vbi_work_handler(struct ivtv *itv)
+void ivtv_vbi_work_handler(struct ivtv *itv)
 {
 	struct v4l2_sliced_vbi_data data;
 
diff --git a/drivers/media/video/ivtv/ivtv-vbi.h b/drivers/media/video/ivtv/ivtv-vbi.h
index cdaea69..ec211b4 100644
--- a/drivers/media/video/ivtv/ivtv-vbi.h
+++ b/drivers/media/video/ivtv/ivtv-vbi.h
@@ -23,4 +23,4 @@
 int ivtv_used_line(struct ivtv *itv, int line, int field);
 void ivtv_disable_vbi(struct ivtv *itv);
 void ivtv_set_vbi(unsigned long arg);
-void vbi_work_handler(struct ivtv *itv);
+void ivtv_vbi_work_handler(struct ivtv *itv);