All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Richard Röjfors" <richard.rojfors.ext@mocean-labs.com>
To: linux-kernel@vger.kernel.org
Cc: Andrew Morton <akpm@linux-foundation.org>, linux-media@vger.kernel.org
Subject: [PATCH 5/9] V4L2: Added Timberdale Logiwin driver
Date: Fri, 05 Jun 2009 15:40:55 +0200	[thread overview]
Message-ID: <4A292067.1070405@mocean-labs.com> (raw)

V4L2 video capture driver for the logiwin IP on the Timberdale FPGA.

The driver uses the Timberdale DMA engine

Signed-off-by: Richard Röjfors <richard.rojfors.ext@mocean-labs.com>
---
Index: linux-2.6.30-rc7/drivers/media/video/timblogiw.c
===================================================================
--- linux-2.6.30-rc7/drivers/media/video/timblogiw.c	(revision 0)
+++ linux-2.6.30-rc7/drivers/media/video/timblogiw.c	(revision 867)
@@ -0,0 +1,949 @@
+/*
+ * timblogiw.c timberdale FPGA LogiWin Video In driver
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Supports:
+ * Timberdale FPGA LogiWin Video In
+ */
+
+#include <linux/list.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include "timblogiw.h"
+#include <linux/mfd/timbdma.h>
+#include <linux/i2c.h>
+#include <media/timb_video.h>
+
+#define TIMBLOGIW_CTRL 0x40
+
+#define TIMBLOGIW_H_SCALE 0x20
+#define TIMBLOGIW_V_SCALE 0x28
+
+#define TIMBLOGIW_X_CROP 0x58
+#define TIMBLOGIW_Y_CROP 0x60
+
+#define TIMBLOGIW_W_CROP 0x00
+#define TIMBLOGIW_H_CROP 0x08
+
+#define TIMBLOGIW_VERSION_CODE 0x02
+
+#define TIMBLOGIW_BUF	0x04
+#define TIMBLOGIW_TBI	0x2c
+#define TIMBLOGIW_BPL	0x30
+
+#define dbg(...)
+
+#define DMA_BUFFER_SIZE (720 * 576 * 2)
+
+const struct timblogiw_tvnorm timblogiw_tvnorms[] = {
+	{
+		.std			= V4L2_STD_PAL,
+		.name			= "PAL",
+		.width			= 720,
+		.height			= 576
+	},
+	{
+		.std			= V4L2_STD_NTSC_M,
+		.name			= "NTSC",
+		.width			= 720,
+		.height			= 480
+	}
+};
+
+static int timblogiw_bytes_per_line(const struct timblogiw_tvnorm *norm)
+{
+	return norm->width * 2;
+}
+
+
+static int timblogiw_frame_size(const struct timblogiw_tvnorm *norm)
+{
+	return norm->height * timblogiw_bytes_per_line(norm);
+}
+
+static const struct timblogiw_tvnorm *timblogiw_get_norm(const v4l2_std_id std)
+{
+	int i;
+	for (i = 0; i < ARRAY_SIZE(timblogiw_tvnorms); i++)
+		if (timblogiw_tvnorms[i].std == std)
+			return timblogiw_tvnorms + i;
+
+	/* default to first element */
+	return timblogiw_tvnorms;
+}
+
+static void timblogiw_handleframe(unsigned long arg)
+{
+	struct timblogiw_frame *f;
+	struct timblogiw *lw = (struct timblogiw *)arg;
+
+	spin_lock_bh(&lw->queue_lock);
+	if (lw->dma.filled && !list_empty(&lw->inqueue)) {
+		/* put the entry in the outqueue */
+		f = list_entry(lw->inqueue.next, struct timblogiw_frame, frame);
+
+		/* copy data from the DMA buffer */
+		memcpy(f->bufmem, lw->dma.filled->buf, f->buf.length);
+		/* buffer consumed */
+		lw->dma.filled = NULL;
+
+		do_gettimeofday(&f->buf.timestamp);
+		f->buf.sequence = ++lw->frame_count;
+		f->buf.field = V4L2_FIELD_NONE;
+		f->state = F_DONE;
+		f->buf.bytesused = f->buf.length;
+		list_move_tail(&f->frame, &lw->outqueue);
+		/* wake up any waiter */
+		wake_up(&lw->wait_frame);
+	} else {
+		/* No user buffer available, consume buffer anyway
+		 * who wants an old video frame?
+		 */
+		lw->dma.filled = NULL;
+	}
+	spin_unlock_bh(&lw->queue_lock);
+}
+
+static int timblogiw_isr(u32 flag, void *pdev)
+{
+	struct timblogiw *lw = (struct timblogiw *)pdev;
+
+	if (!lw->dma.filled && (flag & DMA_IRQ_VIDEO_RX)) {
+		/* Got a frame, store it, and flip to next DMA buffer */
+		lw->dma.filled = lw->dma.transfer + lw->dma.curr;
+		lw->dma.curr = !lw->dma.curr;
+	}
+
+	if (lw->stream == STREAM_ON)
+		timb_start_dma(DMA_IRQ_VIDEO_RX,
+			lw->dma.transfer[lw->dma.curr].handle,
+			timblogiw_frame_size(lw->cur_norm),
+			timblogiw_bytes_per_line(lw->cur_norm));
+
+	if (flag & DMA_IRQ_VIDEO_DROP)
+		dbg("%s: frame dropped\n", __func__);
+	if (flag & DMA_IRQ_VIDEO_RX) {
+		dbg("%s: frame RX\n", __func__);
+		tasklet_schedule(&lw->tasklet);
+	}
+	return 0;
+}
+
+static void timblogiw_empty_framequeues(struct timblogiw *lw)
+{
+	u32 i;
+
+	dbg("%s\n", __func__);
+
+	INIT_LIST_HEAD(&lw->inqueue);
+	INIT_LIST_HEAD(&lw->outqueue);
+
+	for (i = 0; i < lw->num_frames; i++) {
+		lw->frame[i].state = F_UNUSED;
+		lw->frame[i].buf.bytesused = 0;
+	}
+}
+
+u32 timblogiw_request_buffers(struct timblogiw *lw, u32 count)
+{
+	/* needs to be page aligned cause the */
+	/* buffers can be mapped individually! */
+	const size_t imagesize = PAGE_ALIGN(timblogiw_frame_size(lw->cur_norm));
+	void *buff = NULL;
+	u32 i;
+
+	dbg("%s - request of %i buffers of size %zi\n",
+		__func__, count, imagesize);
+
+	lw->dma.transfer[0].buf = pci_alloc_consistent(lw->dev, DMA_BUFFER_SIZE,
+		&lw->dma.transfer[0].handle);
+	lw->dma.transfer[1].buf = pci_alloc_consistent(lw->dev, DMA_BUFFER_SIZE,
+		&lw->dma.transfer[1].handle);
+	if ((lw->dma.transfer[0].buf == NULL) ||
+		(lw->dma.transfer[1].buf == NULL)) {
+		printk(KERN_ALERT "alloc failed\n");
+		if (lw->dma.transfer[0].buf != NULL)
+			pci_free_consistent(lw->dev, DMA_BUFFER_SIZE,
+				lw->dma.transfer[0].buf,
+				lw->dma.transfer[0].handle);
+		if (lw->dma.transfer[1].buf != NULL)
+			pci_free_consistent(lw->dev, DMA_BUFFER_SIZE,
+				lw->dma.transfer[1].buf,
+				lw->dma.transfer[1].handle);
+		return 0;
+	}
+
+	if (count > TIMBLOGIW_NUM_FRAMES)
+		count = TIMBLOGIW_NUM_FRAMES;
+
+	lw->num_frames = count;
+	while (lw->num_frames > 0) {
+		buff = vmalloc_32(lw->num_frames * imagesize);
+		if (buff) {
+			memset(buff, 0, lw->num_frames * imagesize);
+			break;
+		}
+		lw->num_frames--;
+	}
+
+	for (i = 0; i < lw->num_frames; i++) {
+		lw->frame[i].bufmem = buff + i * imagesize;
+		lw->frame[i].buf.index = i;
+		lw->frame[i].buf.m.offset = i * imagesize;
+		lw->frame[i].buf.length = timblogiw_frame_size(lw->cur_norm);
+		lw->frame[i].buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		lw->frame[i].buf.sequence = 0;
+		lw->frame[i].buf.field = V4L2_FIELD_NONE;
+		lw->frame[i].buf.memory = V4L2_MEMORY_MMAP;
+		lw->frame[i].buf.flags = 0;
+	}
+
+	lw->dma.curr = 0;
+	lw->dma.filled = NULL;
+	return lw->num_frames;
+}
+
+void timblogiw_release_buffers(struct timblogiw *lw)
+{
+	dbg("%s\n", __func__);
+
+	if (lw->frame[0].bufmem != NULL) {
+		vfree(lw->frame[0].bufmem);
+		lw->frame[0].bufmem = NULL;
+		lw->num_frames = TIMBLOGIW_NUM_FRAMES;
+		pci_free_consistent(lw->dev, DMA_BUFFER_SIZE,
+			lw->dma.transfer[0].buf, lw->dma.transfer[0].handle);
+		pci_free_consistent(lw->dev, DMA_BUFFER_SIZE,
+			lw->dma.transfer[1].buf, lw->dma.transfer[1].handle);
+	}
+}
+
+/* IOCTL functions */
+
+static int timblogiw_g_fmt(struct file *file, void  *priv,
+	struct v4l2_format *format)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s\n",  __func__);
+
+	if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	format->fmt.pix.width = lw->cur_norm->width;
+	format->fmt.pix.height = lw->cur_norm->height;
+	format->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+	format->fmt.pix.bytesperline = timblogiw_bytes_per_line(lw->cur_norm);
+	format->fmt.pix.sizeimage = timblogiw_frame_size(lw->cur_norm);
+	format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	format->fmt.pix.field = V4L2_FIELD_NONE;
+	return 0;
+}
+
+static int timblogiw_try_fmt(struct file *file, void  *priv,
+	struct v4l2_format *format)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+	struct v4l2_pix_format *pix = &format->fmt.pix;
+
+	dbg("%s - width=%d, height=%d, pixelformat=%d, field=%d\n"
+		"bytes per line %d, size image: %d, colorspace: %d\n",
+		__func__,
+		pix->width, pix->height, pix->pixelformat, pix->field,
+		pix->bytesperline, pix->sizeimage, pix->colorspace);
+
+	if (format->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (format->fmt.pix.field != V4L2_FIELD_NONE)
+		return -EINVAL;
+
+	if ((lw->cur_norm->height != pix->height) ||
+		(lw->cur_norm->width != pix->width)) {
+		pix->width = lw->cur_norm->width;
+		pix->height = lw->cur_norm->height;
+	}
+
+	return 0;
+}
+
+static int timblogiw_querycap(struct file *file, void  *priv,
+	struct v4l2_capability *cap)
+{
+	dbg("%s\n",  __func__);
+	memset(cap, 0, sizeof(*cap));
+	strncpy(cap->card, "Timberdale Video", sizeof(cap->card)-1);
+	strncpy(cap->driver, "Timblogiw", sizeof(cap->card)-1);
+	cap->version = TIMBLOGIW_VERSION_CODE;
+	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE |
+		V4L2_CAP_STREAMING;
+
+	return 0;
+}
+
+static int timblogiw_enum_fmt(struct file *file, void  *priv,
+	struct v4l2_fmtdesc *fmt)
+{
+	dbg("%s, index: %d\n",  __func__, fmt->index);
+
+	if (fmt->index != 0)
+		return -EINVAL;
+	memset(fmt, 0, sizeof(*fmt));
+	fmt->index = 0;
+	fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	strncpy(fmt->description, "4:2:2, packed, YUYV",
+		sizeof(fmt->description)-1);
+	fmt->pixelformat = V4L2_PIX_FMT_YUYV;
+	memset(fmt->reserved, 0, sizeof(fmt->reserved));
+
+	return 0;
+}
+
+static int timblogiw_reqbufs(struct file *file, void  *priv,
+	struct v4l2_requestbuffers *rb)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s\n",  __func__);
+
+	if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+		rb->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+
+	timblogiw_empty_framequeues(lw);
+
+	timblogiw_release_buffers(lw);
+	if (rb->count)
+		rb->count = timblogiw_request_buffers(lw, rb->count);
+
+	dbg("%s - VIDIOC_REQBUFS: io method is mmap. num bufs %i\n",
+		__func__, rb->count);
+
+	return 0;
+}
+
+static int timblogiw_querybuf(struct file *file, void  *priv,
+	struct v4l2_buffer *b)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s\n",  __func__);
+
+	if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+		b->index >= lw->num_frames)
+		return -EINVAL;
+
+	memcpy(b, &lw->frame[b->index].buf, sizeof(*b));
+
+	if (lw->frame[b->index].vma_use_count)
+		b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+	if (lw->frame[b->index].state == F_DONE)
+		b->flags |= V4L2_BUF_FLAG_DONE;
+	else if (lw->frame[b->index].state != F_UNUSED)
+		b->flags |= V4L2_BUF_FLAG_QUEUED;
+
+	return 0;
+}
+
+static int timblogiw_qbuf(struct file *file, void  *priv, struct v4l2_buffer *b)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+	unsigned long lock_flags;
+
+	if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+		b->index >= lw->num_frames)
+		return -EINVAL;
+
+	if (lw->frame[b->index].state != F_UNUSED)
+		return -EAGAIN;
+
+	if (b->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+
+	lw->frame[b->index].state = F_QUEUED;
+
+	spin_lock_irqsave(&lw->queue_lock, lock_flags);
+	list_add_tail(&lw->frame[b->index].frame, &lw->inqueue);
+	spin_unlock_irqrestore(&lw->queue_lock, lock_flags);
+
+	return 0;
+}
+
+static int timblogiw_dqbuf(struct file *file, void  *priv,
+	struct v4l2_buffer *b)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+	struct timblogiw_frame *f;
+	unsigned long lock_flags;
+	int ret = 0;
+
+	if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		dbg("%s - VIDIOC_DQBUF, illegal buf type!\n",
+			__func__);
+		return -EINVAL;
+	}
+
+	if (list_empty(&lw->outqueue)) {
+		if (file->f_flags & O_NONBLOCK)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible(lw->wait_frame,
+			!list_empty(&lw->outqueue));
+		if (ret)
+			return ret;
+	}
+
+	spin_lock_irqsave(&lw->queue_lock, lock_flags);
+	f = list_entry(lw->outqueue.next,
+			struct timblogiw_frame, frame);
+	list_del(lw->outqueue.next);
+	spin_unlock_irqrestore(&lw->queue_lock, lock_flags);
+
+	f->state = F_UNUSED;
+	memcpy(b, &f->buf, sizeof(*b));
+
+	if (f->vma_use_count)
+		b->flags |= V4L2_BUF_FLAG_MAPPED;
+
+	return 0;
+}
+
+static int timblogiw_g_std(struct file *file, void  *priv, v4l2_std_id *std)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s\n",  __func__);
+
+	*std = lw->cur_norm->std;
+	return 0;
+}
+
+static int timblogiw_s_std(struct file *file, void  *priv, v4l2_std_id *std)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s\n",  __func__);
+
+	if (!(*std & lw->cur_norm->std))
+		return -EINVAL;
+	return 0;
+}
+
+static int timblogiw_enuminput(struct file *file, void  *priv,
+	struct v4l2_input *inp)
+{
+	dbg("%s\n",  __func__);
+
+	if (inp->index != 0)
+		return -EINVAL;
+
+	memset(inp, 0, sizeof(*inp));
+	inp->index = 0;
+
+	strncpy(inp->name, "Timb input 1", sizeof(inp->name) - 1);
+	inp->type = V4L2_INPUT_TYPE_CAMERA;
+	inp->std = V4L2_STD_ALL;
+
+	return 0;
+}
+
+static int timblogiw_g_input(struct file *file, void  *priv,
+	unsigned int *input)
+{
+	dbg("%s\n",  __func__);
+
+	*input = 0;
+
+	return 0;
+}
+
+static int timblogiw_s_input(struct file *file, void  *priv, unsigned int input)
+{
+	dbg("%s\n",  __func__);
+
+	if (input != 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int timblogiw_streamon(struct file *file, void  *priv, unsigned int type)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+	struct timblogiw_frame *f;
+
+	dbg("%s\n",  __func__);
+
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		dbg("%s - No capture device\n", __func__);
+		return -EINVAL;
+	}
+
+	if (list_empty(&lw->inqueue)) {
+		dbg("%s - inqueue is empty\n", __func__);
+		return -EINVAL;
+	}
+
+	if (lw->stream == STREAM_ON)
+		return 0;
+
+	lw->stream = STREAM_ON;
+
+	f = list_entry(lw->inqueue.next,
+		struct timblogiw_frame, frame);
+
+	dbg("%s - f size: %d, bpr: %d, dma addr: %x\n", __func__,
+		timblogiw_frame_size(lw->cur_norm),
+		timblogiw_bytes_per_line(lw->cur_norm),
+		(unsigned int)lw->dma.transfer[lw->dma.curr].handle);
+
+	timb_start_dma(DMA_IRQ_VIDEO_RX,
+		lw->dma.transfer[lw->dma.curr].handle,
+		timblogiw_frame_size(lw->cur_norm),
+		timblogiw_bytes_per_line(lw->cur_norm));
+
+	return 0;
+}
+
+static int timblogiw_streamoff(struct file *file, void  *priv,
+	unsigned int type)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s\n",  __func__);
+
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (lw->stream == STREAM_ON) {
+		unsigned long lock_flags;
+		spin_lock_irqsave(&lw->queue_lock, lock_flags);
+		timb_stop_dma(DMA_IRQ_VIDEO_RX);
+		lw->stream = STREAM_OFF;
+		spin_unlock_irqrestore(&lw->queue_lock, lock_flags);
+	}
+	timblogiw_empty_framequeues(lw);
+
+	return 0;
+}
+
+static int timblogiw_querystd(struct file *file, void  *priv, v4l2_std_id *std)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s\n",  __func__);
+
+	return v4l2_subdev_call(lw->sd_enc, video, querystd, std);
+}
+
+static int timblogiw_enum_framesizes(struct file *file, void  *priv,
+	struct v4l2_frmsizeenum *fsize)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+
+	dbg("%s - index: %d, format: %d\n",  __func__,
+		fsize->index, fsize->pixel_format);
+
+	if ((fsize->index != 0) ||
+		(fsize->pixel_format != V4L2_PIX_FMT_YUYV))
+		return -EINVAL;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fsize->discrete.width = lw->cur_norm->width;
+	fsize->discrete.height = lw->cur_norm->height;
+
+	return 0;
+}
+
+/*******************************
+ * Device Operations functions *
+ *******************************/
+
+static int timblogiw_open(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct timblogiw *lw = video_get_drvdata(vdev);
+	v4l2_std_id std = V4L2_STD_UNKNOWN;
+
+	dbg("%s -\n", __func__);
+
+	mutex_init(&lw->fileop_lock);
+	spin_lock_init(&lw->queue_lock);
+	init_waitqueue_head(&lw->wait_frame);
+
+	mutex_lock(&lw->lock);
+
+	timblogiw_querystd(file, NULL, &std);
+	lw->video_dev->tvnorms = std;
+	lw->cur_norm = timblogiw_get_norm(std);
+
+	file->private_data = lw;
+	lw->stream = STREAM_OFF;
+	lw->num_frames = TIMBLOGIW_NUM_FRAMES;
+
+	timblogiw_empty_framequeues(lw);
+	timb_set_dma_interruptcb(DMA_IRQ_VIDEO_RX | DMA_IRQ_VIDEO_DROP,
+		timblogiw_isr, (void *)lw);
+	mutex_unlock(&lw->lock);
+
+	return 0;
+}
+
+static int timblogiw_close(struct file *file)
+{
+	struct timblogiw *lw = file->private_data;
+
+	dbg("%s - entry\n", __func__);
+
+	mutex_lock(&lw->lock);
+
+	timb_stop_dma(DMA_IRQ_VIDEO_RX);
+	timb_set_dma_interruptcb(DMA_IRQ_VIDEO_RX | DMA_IRQ_VIDEO_DROP, NULL,
+		NULL);
+	timblogiw_release_buffers(lw);
+
+	mutex_unlock(&lw->lock);
+	return 0;
+}
+
+static ssize_t timblogiw_read(struct file *file, char __user *data,
+	size_t count, loff_t *ppos)
+{
+	dbg("%s - read request\n", __func__);
+	return -EINVAL;
+}
+
+static void timblogiw_vm_open(struct vm_area_struct *vma)
+{
+	struct timblogiw_frame *f = vma->vm_private_data;
+	f->vma_use_count++;
+}
+
+static void timblogiw_vm_close(struct vm_area_struct *vma)
+{
+	struct timblogiw_frame *f = vma->vm_private_data;
+	f->vma_use_count--;
+}
+
+static struct vm_operations_struct timblogiw_vm_ops = {
+	.open = timblogiw_vm_open,
+	.close = timblogiw_vm_close,
+};
+
+static int timblogiw_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	unsigned long size = vma->vm_end - vma->vm_start, start = vma->vm_start;
+	void *pos;
+	u32 i;
+	int ret = -EINVAL;
+
+	struct timblogiw *lw = filp->private_data;
+	dbg("%s\n", __func__);
+
+	if (mutex_lock_interruptible(&lw->fileop_lock))
+		return -ERESTARTSYS;
+
+	if (!(vma->vm_flags & VM_WRITE) ||
+		size != PAGE_ALIGN(lw->frame[0].buf.length))
+		goto error_unlock;
+
+	for (i = 0; i < lw->num_frames; i++)
+		if ((lw->frame[i].buf.m.offset >> PAGE_SHIFT) == vma->vm_pgoff)
+			break;
+
+	if (i == lw->num_frames) {
+		dbg("%s - user supplied mapping address is out of range\n",
+			__func__);
+		goto error_unlock;
+	}
+
+	vma->vm_flags |= VM_IO;
+	vma->vm_flags |= VM_RESERVED;	/* Do not swap out this VMA */
+
+	pos = lw->frame[i].bufmem;
+	while (size > 0) {		/* size is page-aligned */
+		if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
+			dbg("%s - vm_insert_page failed\n", __func__);
+			ret = -EAGAIN;
+			goto error_unlock;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vma->vm_ops = &timblogiw_vm_ops;
+	vma->vm_private_data = &lw->frame[i];
+	timblogiw_vm_open(vma);
+	ret = 0;
+
+error_unlock:
+	mutex_unlock(&lw->fileop_lock);
+	return ret;
+}
+
+
+void timblogiw_vdev_release(struct video_device *vdev)
+{
+	kfree(vdev);
+}
+
+static const struct v4l2_ioctl_ops timblogiw_ioctl_ops = {
+	.vidioc_querycap      = timblogiw_querycap,
+	.vidioc_enum_fmt_vid_cap  = timblogiw_enum_fmt,
+	.vidioc_g_fmt_vid_cap     = timblogiw_g_fmt,
+	.vidioc_try_fmt_vid_cap   = timblogiw_try_fmt,
+	.vidioc_s_fmt_vid_cap     = timblogiw_try_fmt,
+	.vidioc_reqbufs       = timblogiw_reqbufs,
+	.vidioc_querybuf      = timblogiw_querybuf,
+	.vidioc_qbuf          = timblogiw_qbuf,
+	.vidioc_dqbuf         = timblogiw_dqbuf,
+	.vidioc_g_std         = timblogiw_g_std,
+	.vidioc_s_std         = timblogiw_s_std,
+	.vidioc_enum_input    = timblogiw_enuminput,
+	.vidioc_g_input       = timblogiw_g_input,
+	.vidioc_s_input       = timblogiw_s_input,
+	.vidioc_streamon      = timblogiw_streamon,
+	.vidioc_streamoff     = timblogiw_streamoff,
+	.vidioc_querystd      = timblogiw_querystd,
+	.vidioc_enum_framesizes = timblogiw_enum_framesizes,
+};
+
+static const struct v4l2_file_operations timblogiw_fops = {
+	.owner		= THIS_MODULE,
+	.open	 	= timblogiw_open,
+	.release	= timblogiw_close,
+	.ioctl		= video_ioctl2, /* V4L2 ioctl handler */
+	.mmap		= timblogiw_mmap,
+	.read		= timblogiw_read,
+};
+
+static const struct video_device timblogiw_template = {
+	.name		= TIMBLOGIWIN_NAME,
+	.fops		= &timblogiw_fops,
+	.ioctl_ops 	= &timblogiw_ioctl_ops,
+	.release	= &timblogiw_vdev_release,
+	.minor		= -1
+};
+
+
+struct find_addr_arg {
+	char const *name;
+	struct i2c_client *client;
+};
+
+static int find_name(struct device *dev, void *argp)
+{
+	struct find_addr_arg	*arg = (struct find_addr_arg *)argp;
+	struct i2c_client	*client = i2c_verify_client(dev);
+
+	if (client && !strcmp(arg->name, client->name) && client->driver)
+		arg->client = client;
+
+	return 0;
+}
+
+static int timblogiw_probe(struct platform_device *dev)
+{
+	int err;
+	struct timblogiw *lw;
+	struct resource *iomem;
+	struct timb_video_platform_data *pdata = dev->dev.platform_data;
+	struct i2c_adapter *adapt;
+	struct i2c_client *encoder;
+	struct find_addr_arg find_arg;
+
+	if (!pdata) {
+		printk(KERN_ERR "timblogiw: Platform data missing\n");
+		err = -EINVAL;
+		goto err_mem;
+	}
+
+	iomem = platform_get_resource(dev, IORESOURCE_MEM, 0);
+	if (!iomem) {
+		err = -EINVAL;
+		goto err_mem;
+	}
+
+	lw = kzalloc(sizeof(*lw), GFP_KERNEL);
+	if (!lw) {
+		err = -EINVAL;
+		goto err_mem;
+	}
+
+	/* find the PCI device from the parent... */
+	if (!dev->dev.parent) {
+		printk(KERN_ERR "timblogiw: No parent device found??\n");
+		err = -ENODEV;
+		goto err_encoder;
+	}
+
+	lw->dev = container_of(dev->dev.parent, struct pci_dev, dev);
+
+	/* find the video decoder */
+	adapt = i2c_get_adapter(pdata->i2c_adapter);
+	if (!adapt) {
+		printk(KERN_ERR "timblogiw: No I2C bus\n");
+		err = -ENODEV;
+		goto err_encoder;
+	}
+
+	/* now find the encoder */
+#ifdef MODULE
+	request_module(pdata->encoder);
+#endif
+	/* Code for finding the I2C child */
+	find_arg.name = pdata->encoder;
+	find_arg.client = NULL;
+	device_for_each_child(&adapt->dev, &find_arg, find_name);
+	encoder = find_arg.client;
+	i2c_put_adapter(adapt);
+
+	if (!encoder) {
+		printk(KERN_ERR "timblogiw: Failed to get encoder\n");
+		err = -ENODEV;
+		goto err_encoder;
+	}
+
+	/* Lock the module */
+	if (!try_module_get(encoder->driver->driver.owner)) {
+		err = -ENODEV;
+		goto err_encoder;
+	}
+
+	lw->sd_enc = i2c_get_clientdata(encoder);
+
+	mutex_init(&lw->lock);
+
+	lw->video_dev = video_device_alloc();
+	if (!lw->video_dev) {
+		err = -ENOMEM;
+		goto err_video_req;
+	}
+	*lw->video_dev = timblogiw_template;
+
+	err = video_register_device(lw->video_dev, VFL_TYPE_GRABBER, 0);
+	if (err) {
+		video_device_release(lw->video_dev);
+		printk(KERN_ALERT "Error reg video\n");
+		goto err_video_req;
+	}
+
+	tasklet_init(&lw->tasklet, timblogiw_handleframe, (unsigned long)lw);
+
+	if (!request_mem_region(iomem->start, resource_size(iomem),
+		"timb-video")) {
+		err = -EBUSY;
+		goto err_request;
+	}
+
+	lw->membase = ioremap(iomem->start, resource_size(iomem));
+	if (!lw->membase) {
+		err = -ENOMEM;
+		goto err_ioremap;
+	}
+
+	platform_set_drvdata(dev, lw);
+	video_set_drvdata(lw->video_dev, lw);
+
+	return 0;
+
+err_ioremap:
+	release_mem_region(iomem->start, resource_size(iomem));
+err_request:
+	if (-1 != lw->video_dev->minor)
+		video_unregister_device(lw->video_dev);
+	else
+		video_device_release(lw->video_dev);
+err_video_req:
+	module_put(lw->sd_enc->owner);
+err_encoder:
+	kfree(lw);
+err_mem:
+	printk(KERN_ERR
+		"timblogiw: Failed to register Timberdale Video In: %d\n", err);
+
+	return err;
+}
+
+static int timblogiw_remove(struct platform_device *dev)
+{
+	struct timblogiw *lw = platform_get_drvdata(dev);
+	struct resource *iomem = platform_get_resource(dev, IORESOURCE_MEM, 0);
+
+	if (-1 != lw->video_dev->minor)
+		video_unregister_device(lw->video_dev);
+	else
+		video_device_release(lw->video_dev);
+
+	module_put(lw->sd_enc->owner);
+	tasklet_kill(&lw->tasklet);
+	iounmap(lw->membase);
+	release_mem_region(iomem->start, resource_size(iomem));
+	kfree(lw);
+
+	return 0;
+}
+
+static struct platform_driver timblogiw_platform_driver = {
+	.driver = {
+		.name	= "timb-video",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= timblogiw_probe,
+	.remove		= timblogiw_remove,
+};
+
+/*--------------------------------------------------------------------------*/
+
+static int __init timblogiw_init(void)
+{
+	return platform_driver_register(&timblogiw_platform_driver);
+}
+
+static void __exit timblogiw_exit(void)
+{
+	platform_driver_unregister(&timblogiw_platform_driver);
+}
+
+module_init(timblogiw_init);
+module_exit(timblogiw_exit);
+
+MODULE_DESCRIPTION("Timberdale Video In driver");
+MODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:timb-video");
+
Index: linux-2.6.30-rc7/drivers/media/video/timblogiw.h
===================================================================
--- linux-2.6.30-rc7/drivers/media/video/timblogiw.h	(revision 0)
+++ linux-2.6.30-rc7/drivers/media/video/timblogiw.h	(revision 864)
@@ -0,0 +1,94 @@
+/*
+ * timblogiw.h timberdale FPGA LogiWin Video In driver defines
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Supports:
+ * Timberdale FPGA LogiWin Video In
+ */
+
+#ifndef _TIMBLOGIW_H
+#define _TIMBLOGIW_H
+
+#include <linux/interrupt.h>
+
+#define TIMBLOGIWIN_NAME    "Timberdale Video-In"
+
+#define TIMBLOGIW_NUM_FRAMES	10
+
+
+enum timblogiw_stream_state {
+	STREAM_OFF,
+	STREAM_ON,
+};
+
+enum timblogiw_frame_state {
+	F_UNUSED = 0,
+	F_QUEUED,
+	F_GRABBING,
+	F_DONE,
+	F_ERROR,
+};
+
+struct timblogiw_frame {
+	void				*bufmem;
+	struct v4l2_buffer		buf;
+	enum timblogiw_frame_state	state;
+	struct list_head		frame;
+	unsigned long			vma_use_count;
+};
+
+struct timblogiw_tvnorm {
+	v4l2_std_id std;
+	char    *name;
+	u16     width;
+	u16     height;
+};
+
+
+
+struct timbdma_transfer {
+	dma_addr_t	handle;
+	void		*buf;
+};
+
+struct timbdma_control {
+	struct timbdma_transfer	transfer[2];
+	struct timbdma_transfer *filled;
+	int curr;
+};
+
+struct timblogiw {
+	struct i2c_client		*decoder;
+	struct timblogiw_frame		frame[TIMBLOGIW_NUM_FRAMES];
+	int				num_frames;
+	unsigned int			frame_count;
+	struct list_head		inqueue, outqueue;
+	spinlock_t			queue_lock; /* mutual exclusion */
+	enum timblogiw_stream_state	stream;
+	struct video_device		*video_dev;
+	struct mutex			lock, fileop_lock;
+	wait_queue_head_t		wait_frame;
+	struct timblogiw_tvnorm const	*cur_norm;
+	struct pci_dev			*dev;
+	struct timbdma_control		dma;
+	void __iomem			*membase;
+	struct tasklet_struct		tasklet;
+	struct v4l2_subdev *sd_enc;	/* encoder */
+};
+
+#endif /* _TIMBLOGIW_H */
+
Index: linux-2.6.30-rc7/include/media/timb_video.h
===================================================================
--- linux-2.6.30-rc7/include/media/timb_video.h	(revision 0)
+++ linux-2.6.30-rc7/include/media/timb_video.h	(revision 864)
@@ -0,0 +1,30 @@
+/*
+ * timb_video.h Platform struct for the Timberdale video driver
+ * Copyright (c) 2009 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _TIMB_VIDEO_
+#define _TIMB_VIDEO_ 1
+
+#include <linux/i2c.h>
+
+struct timb_video_platform_data {
+	int i2c_adapter; /* The I2C adapter where the encoder is attached */
+	char encoder[32];
+};
+
+#endif
+

             reply	other threads:[~2009-06-05 13:41 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2009-06-05 13:40 Richard Röjfors [this message]
2009-06-06  8:40 ` [PATCH 5/9] V4L2: Added Timberdale Logiwin driver Alexey Klimov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4A292067.1070405@mocean-labs.com \
    --to=richard.rojfors.ext@mocean-labs.com \
    --cc=akpm@linux-foundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.