Message ID | 4A292067.1070405@mocean-labs.com (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
Hello, Richard i have only two small suggestions. On Fri, Jun 5, 2009 at 5:40 PM, Richard Röjfors<richard.rojfors.ext@mocean-labs.com> wrote: > 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"); It's probably better to add module name here. This will make easy to understand from what module this message come. > +        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; Looks like this error is -ENOMEM. > +        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"); Probably, here module name is needed too.
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 +
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> --- -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html