Commit 37f5aefd authored by Robert Jarzmik's avatar Robert Jarzmik Committed by Mauro Carvalho Chehab
Browse files

V4L/DVB (11320): pxa_camera: Remove YUV planar formats hole



All planes were PAGE aligned (ie. 4096 bytes aligned). This
is not consistent with YUV422 format, which requires Y, U
and V planes glued together.  The new implementation forces
the alignement on 8 bytes (DMA requirement), which is almost
always the case (granted by width x height being a multiple
of 8).

The test cases include tests in both YUV422 and RGB565 :
 - a picture of size 111 x 111 (cross RAM pages example)
 - a picture of size 1023 x 4 in (under 1 RAM page)
 - a picture of size 1024 x 4 in (exactly 1 RAM page)
 - a picture of size 1025 x 4 in (over 1 RAM page)
 - a picture of size 1280 x 1024 (many RAM pages)

Signed-off-by: default avatarRobert Jarzmik <robert.jarzmik@free.fr>
Signed-off-by: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 92a8337b
Loading
Loading
Loading
Loading
+105 −38
Original line number Diff line number Diff line
@@ -287,19 +287,63 @@ static void free_buffer(struct videobuf_queue *vq, struct pxa_buffer *buf)
	buf->vb.state = VIDEOBUF_NEEDS_INIT;
}

static int calculate_dma_sglen(struct scatterlist *sglist, int sglen,
			       int sg_first_ofs, int size)
{
	int i, offset, dma_len, xfer_len;
	struct scatterlist *sg;

	offset = sg_first_ofs;
	for_each_sg(sglist, sg, sglen, i) {
		dma_len = sg_dma_len(sg);

		/* PXA27x Developer's Manual 27.4.4.1: round up to 8 bytes */
		xfer_len = roundup(min(dma_len - offset, size), 8);

		size = max(0, size - xfer_len);
		offset = 0;
		if (size == 0)
			break;
	}

	BUG_ON(size != 0);
	return i + 1;
}

/**
 * pxa_init_dma_channel - init dma descriptors
 * @pcdev: pxa camera device
 * @buf: pxa buffer to find pxa dma channel
 * @dma: dma video buffer
 * @channel: dma channel (0 => 'Y', 1 => 'U', 2 => 'V')
 * @cibr: camera Receive Buffer Register
 * @size: bytes to transfer
 * @sg_first: first element of sg_list
 * @sg_first_ofs: offset in first element of sg_list
 *
 * Prepares the pxa dma descriptors to transfer one camera channel.
 * Beware sg_first and sg_first_ofs are both input and output parameters.
 *
 * Returns 0 or -ENOMEM if no coherent memory is available
 */
static int pxa_init_dma_channel(struct pxa_camera_dev *pcdev,
				struct pxa_buffer *buf,
				struct videobuf_dmabuf *dma, int channel,
				int sglen, int sg_start, int cibr,
				unsigned int size)
				int cibr, int size,
				struct scatterlist **sg_first, int *sg_first_ofs)
{
	struct pxa_cam_dma *pxa_dma = &buf->dmas[channel];
	int i;
	struct scatterlist *sg;
	int i, offset, sglen;
	int dma_len = 0, xfer_len = 0;

	if (pxa_dma->sg_cpu)
		dma_free_coherent(pcdev->dev, pxa_dma->sg_size,
				  pxa_dma->sg_cpu, pxa_dma->sg_dma);

	sglen = calculate_dma_sglen(*sg_first, dma->sglen,
				    *sg_first_ofs, size);

	pxa_dma->sg_size = (sglen + 1) * sizeof(struct pxa_dma_desc);
	pxa_dma->sg_cpu = dma_alloc_coherent(pcdev->dev, pxa_dma->sg_size,
					     &pxa_dma->sg_dma, GFP_KERNEL);
@@ -307,28 +351,54 @@ static int pxa_init_dma_channel(struct pxa_camera_dev *pcdev,
		return -ENOMEM;

	pxa_dma->sglen = sglen;
	offset = *sg_first_ofs;

	for (i = 0; i < sglen; i++) {
		int sg_i = sg_start + i;
		struct scatterlist *sg = dma->sglist;
		unsigned int dma_len = sg_dma_len(&sg[sg_i]), xfer_len;
	dev_dbg(pcdev->dev, "DMA: sg_first=%p, sglen=%d, ofs=%d, dma.desc=%x\n",
		*sg_first, sglen, *sg_first_ofs, pxa_dma->sg_dma);

		pxa_dma->sg_cpu[i].dsadr = pcdev->res->start + cibr;
		pxa_dma->sg_cpu[i].dtadr = sg_dma_address(&sg[sg_i]);

	for_each_sg(*sg_first, sg, sglen, i) {
		dma_len = sg_dma_len(sg);

		/* PXA27x Developer's Manual 27.4.4.1: round up to 8 bytes */
		xfer_len = (min(dma_len, size) + 7) & ~7;
		xfer_len = roundup(min(dma_len - offset, size), 8);

		size = max(0, size - xfer_len);

		pxa_dma->sg_cpu[i].dsadr = pcdev->res->start + cibr;
		pxa_dma->sg_cpu[i].dtadr = sg_dma_address(sg) + offset;
		pxa_dma->sg_cpu[i].dcmd =
			DCMD_FLOWSRC | DCMD_BURST8 | DCMD_INCTRGADDR | xfer_len;
		size -= dma_len;
		pxa_dma->sg_cpu[i].ddadr =
			pxa_dma->sg_dma + (i + 1) * sizeof(struct pxa_dma_desc);

		dev_vdbg(pcdev->dev, "DMA: desc.%08x->@phys=0x%08x, len=%d\n",
			 pxa_dma->sg_dma + i * sizeof(struct pxa_dma_desc),
			 sg_dma_address(sg) + offset, xfer_len);
		offset = 0;

		if (size == 0)
			break;
	}

	pxa_dma->sg_cpu[sglen - 1].ddadr = DDADR_STOP;
	pxa_dma->sg_cpu[sglen - 1].dcmd |= DCMD_ENDIRQEN;

	/*
	 * Handle 1 special case :
	 *  - in 3 planes (YUV422P format), we might finish with xfer_len equal
	 *    to dma_len (end on PAGE boundary). In this case, the sg element
	 *    for next plane should be the next after the last used to store the
	 *    last scatter gather RAM page
	 */
	if (xfer_len >= dma_len) {
		*sg_first_ofs = xfer_len - dma_len;
		*sg_first = sg_next(sg);
	} else {
		*sg_first_ofs = xfer_len;
		*sg_first = sg;
	}

	return 0;
}

@@ -340,7 +410,6 @@ static int pxa_videobuf_prepare(struct videobuf_queue *vq,
	struct pxa_camera_dev *pcdev = ici->priv;
	struct pxa_buffer *buf = container_of(vb, struct pxa_buffer, vb);
	int ret;
	int sglen_y,  sglen_yu = 0, sglen_u = 0, sglen_v = 0;
	int size_y, size_u = 0, size_v = 0;

	dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__,
@@ -379,39 +448,37 @@ static int pxa_videobuf_prepare(struct videobuf_queue *vq,
	}

	if (vb->state == VIDEOBUF_NEEDS_INIT) {
		unsigned int size = vb->size;
		int size = vb->size;
		int next_ofs = 0;
		struct videobuf_dmabuf *dma = videobuf_to_dma(vb);
		struct scatterlist *sg;

		ret = videobuf_iolock(vq, vb, NULL);
		if (ret)
			goto fail;

		if (pcdev->channels == 3) {
			/* FIXME the calculations should be more precise */
			sglen_y = dma->sglen / 2;
			sglen_u = sglen_v = dma->sglen / 4 + 1;
			sglen_yu = sglen_y + sglen_u;
			size_y = size / 2;
			size_u = size_v = size / 4;
		} else {
			sglen_y = dma->sglen;
			size_y = size;
		}

		/* init DMA for Y channel */
		ret = pxa_init_dma_channel(pcdev, buf, dma, 0, sglen_y,
					   0, 0x28, size_y);
		sg = dma->sglist;

		/* init DMA for Y channel */
		ret = pxa_init_dma_channel(pcdev, buf, dma, 0, CIBR0, size_y,
					   &sg, &next_ofs);
		if (ret) {
			dev_err(pcdev->dev,
				"DMA initialization for Y/RGB failed\n");
			goto fail;
		}

		if (pcdev->channels == 3) {
		/* init DMA for U channel */
			ret = pxa_init_dma_channel(pcdev, buf, dma, 1, sglen_u,
						   sglen_y, 0x30, size_u);
		if (size_u)
			ret = pxa_init_dma_channel(pcdev, buf, dma, 1, CIBR1,
						   size_u, &sg, &next_ofs);
		if (ret) {
			dev_err(pcdev->dev,
				"DMA initialization for U failed\n");
@@ -419,14 +486,14 @@ static int pxa_videobuf_prepare(struct videobuf_queue *vq,
		}

		/* init DMA for V channel */
			ret = pxa_init_dma_channel(pcdev, buf, dma, 2, sglen_v,
						   sglen_yu, 0x38, size_v);
		if (size_v)
			ret = pxa_init_dma_channel(pcdev, buf, dma, 2, CIBR2,
						   size_v, &sg, &next_ofs);
		if (ret) {
			dev_err(pcdev->dev,
				"DMA initialization for V failed\n");
			goto fail_v;
		}
		}

		vb->state = VIDEOBUF_PREPARED;
	}