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@xxxxxxxxxxxxxxx> --- 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@xxxxxxxxxxxxxxx>"); +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 + -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html