diff options
author | Archit Taneja <archit@ti.com> | 2010-08-02 13:36:58 +0530 |
---|---|---|
committer | Ricardo Perez Olivares <x0081762@ti.com> | 2010-09-14 19:26:47 -0500 |
commit | b6f2c25faead22c7a02d1eaf081964d8572a19dd (patch) | |
tree | df7d644d61afc8afebdd5f7f763c6c61aed15d20 /drivers/media | |
parent | e1411230efe9ce802f357295985f19e4cfa72f6d (diff) |
V4L2: Add source files for WB capture device
Signed-off-by: Archit Taneja <archit@ti.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/video/omap/Makefile | 2 | ||||
-rw-r--r-- | drivers/media/video/omap/omap_wb.c | 1135 | ||||
-rw-r--r-- | drivers/media/video/omap/omap_wbdef.h | 93 |
3 files changed, 1229 insertions, 1 deletions
diff --git a/drivers/media/video/omap/Makefile b/drivers/media/video/omap/Makefile index b28788070ae1..d206d6fc5aab 100644 --- a/drivers/media/video/omap/Makefile +++ b/drivers/media/video/omap/Makefile @@ -3,5 +3,5 @@ # # OMAP2/3 Display driver -omap-vout-y := omap_vout.o omap_voutlib.o +omap-vout-y := omap_vout.o omap_voutlib.o omap_wb.o obj-$(CONFIG_VIDEO_OMAP2_VOUT) += omap-vout.o diff --git a/drivers/media/video/omap/omap_wb.c b/drivers/media/video/omap/omap_wb.c new file mode 100644 index 000000000000..84856a10e62c --- /dev/null +++ b/drivers/media/video/omap/omap_wb.c @@ -0,0 +1,1135 @@ +/* + * drivers/media/video/omap/omap_wb.c + * + * Copyright (C) 2005-2009 Texas Instruments. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * Leveraged code from the OMAP2 camera driver + * Video-for-Linux (Version 2) camera capture driver for + * the OMAP24xx camera controller. + * + * Author: Andy Lowe (source@mvista.com) + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2009 Texas Instruments. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/vmalloc.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/kdev_t.h> +#include <linux/types.h> +#include <linux/wait.h> +#include <linux/videodev2.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <media/videobuf-dma-contig.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-common.h> +#include <media/v4l2-device.h> + +#include <asm/processor.h> +#include <plat/dma.h> +#include <plat/vram.h> +#include <plat/display.h> + +#include "omap_wbdef.h" +#include "omap_voutlib.h" +#include <mach/tiler.h> + +#define WB_NAME "omap_wb" + +#define QQVGA_WIDTH 160 +#define QQVGA_HEIGHT 120 + +struct mutex wb_lock; +static struct videobuf_queue_ops video_vbq_ops; + +static int debug_wb; + +module_param(debug_wb, bool, S_IRUGO); +MODULE_PARM_DESC(debug_wb, "Debug level (0-1)"); + +int omap_vout_try_format(struct v4l2_pix_format *pix); +enum omap_color_mode video_mode_to_dss_mode( + struct v4l2_pix_format *pix); +void omap_wb_isr(void *arg, unsigned int irqstatus); +int omap_dss_wb_apply(struct omap_overlay_manager *mgr, struct omap_writeback *wb); + +int omap_setup_wb(struct omap_wb_device *wb_device, u32 addr, u32 uv_addr) +{ + struct omap_writeback *wb; + struct omap_overlay_manager *mgr = NULL; + struct omap_writeback_info wb_info; + int r = 0; + int i = 0; + wb_info.enabled = true; + wb_info.info_dirty = true; + wb_info.capturemode = wb_device->capturemode; + wb_info.dss_mode = wb_device->dss_mode; + wb_info.height = wb_device->height; + wb_info.width = wb_device->width; + wb_info.source = wb_device->source; + wb_info.source_type = wb_device->source_type; + wb_info.paddr = addr; + wb_info.puv_addr = uv_addr; + + v4l2_dbg(1, debug_wb, &wb_device->wb_dev->v4l2_dev, + "omap_write back struct contents :\n" + "enabled = %d\n infodirty = %d\n" + "capturemode = %d\n dss_mode = %d\n" + "height = %d\n width = %d\n source = %d\n" + "source_type = %d\n paddr =%x\n puvaddr = %x\n", + wb_info.enabled, wb_info.info_dirty, wb_info.capturemode, + wb_info.dss_mode, wb_info.height, wb_info.width, + wb_info.source, wb_info.source_type, wb_info.paddr, + wb_info.puv_addr); + + for (i = 0; i < omap_dss_get_num_overlay_managers(); ++i) { + /* Fix : checking for mgr will shift to DSS2 */ + mgr = omap_dss_get_overlay_manager(i); + if (strcmp(mgr->name, "lcd") == 0) + break; + } + + wb = omap_dss_get_wb(0); + + if (!wb) { + printk(KERN_ERR WB_NAME "couldn't get wb struct\n"); + r = -EINVAL; + goto err; + } + wb->enabled = true; + wb->info_dirty = true; + + r = wb->set_wb_info(wb, &wb_info); + if (r) { + printk(KERN_ERR WB_NAME "wb_info not set\n"); + goto err; + } + r = omap_dss_wb_apply(mgr, wb); +err: + return r; +} + +/* + * + * IOCTL interface. + * + */ + +static int vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct omap_wb_device *wb = fh; + + strlcpy(cap->driver, WB_NAME, + sizeof(cap->driver)); + strlcpy(cap->card, wb->vfd->name, sizeof(cap->card)); + cap->bus_info[0] = '\0'; + cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; + return 0; +} + +static int vidioc_g_fmt_vid_wb(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap_wb_device *wb = fh; + + f->fmt.pix = wb->pix; + return 0; + +} + +static int vidioc_s_fmt_vid_wb(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct omap_wb_device *wb = fh; + int bpp; + + if (wb->streaming) + return -EBUSY; + + mutex_lock(&wb->lock); + + bpp = omap_vout_try_format(&f->fmt.pix); + + if (V4L2_PIX_FMT_NV12 == f->fmt.pix.pixelformat) + f->fmt.pix.sizeimage = f->fmt.pix.width * + f->fmt.pix.height * 3/2; + else + f->fmt.pix.sizeimage = f->fmt.pix.width * + f->fmt.pix.height * bpp; + + /* try & set the new output format */ + wb->bpp = bpp; + wb->pix = f->fmt.pix; + + mutex_unlock(&wb->lock); + + return 0; +} + +static int vidioc_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *req) +{ + struct omap_wb_device *wb = fh; + struct videobuf_queue *q = &wb->vbq; + unsigned int i; + int ret = 0; + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, "entered REQbuf: \n"); + + if ((req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) || (req->count < 0)) + return -EINVAL; + + /* if memory is not mmp or userptr + return error */ + if ((V4L2_MEMORY_MMAP != req->memory) && + (V4L2_MEMORY_USERPTR != req->memory)) + return -EINVAL; + + mutex_lock(&wb->lock); + + /* Cannot be requested when streaming is on */ + if (wb->streaming) { + mutex_unlock(&wb->lock); + return -EBUSY; + } + + /* If buffers are already allocated free them */ + if (q->bufs[0] && (V4L2_MEMORY_MMAP == q->bufs[0]->memory)) { + if (wb->mmap_count) { + mutex_unlock(&wb->lock); + return -EBUSY; + } + + videobuf_mmap_free(q); + } else if (q->bufs[0] && (V4L2_MEMORY_USERPTR == q->bufs[0]->memory)) { + if (wb->buffer_allocated) { + videobuf_mmap_free(q); + for (i = 0; i < wb->buffer_allocated; i++) { + kfree(q->bufs[i]); + q->bufs[i] = NULL; + } + wb->buffer_allocated = 0; + } + } + /*store the memory type in data structure */ + wb->memory = req->memory; + + INIT_LIST_HEAD(&wb->dma_queue); + + /* call videobuf_reqbufs api */ + ret = videobuf_reqbufs(q, req); + if (ret < 0) { + mutex_unlock(&wb->lock); + return ret; + } + + wb->buffer_allocated = req->count; + mutex_unlock(&wb->lock); + return 0; +} + +static int vidioc_querybuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct omap_wb_device *wb = fh; + + return videobuf_querybuf(&wb->vbq, b); +} + +static int vidioc_qbuf(struct file *file, void *fh, + struct v4l2_buffer *buffer) +{ + struct omap_wb_device *wb = fh; + struct videobuf_queue *q = &wb->vbq; + int ret = 0; + u32 addr = 0, uv_addr = 0; + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "entered qbuf: buffer address: %x\n", (unsigned int) buffer); + + if ((V4L2_BUF_TYPE_VIDEO_CAPTURE != buffer->type) || + (buffer->index >= wb->buffer_allocated) || + + (q->bufs[buffer->index]->memory != buffer->memory)) { + return -EINVAL; + } + if (V4L2_MEMORY_USERPTR == buffer->memory) { + if ((buffer->length < wb->pix.sizeimage) || + (0 == buffer->m.userptr)) { + return -EINVAL; + } + } + + ret = videobuf_qbuf(q, buffer); + + if (wb->streaming && wb->buf_empty) { + addr = (unsigned long)wb->queued_buf_addr[wb->next_frm->i]; + uv_addr = (unsigned long) wb->queued_buf_uv_addr[wb->cur_frm->i]; + wb->buf_empty = 0; + omap_setup_wb(wb, addr, uv_addr); + } + return ret; +} + +static int vidioc_dqbuf(struct file *file, void *fh, + struct v4l2_buffer *b) +{ + struct omap_wb_device *wb = fh; + struct videobuf_queue *q = &wb->vbq; + int ret = 0; + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "entered DQbuf: buffer address: %x \n", (unsigned int) b); + + if (!wb->streaming) + return -EINVAL; + + if (file->f_flags & O_NONBLOCK) + /* Call videobuf_dqbuf for non blocking mode */ + ret = videobuf_dqbuf(q, (struct v4l2_buffer *)b, 1); + else + /* Call videobuf_dqbuf for blocking mode */ + ret = videobuf_dqbuf(q, (struct v4l2_buffer *)b, 0); + return ret; +} + +static int vidioc_streamon(struct file *file, void *fh, + enum v4l2_buf_type i) +{ + struct omap_wb_device *wb = fh; + struct videobuf_queue *q = &wb->vbq; + u32 addr = 0, uv_addr = 0; + int r = 0; + u32 mask = 0; + + mutex_lock(&wb->lock); + + if (wb->streaming) { + mutex_unlock(&wb->lock); + return -EBUSY; + } + + r = videobuf_streamon(q); + if (r < 0) { + mutex_unlock(&wb->lock); + return r; + } + + if (list_empty(&wb->dma_queue)) { + mutex_unlock(&wb->lock); + return -EIO; + } + /* Get the next frame from the buffer queue */ + wb->next_frm = wb->cur_frm = list_entry(wb->dma_queue.next, + struct videobuf_buffer, queue); + /* Remove buffer from the buffer queue */ + list_del(&wb->cur_frm->queue); + /* Mark state of the current frame to active */ + wb->cur_frm->state = VIDEOBUF_ACTIVE; + + /* set flag here. Next QBUF will start DMA */ + wb->streaming = 1; + + wb->first_int = 1; + + addr = (unsigned long) wb->queued_buf_addr[wb->cur_frm->i]; + + uv_addr = (unsigned long) wb->queued_buf_uv_addr[wb->cur_frm->i]; + + mask = DISPC_IRQ_FRAMEDONE_WB; + + r = omap_dispc_register_isr(omap_wb_isr, wb, mask); + + r = omap_setup_wb(wb, addr, uv_addr); + if (r) + printk(KERN_ERR WB_NAME "error in setup_wb\n"); + + mutex_unlock(&wb->lock); + return r; +} + +static int vidioc_streamoff(struct file *file, void *fh, + enum v4l2_buf_type i) +{ + struct omap_wb_device *wb = fh; + u32 mask = 0; + + if (!wb->streaming) + return -EINVAL; + + wb->streaming = 0; + mask = DISPC_IRQ_FRAMEDONE_WB; + + omap_dispc_unregister_isr(omap_wb_isr, wb, mask); + + INIT_LIST_HEAD(&wb->dma_queue); + videobuf_streamoff(&wb->vbq); + videobuf_queue_cancel(&wb->vbq); + return 0; +} + +static int vidioc_default_wb(struct file *file, void *fh, + int cmd, void *arg) +{ + struct v4l2_writeback_ioctl_data *wb_data = NULL; + struct omap_wb_device *wb = fh; + int r = -1; + enum omap_color_mode dss_mode; + + switch (cmd) { + case VIDIOC_CUSTOM_S_WB: + wb_data = (struct v4l2_writeback_ioctl_data *)arg; + + if (!wb_data) { + return -EINVAL; + printk(KERN_ERR WB_NAME "error in S_WB\n"); + } + mutex_lock(&wb->lock); + wb->enabled = wb_data->enabled; + wb->pix = wb_data->pix; + wb->source = wb_data->source; + wb->capturemode = wb_data->capturemode; + /* source type hardcoded for now */ + wb->source_type = V4L2_WB_SOURCE_OVERLAY; + + dss_mode = video_mode_to_dss_mode(&wb->pix); + + if (dss_mode == -EINVAL) { + printk(KERN_ERR WB_NAME "invalid dss_mode\n"); + r = -EINVAL; + goto err; + } + + mutex_unlock(&wb->lock); + + break; + + case VIDIOC_CUSTOM_G_WB: + wb_data = (struct v4l2_writeback_ioctl_data *)arg; + + if (!wb_data) { + printk(KERN_ERR WB_NAME "error in G_WB\n"); + return -EINVAL; + } + mutex_lock(&wb->lock); + + wb_data->pix = wb->pix; + wb_data->source = wb->source; + wb_data->capturemode = wb->capturemode; + wb_data->source_type = V4L2_WB_SOURCE_OVERLAY; + + mutex_unlock(&wb->lock); + + break; + default: + BUG(); + } + return 0; + + err: + mutex_unlock(&wb->lock); + return r; +} + +static const struct v4l2_ioctl_ops wb_ioctl_fops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_wb, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_wb, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_default = vidioc_default_wb, +}; + +static void omap_wb_tiler_buffer_free(struct omap_wb_device *wb, + unsigned int count, + unsigned int startindex) +{ + int i; + + if (startindex < 0) + startindex = 0; + if (startindex + count > VIDEO_MAX_FRAME) + count = VIDEO_MAX_FRAME - startindex; + + for (i = startindex; i < startindex + count; i++) { + if (wb->buf_phy_addr_alloced[i]) + tiler_free(wb->buf_phy_addr_alloced[i]); + if (wb->buf_phy_uv_addr_alloced[i]) + tiler_free(wb->buf_phy_uv_addr_alloced[i]); + wb->buf_phy_addr[i] = 0; + wb->buf_phy_addr_alloced[i] = 0; + wb->buf_phy_uv_addr[i] = 0; + wb->buf_phy_uv_addr_alloced[i] = 0; + } +} + +/* Allocate the buffers for TILER space. Ideally, the buffers will be ONLY + in tiler space, with different rotated views available by just a convert. + */ +static int omap_wb_tiler_buffer_setup(struct omap_wb_device *wb, + unsigned int *count, unsigned int startindex, + struct v4l2_pix_format *pix) +{ + int i, aligned = 1; + enum tiler_fmt fmt; + + /* normalize buffers to allocate so we stay within bounds */ + int start = (startindex < 0) ? 0 : startindex; + int n_alloc = (start + *count > VIDEO_MAX_FRAME) + ? VIDEO_MAX_FRAME - start : *count; + int bpp = omap_vout_try_format(pix); + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, "tiler buffer alloc:\n" + "count - %d, start -%d :\n", *count, startindex); + + /* special allocation scheme for NV12 format */ + if (OMAP_DSS_COLOR_NV12 == video_mode_to_dss_mode(pix)) { + tiler_alloc_packed_nv12(&n_alloc, pix->width, + pix->height, + (void **) wb->buf_phy_addr + start, + (void **) wb->buf_phy_uv_addr + start, + (void **) wb->buf_phy_addr_alloced + start, + (void **) wb->buf_phy_uv_addr_alloced + start, + aligned); + } else { + /* Only bpp of 1, 2, and 4 is supported by tiler */ + fmt = (bpp == 1 ? TILFMT_8BIT : + bpp == 2 ? TILFMT_16BIT : + bpp == 4 ? TILFMT_32BIT : TILFMT_INVALID); + if (fmt == TILFMT_INVALID) + return -ENOMEM; + + tiler_alloc_packed(&n_alloc, fmt, pix->width, + pix->height, + (void **) wb->buf_phy_addr + start, + (void **) wb->buf_phy_addr_alloced + start, + aligned); + } + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "allocated %d buffers\n", n_alloc); + + if (n_alloc < *count) { + if (n_alloc && (startindex == -1 || + V4L2_MEMORY_MMAP != wb->memory)) { + /* TODO: check this condition's logic */ + omap_wb_tiler_buffer_free(wb, n_alloc, start); + *count = 0; + return -ENOMEM; + } + } + + for (i = start; i < start + n_alloc; i++) { + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "y=%08lx (%d) uv=%08lx (%d)\n", + wb->buf_phy_addr[i], + wb->buf_phy_addr_alloced[i] ? 1 : 0, + wb->buf_phy_uv_addr[i], + wb->buf_phy_uv_addr_alloced[i] ? 1 : 0); + } + + *count = n_alloc; + + return 0; +} + +/* Free tiler buffers */ +static void omap_wb_free_tiler_buffers(struct omap_wb_device *wb) +{ + omap_wb_tiler_buffer_free(wb, wb->buffer_allocated, 0); + wb->buffer_allocated = 0; +} + + +/* Video buffer call backs */ + +/* Buffer setup function is called by videobuf layer when REQBUF ioctl is + * called. This is used to setup buffers and return size and count of + * buffers allocated. After the call to this buffer, videobuf layer will + * setup buffer queue depending on the size and count of buffers + */ +static int omap_wb_buffer_setup(struct videobuf_queue *q, unsigned int *count, + unsigned int *size) +{ + struct omap_wb_device *wb = q->priv_data; + int i; + + if (!wb) + return -EINVAL; + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != q->type) + return -EINVAL; + + /* Now allocated the V4L2 buffers */ + /* i is the block-width - either 4K or 8K, depending upon input width*/ + i = (wb->pix.width * wb->bpp + + TILER_PAGE - 1) & ~(TILER_PAGE - 1); + + /* for NV12 format, buffer is height + height / 2*/ + if (OMAP_DSS_COLOR_NV12 == wb->dss_mode) + *size = wb->buffer_size = (wb->pix.height * 3/2 * i); + else + *size = wb->buffer_size = (wb->pix.height * i); + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "\nheight=%d, size = %d, wb->buffer_sz=%d\n", + wb->pix.height, *size, wb->buffer_size); + if (omap_wb_tiler_buffer_setup(wb, count, 0, &wb->pix)) + return -ENOMEM; + + if (V4L2_MEMORY_MMAP != wb->memory) + return 0; + + return 0; +} + +/* This function will be called when VIDIOC_QBUF ioctl is called. + * It prepare buffers before give out for the display. This function + * user space virtual address into physical address if userptr memory + * exchange mechanism is used. If rotation is enabled, it copies entire + * buffer into VRFB memory space before giving it to the DSS. + */ +static int omap_wb_buffer_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct omap_wb_device *wb = q->priv_data; + + if (VIDEOBUF_NEEDS_INIT == vb->state) { + vb->width = wb->pix.width; + vb->height = wb->pix.height; + vb->size = vb->width * vb->height * wb->bpp; + vb->field = field; + } + vb->state = VIDEOBUF_PREPARED; + + /* Here, we need to use the physical addresses given by Tiler: + */ + wb->queued_buf_addr[vb->i] = (u8 *) wb->buf_phy_addr[vb->i]; + wb->queued_buf_uv_addr[vb->i] = (u8 *) wb->buf_phy_uv_addr[vb->i]; + return 0; +} + +/* Buffer queue funtion will be called from the videobuf layer when _QBUF + * ioctl is called. It is used to enqueue buffer, which is ready to be + * displayed. */ +static void omap_wb_buffer_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct omap_wb_device *wb = q->priv_data; + + /* Driver is also maintainig a queue. So enqueue buffer in the driver + * queue */ + list_add_tail(&vb->queue, &wb->dma_queue); + + vb->state = VIDEOBUF_QUEUED; +} + +/* Buffer release function is called from videobuf layer to release buffer + * which are already allocated */ +static void omap_wb_buffer_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct omap_wb_device *wb = q->priv_data; + + vb->state = VIDEOBUF_NEEDS_INIT; + + if (V4L2_MEMORY_MMAP != wb->memory) + return; +} + +/* + * File operations + */ +static void omap_wb_vm_open(struct vm_area_struct *vma) +{ + struct omap_wb_device *wb = vma->vm_private_data; + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "vm_open [vma=%08lx-%08lx]\n", vma->vm_start, vma->vm_end); + wb->mmap_count++; +} + +static void omap_wb_vm_close(struct vm_area_struct *vma) +{ + struct omap_wb_device *wb = vma->vm_private_data; + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "vm_close [vma=%08lx-%08lx]\n", vma->vm_start, vma->vm_end); + wb->mmap_count--; +} + +static struct vm_operations_struct omap_wb_vm_ops = { + .open = omap_wb_vm_open, + .close = omap_wb_vm_close, +}; + +static int omap_wb_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct omap_wb_device *wb = file->private_data; + struct videobuf_queue *q = &wb->vbq; + int i; + void *pos; + + int j = 0, k = 0, m = 0, p = 0, m_increment = 0; + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + " %s pgoff=0x%lx, start=0x%lx, end=0x%lx\n", __func__, + vma->vm_pgoff, vma->vm_start, vma->vm_end); + + /* look for the buffer to map */ + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + if (NULL == q->bufs[i]) + continue; + if (V4L2_MEMORY_MMAP != q->bufs[i]->memory) + continue; + if (q->bufs[i]->boff == (vma->vm_pgoff << PAGE_SHIFT)) + break; + } + + if (VIDEO_MAX_FRAME == i) { + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, + "offset invalid [offset=0x%lx]\n", + (vma->vm_pgoff << PAGE_SHIFT)); + return -EINVAL; + } + q->bufs[i]->baddr = vma->vm_start; + + vma->vm_flags |= VM_RESERVED; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &omap_wb_vm_ops; + vma->vm_private_data = (void *) wb; + + pos = (void*) wb->buf_phy_addr[i]; + /* get line width */ + /* for NV12, Y buffer is 1bpp*/ + + if (OMAP_DSS_COLOR_NV12 == wb->dss_mode) { + p = (wb->pix.width + + TILER_PAGE - 1) & ~(TILER_PAGE - 1); + m_increment = 64 * TILER_WIDTH; + } else { + p = (wb->pix.width * wb->bpp + + TILER_PAGE - 1) & ~(TILER_PAGE - 1); + + if (wb->bpp > 1) + m_increment = 2*64*TILER_WIDTH; + else + m_increment = 64 * TILER_WIDTH; + } + + for (j = 0; j < wb->pix.height; j++) { + /* map each page of the line */ + + vma->vm_pgoff = + ((unsigned long)pos + m) >> PAGE_SHIFT; + + if (remap_pfn_range(vma, vma->vm_start + k, + ((unsigned long)pos + m) >> PAGE_SHIFT, + p, vma->vm_page_prot)) + return -EAGAIN; + k += p; + m += m_increment; + } + m = 0; + + /* UV Buffer in case of NV12 format */ + if (OMAP_DSS_COLOR_NV12 == wb->dss_mode) { + pos = wb->buf_phy_uv_addr[i]; + /* UV buffer is 2 bpp, but half size, so p remains */ + m_increment = 2*64*TILER_WIDTH; + + /* UV buffer is height / 2*/ + for (j = 0; j < wb->pix.height / 2; j++) { + /* map each page of the line */ + vma->vm_pgoff = + ((unsigned long)pos + m) >> PAGE_SHIFT; + + if (remap_pfn_range(vma, vma->vm_start + k, + ((unsigned long)pos + m) >> PAGE_SHIFT, + p, vma->vm_page_prot)) + return -EAGAIN; + k += p; + m += m_increment; + } + } + + vma->vm_flags &= ~VM_IO; /* using shared anonymous pages */ + wb->mmap_count++; + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, "Exiting %s\n", __func__); + return 0; +} + +static int omap_wb_release(struct file *file) +{ + + struct omap_wb_device *wb = file->private_data; + struct videobuf_queue *q; + unsigned int r = 0; + + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, "Entering %s\n", __func__); + + if (!wb) + return 0; + + q = &wb->vbq; + + omap_wb_free_tiler_buffers(wb); + + videobuf_mmap_free(q); + + /* Even if apply changes fails we should continue + freeing allocated memeory */ + if (wb->streaming) { + u32 mask = 0; + + mask = DISPC_IRQ_FRAMEDONE_WB; + + omap_dispc_unregister_isr(omap_wb_isr, wb, mask); + wb->streaming = 0; + + videobuf_streamoff(q); + videobuf_queue_cancel(q); + } + + if (wb->mmap_count != 0) + wb->mmap_count = 0; + + wb->opened -= 1; + file->private_data = NULL; + + if (wb->buffer_allocated) + videobuf_mmap_free(q); + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, "Exiting %s\n", __func__); + return r; +} + +static int omap_wb_open(struct file *file) +{ + struct omap_wb_device *wb = NULL; + struct videobuf_queue *q; + + wb = video_drvdata(file); + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, "Entering %s\n", __func__); + + if (wb == NULL) + return -ENODEV; + + /* for now, we only support single open */ + if (wb->opened) + return -EBUSY; + + wb->opened += 1; + wb->enabled = true; + file->private_data = wb; + wb->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + q = &wb->vbq; + video_vbq_ops.buf_setup = omap_wb_buffer_setup; + video_vbq_ops.buf_prepare = omap_wb_buffer_prepare; + video_vbq_ops.buf_release = omap_wb_buffer_release; + video_vbq_ops.buf_queue = omap_wb_buffer_queue; + spin_lock_init(&wb->vbq_lock); + + videobuf_queue_dma_contig_init(q, &video_vbq_ops, q->dev, &wb->vbq_lock, + wb->type, V4L2_FIELD_NONE, sizeof(struct videobuf_buffer), + wb); + v4l2_dbg(1, debug_wb, &wb->wb_dev->v4l2_dev, "Exiting %s\n", __func__); + return 0; +} + +static struct v4l2_file_operations wb_fops = { + .owner = THIS_MODULE, + .ioctl = video_ioctl2, + .mmap = omap_wb_mmap, + .open = omap_wb_open, + .release = omap_wb_release, +}; + + +/* Init functions used during driver intitalization */ +/* Initial setup of video_data */ +static int __init omap_wb_setup_video_data(struct omap_wb_device *wb) +{ + struct v4l2_pix_format *pix; + struct video_device *vfd; + /* set the default pix */ + pix = &wb->pix; + + /* Set the default picture of QVGA */ + pix->width = QQVGA_WIDTH; + pix->height = QQVGA_HEIGHT; + + /* Default pixel format is RGB 5-6-5 */ + pix->pixelformat = V4L2_PIX_FMT_RGB32; + pix->field = V4L2_FIELD_ANY; + pix->bytesperline = pix->width * 2; + pix->sizeimage = pix->bytesperline * pix->height; + pix->priv = 0; + pix->colorspace = V4L2_COLORSPACE_JPEG; + + wb->width = pix->width; + wb->height = pix->height; + wb->bpp = RGB565_BPP; + wb->enabled = false; + wb->buffer_allocated = 0; + wb->capturemode = V4L2_WB_CAPTURE_ALL; + wb->source = V4L2_WB_OVERLAY0; + wb->source_type = V4L2_WB_SOURCE_OVERLAY; + wb->dss_mode = OMAP_DSS_COLOR_ARGB32; + /* initialize the video_device struct */ + vfd = wb->vfd = video_device_alloc(); + + if (!vfd) { + printk(KERN_ERR WB_NAME ": could not allocate" + " WB device struct\n"); + return -ENOMEM; + } + vfd->release = video_device_release; + vfd->ioctl_ops = &wb_ioctl_fops; + + strlcpy(vfd->name, WB_NAME, sizeof(vfd->name)); + vfd->vfl_type = VFL_TYPE_GRABBER; + + /* need to register for a VID_HARDWARE_* ID in videodev.h */ + vfd->fops = &wb_fops; + mutex_init(&wb->lock); + + vfd->minor = -1; + return 0; +} + + +/* Create wb devices */ +static int __init omap_wb_create_video_devices(struct platform_device *pdev) +{ + int r = 0, k; + struct omap_wb_device *wb; + struct video_device *vfd = NULL; + struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); + + struct omap2wb_device *wb_dev = container_of(v4l2_dev, struct + omap2wb_device, v4l2_dev); + + for (k = 0; k < pdev->num_resources; k++) { + + wb = kmalloc(sizeof(struct omap_wb_device), GFP_KERNEL); + if (!wb) { + printk(KERN_ERR WB_NAME + ": could not allocate memory\n"); + return -ENOMEM; + } + + memset(wb, 0, sizeof(struct omap_wb_device)); + + wb->wid = k; + wb->wb_dev = wb_dev; + + /* Setup the default configuration for the wb devices + */ + if (omap_wb_setup_video_data(wb) != 0) { + r = -ENOMEM; + goto error; + } + + /* Register the Video device with V4L2 + */ + vfd = wb->vfd; + if (video_register_device(vfd, VFL_TYPE_GRABBER, k + 1) < 0) { + printk(KERN_ERR WB_NAME ": could not register " + "Video for Linux device\n"); + vfd->minor = -1; + r = -ENODEV; + goto error1; + } + video_set_drvdata(vfd, wb); + + goto success; + +error1: + video_device_release(vfd); +error: + kfree(wb); + return r; + +success: + printk(KERN_INFO WB_NAME ": registered and initialized " + "wb device %d [v4l2]\n", vfd->minor); + if (k == (pdev->num_resources - 1)) + return 0; + } + return -ENODEV; + +} + +/* Driver functions */ + +static void omap_wb_cleanup_device(struct omap_wb_device *wb) +{ + struct video_device *vfd; + + if (!wb) + return; + + vfd = wb->vfd; + + if (vfd) { + if (vfd->minor == -1) { + /* + * The device was never registered, so release the + * video_device struct directly. + */ + video_device_release(vfd); + } else { + /* + * The unregister function will release the video_device + * struct as well as unregistering it. + */ + video_unregister_device(vfd); + } + } + + omap_wb_free_tiler_buffers(wb); + + kfree(wb); +} + +static int omap_wb_remove(struct platform_device *pdev) +{ + + struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); + struct omap2wb_device *wb_dev = container_of(v4l2_dev, struct + omap2wb_device, v4l2_dev); + int k; + + v4l2_device_unregister(v4l2_dev); + for (k = 0; k < pdev->num_resources; k++) + omap_wb_cleanup_device(wb_dev->wb); + + kfree(wb_dev); + + return 0; +} + +static int __init omap_wb_probe(struct platform_device *pdev) +{ + int r = 0; + struct omap2wb_device *wb_dev = NULL; + + if (pdev->num_resources == 0) { + dev_err(&pdev->dev, "probed for an unknown device\n"); + r = -ENODEV; + return r; + } + + wb_dev = kzalloc(sizeof(struct omap2wb_device), GFP_KERNEL); + if (wb_dev == NULL) { + r = -ENOMEM; + return r; + } + + if (v4l2_device_register(&pdev->dev, &wb_dev->v4l2_dev) < 0) { + printk(KERN_ERR WB_NAME "v4l2_device_register failed\n"); + return -ENODEV; + } + + r = omap_wb_create_video_devices(pdev); + if (r) + goto error0; + + return 0; + +error0: + kfree(wb_dev); + return r; +} + +static struct platform_driver omap_wb_driver = { + .driver = { + .name = WB_NAME, + }, + .probe = omap_wb_probe, + .remove = omap_wb_remove, +}; +void omap_wb_isr(void *arg, unsigned int irqstatus) +{ + struct timeval timevalue = {0}; + int r = 0; + struct omap_wb_device *wb = + (struct omap_wb_device *) arg; + u32 addr, uv_addr, flags; + + spin_lock_irqsave(&wb->vbq_lock, flags); + + if (!wb->first_int && (wb->cur_frm != wb->next_frm)) { + wb->cur_frm->ts = timevalue; + wb->cur_frm->state = VIDEOBUF_DONE; + wake_up_interruptible(&wb->cur_frm->done); + wb->cur_frm = wb->next_frm; + } + + wb->first_int = 0; + if (list_empty(&wb->dma_queue)) { + wb->buf_empty = true; + spin_unlock_irqrestore(&wb->vbq_lock, flags); + return; + } + + wb->next_frm = list_entry(wb->dma_queue.next, + struct videobuf_buffer, queue); + list_del(&wb->next_frm->queue); + + wb->next_frm->state = VIDEOBUF_ACTIVE; + addr = (unsigned long)wb->queued_buf_addr[wb->next_frm->i]; + +#ifdef CONFIG_ARCH_OMAP4 + uv_addr = (unsigned long)wb->queued_buf_uv_addr[ + wb->next_frm->i]; +#endif + r = omap_setup_wb(wb, addr, uv_addr); + if (r) + printk(KERN_ERR WB_NAME "error in setup_wb %d\n", r); + + spin_unlock_irqrestore(&wb->vbq_lock, flags); +} + +static int __init omap_wb_init(void) +{ + + if (platform_driver_register(&omap_wb_driver) != 0) { + printk(KERN_ERR WB_NAME ": could not register \ + WB driver\n"); + return -EINVAL; + } + mutex_init(&wb_lock); + return 0; +} + +static void omap_wb_cleanup(void) +{ + platform_driver_unregister(&omap_wb_driver); +} + +late_initcall(omap_wb_init); +module_exit(omap_wb_cleanup); + diff --git a/drivers/media/video/omap/omap_wbdef.h b/drivers/media/video/omap/omap_wbdef.h new file mode 100644 index 000000000000..accabf537b06 --- /dev/null +++ b/drivers/media/video/omap/omap_wbdef.h @@ -0,0 +1,93 @@ +/* + * drivers/media/video/omap/omap_wbdef.h + * + * Copyright (C) 2009 Texas Instruments. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <plat/display.h> +#include <linux/videodev2.h> + +#define YUYV_BPP 2 +#define RGB565_BPP 2 +#define RGB24_BPP 3 +#define RGB32_BPP 4 +#define TILE_SIZE 32 + +struct omap2wb_device { + struct mutex mtx; + + int state; + struct v4l2_device v4l2_dev; + struct omap_wb_device *wb; +}; + +struct omap_wb_device { + + struct video_device *vfd; + int wid; + int opened; + struct omap2wb_device *wb_dev; + /* we don't allow to change image fmt/size once buffer has + * been allocated + */ + int buffer_allocated; + /* allow to reuse previously allocated buffer which is big enough */ + int buffer_size; + /* keep buffer info across opens */ + unsigned long buf_virt_addr[VIDEO_MAX_FRAME]; + unsigned long buf_phy_addr[VIDEO_MAX_FRAME]; + /* keep which buffers we actually allocated (via tiler) */ + unsigned long buf_phy_uv_addr_alloced[VIDEO_MAX_FRAME]; + unsigned long buf_phy_addr_alloced[VIDEO_MAX_FRAME]; + + /* NV12 support*/ + unsigned long buf_phy_uv_addr[VIDEO_MAX_FRAME]; + u8 *queued_buf_uv_addr[VIDEO_MAX_FRAME]; + + enum omap_color_mode dss_mode; + + unsigned long width; + unsigned long height; + /* we don't allow to request new buffer when old buffers are + * still mmaped + */ + int mmap_count; + + spinlock_t vbq_lock; /* spinlock for videobuf queues */ + unsigned long field_count; /* field counter for videobuf_buffer */ + + bool enabled; + /* non-NULL means streaming is in progress. */ + bool streaming; + + struct v4l2_pix_format pix; + struct v4l2_window win; + + /* Lock to protect the shared data structures in ioctl */ + struct mutex lock; + + int bpp; /* bytes per pixel */ + + int ps, vr_ps, line_length, first_int, field_id; + enum v4l2_memory memory; + struct videobuf_buffer *cur_frm, *next_frm; + struct list_head dma_queue; + u8 *queued_buf_addr[VIDEO_MAX_FRAME]; + void *isr_handle; + + /* Buffer queue variables */ + struct omap_wb_device *wb; + enum v4l2_buf_type type; + struct videobuf_queue vbq; + int io_allowed; + + /*write back specific*/ + enum v4l2_writeback_source source; + enum v4l2_writeback_source_type source_type; + enum v4l2_writeback_capturemode capturemode; + bool buf_empty; +}; |