diff options
Diffstat (limited to 'drivers/media/video/mxc/output/mx27_v4l2_output.c')
-rw-r--r-- | drivers/media/video/mxc/output/mx27_v4l2_output.c | 1442 |
1 files changed, 1442 insertions, 0 deletions
diff --git a/drivers/media/video/mxc/output/mx27_v4l2_output.c b/drivers/media/video/mxc/output/mx27_v4l2_output.c new file mode 100644 index 000000000000..a00f92d8e979 --- /dev/null +++ b/drivers/media/video/mxc/output/mx27_v4l2_output.c @@ -0,0 +1,1442 @@ +/* + * Copyright 2005-2009 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*! + * @file mx27_v4l2_output.c + * + * @brief MX27 V4L2 Video Output Driver + * + * Video4Linux2 Output Device using MX27 eMMA Post-processing functionality. + * + * @ingroup MXC_V4L2_OUTPUT + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/semaphore.h> +#include <linux/poll.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-ioctl.h> + +#include "mxc_v4l2_output.h" +#include "mx27_pp.h" +#include "../drivers/video/mxc/mx2fb.h" + +#define SDC_FG_FB_FORMAT V4L2_PIX_FMT_RGB565 + +struct v4l2_output mxc_outputs[1] = { + { + .index = 0, + .name = "DISP0 Video Out", + .type = V4L2_OUTPUT_TYPE_ANALOG, /* not really correct, + but no other choice */ + .audioset = 0, + .modulator = 0, + .std = V4L2_STD_UNKNOWN}, +}; + +static int video_nr = 16; +static spinlock_t g_lock = SPIN_LOCK_UNLOCKED; +vout_data *g_vout; + +/* debug counters */ +uint32_t g_irq_cnt; +uint32_t g_buf_output_cnt; +uint32_t g_buf_q_cnt; +uint32_t g_buf_dq_cnt; + +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC +static uint32_t g_output_fb = -1; +static uint32_t g_fb_enabled = 0; +static uint32_t g_pp_ready = 0; + +static int fb_event_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + struct fb_event *event = data; + struct fb_info *info = event->info; + unsigned long lock_flags; + int blank, i; + + for (i = 0; i < num_registered_fb; i++) + if (registered_fb[i] == info) + break; + + /* + * Check if the event is sent by the framebuffer in which + * the video is displayed. + */ + if (i != g_output_fb) + return 0; + + switch (action) { + case FB_EVENT_BLANK: + blank = *(int *)event->data; + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = !blank; + if (blank && g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + case FB_EVENT_MXC_EOF: + spin_lock_irqsave(&g_lock, lock_flags); + g_fb_enabled = 1; + if (g_pp_ready) { + if (pp_enable(1)) + pr_debug("unable to enable PP\n"); + g_pp_ready = 0; + } + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + + return 0; +} + +static struct notifier_block fb_event_notifier = { + .notifier_call = fb_event_notify, +}; + +static struct notifier_block mx2fb_event_notifier = { + .notifier_call = fb_event_notify, +}; +#endif + +#define QUEUE_SIZE (MAX_FRAME_NUM + 1) +static __inline int queue_size(v4l_queue * q) +{ + if (q->tail >= q->head) + return (q->tail - q->head); + else + return ((q->tail + QUEUE_SIZE) - q->head); +} + +static __inline int queue_buf(v4l_queue * q, int idx) +{ + if (((q->tail + 1) % QUEUE_SIZE) == q->head) + return -1; /* queue full */ + q->list[q->tail] = idx; + q->tail = (q->tail + 1) % QUEUE_SIZE; + return 0; +} + +static __inline int dequeue_buf(v4l_queue * q) +{ + int ret; + if (q->tail == q->head) + return -1; /* queue empty */ + ret = q->list[q->head]; + q->head = (q->head + 1) % QUEUE_SIZE; + return ret; +} + +static __inline int peek_next_buf(v4l_queue * q) +{ + if (q->tail == q->head) + return -1; /* queue empty */ + return q->list[q->head]; +} + +static __inline unsigned long get_jiffies(struct timeval *t) +{ + struct timeval cur; + + if (t->tv_usec >= 1000000) { + t->tv_sec += t->tv_usec / 1000000; + t->tv_usec = t->tv_usec % 1000000; + } + + do_gettimeofday(&cur); + if ((t->tv_sec < cur.tv_sec) + || ((t->tv_sec == cur.tv_sec) && (t->tv_usec < cur.tv_usec))) + return jiffies; + + if (t->tv_usec < cur.tv_usec) { + cur.tv_sec = t->tv_sec - cur.tv_sec - 1; + cur.tv_usec = t->tv_usec + 1000000 - cur.tv_usec; + } else { + cur.tv_sec = t->tv_sec - cur.tv_sec; + cur.tv_usec = t->tv_usec - cur.tv_usec; + } + + return jiffies + timeval_to_jiffies(&cur); +} + +/*! + * Private function to free buffers + * + * @param bufs_paddr Array of physical address of buffers to be freed + * + * @param bufs_vaddr Array of virtual address of buffers to be freed + * + * @param num_buf Number of buffers to be freed + * + * @param size Size for each buffer to be free + * + * @return status 0 success. + */ +static int mxc_free_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + if (bufs_vaddr[i] != 0) { + dma_free_coherent(0, size, bufs_vaddr[i], + bufs_paddr[i]); + pr_debug("freed @ paddr=0x%08X\n", (u32) bufs_paddr[i]); + bufs_paddr[i] = 0; + bufs_vaddr[i] = NULL; + } + } + return 0; +} + +/*! + * Private function to allocate buffers + * + * @param bufs_paddr Output array of physical address of buffers allocated + * + * @param bufs_vaddr Output array of virtual address of buffers allocated + * + * @param num_buf Input number of buffers to allocate + * + * @param size Input size for each buffer to allocate + * + * @return status -0 Successfully allocated a buffer, -ENOBUFS failed. + */ +static int mxc_allocate_buffers(dma_addr_t bufs_paddr[], void *bufs_vaddr[], + int num_buf, int size) +{ + int i; + + for (i = 0; i < num_buf; i++) { + bufs_vaddr[i] = dma_alloc_coherent(0, size, + &bufs_paddr[i], + GFP_DMA | GFP_KERNEL); + + if (bufs_vaddr[i] == 0) { + mxc_free_buffers(bufs_paddr, bufs_vaddr, i, size); + pr_debug("dma_alloc_coherent failed.\n"); + return -ENOBUFS; + } + pr_debug("allocated @ paddr=0x%08X, size=%d.\n", + (u32) bufs_paddr[i], size); + } + + return 0; +} + +static void mxc_v4l2out_timer_handler(unsigned long arg) +{ + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = (vout_data *) arg; + + pr_debug("timer handler: %lu\n", jiffies); + + spin_lock_irqsave(&g_lock, lock_flags); + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + pr_debug("stream has stopped\n"); + goto exit0; + } + + /* + * If timer occurs before PP h/w is ready, then set the state to + * paused and the timer will be set again when next buffer is queued + * or PP completes. + */ + if (vout->ipu_buf[0] != -1) { + pr_debug("buffer is busy\n"); + vout->state = STATE_STREAM_PAUSED; + goto exit0; + } + + /* Dequeue buffer and pass to PP */ + index = dequeue_buf(&vout->ready_q); + if (index == -1) { /* no buffers ready, should never occur */ + pr_debug("mxc_v4l2out: timer - no queued buffers ready\n"); + goto exit0; + } + + g_buf_dq_cnt++; + vout->frame_count++; + vout->ipu_buf[0] = index; + + if (pp_ptr((unsigned int)vout->queue_buf_paddr[index])) { + pr_debug("unable to update buffer\n"); + goto exit0; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + if (g_fb_enabled && (vout->v4l2_fb.flags != V4L2_FBUF_FLAG_OVERLAY)) + g_pp_ready = 1; + else if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#else + if (pp_enable(1)) { + pr_debug("unable to enable PP\n"); + goto exit0; + } +#endif + pr_debug("enabled index %d\n", index); + + /* Setup timer for next buffer */ + index = peek_next_buf(&vout->ready_q); + pr_debug("next index %d\n", index); + if (index != -1) { + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } else { + vout->state = STATE_STREAM_PAUSED; + pr_debug("timer handler paused\n"); + } + + exit0: + spin_unlock_irqrestore(&g_lock, lock_flags); +} + +irqreturn_t mxc_v4l2out_pp_in_irq_handler(int irq, void *dev_id) +{ + int last_buf; + int index; + unsigned long timeout; + unsigned long lock_flags; + vout_data *vout = dev_id; + + spin_lock_irqsave(&g_lock, lock_flags); + + g_irq_cnt++; + + if ((vout->state == STATE_STREAM_OFF) + || (vout->state == STATE_STREAM_STOPPING)) { + spin_unlock_irqrestore(&g_lock, lock_flags); + return IRQ_HANDLED; + } + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + gwinfo.enabled = 1; + gwinfo.alpha_value = 255; + gwinfo.ck_enabled = 0; + gwinfo.xpos = vout->crop_current.left; + gwinfo.ypos = vout->crop_current.top; + gwinfo.base = (unsigned long)vout->display_bufs[pp_num_last()]; + gwinfo.xres = vout->crop_current.width; + gwinfo.yres = vout->crop_current.height; + gwinfo.xres_virtual = vout->crop_current.width; + gwinfo.vs_reversed = 0; + + mx2_gw_set(&gwinfo); + } + + /* Process previous buffer */ + last_buf = vout->ipu_buf[0]; + pr_debug("last_buf %d g_irq_cnt %d\n", last_buf, g_irq_cnt); + if (last_buf != -1) { + g_buf_output_cnt++; + vout->v4l2_bufs[last_buf].flags = V4L2_BUF_FLAG_DONE; + queue_buf(&vout->done_q, last_buf); + vout->ipu_buf[0] = -1; + wake_up_interruptible(&vout->v4l_bufq); + } + + /* Setup timer for next buffer, when stream has been paused */ + if ((vout->state == STATE_STREAM_PAUSED) + && ((index = peek_next_buf(&vout->ready_q)) != -1)) { + pr_debug("next index %d\n", index); + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = + vout->start_jiffies + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->state = STATE_STREAM_ON; + + if (mod_timer(&vout->output_timer, timeout)) + pr_debug("warning: timer was already set\n"); + + pr_debug("timer handler next schedule: %lu\n", timeout); + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + + return IRQ_HANDLED; +} + +/*! + * Start the output stream + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamon(vout_data * vout) +{ + unsigned long timeout; + int index; + + if (!vout) + return -EINVAL; + + if (vout->state != STATE_STREAM_OFF) + return -EBUSY; + + if (queue_size(&vout->ready_q) < 1) { + pr_debug("no buffers queued yet!\n"); + return -EINVAL; + } + + vout->ipu_buf[0] = -1; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + /* Allocate buffers for foreground */ + if (mxc_allocate_buffers(vout->display_bufs, + vout->display_bufs_vaddr, 2, + vout->display_buf_size) < 0) { + pr_debug("unable to allocate SDC FG buffers\n"); + return -ENOMEM; + } + } + + /* Configure PP */ + if (pp_cfg(vout)) { + /* Free previously allocated buffer */ + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + pr_debug("failed to config PP.\n"); + return -EINVAL; + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = vout->output_fb_num[vout->cur_disp_output]; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_register_client(&fb_event_notifier); + mx2fb_register_client(&mx2fb_event_notifier); +#endif + vout->frame_count = 0; + vout->state = STATE_STREAM_ON; + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == 0) + && (vout->v4l2_bufs[index].timestamp.tv_usec == 0)) + timeout = jiffies; + else + timeout = get_jiffies(&vout->v4l2_bufs[index].timestamp); + + if (jiffies >= timeout) { + pr_debug("warning: timer timeout already expired.\n"); + } + + vout->start_jiffies = vout->output_timer.expires = timeout; + pr_debug("STREAMON:Add timer %d timeout @ %lu jiffies, current = %lu\n", + index, timeout, jiffies); + add_timer(&vout->output_timer); + + return 0; +} + +/*! + * Shut down the voutera + * + * @param vout structure vout_data * + * + * @return status 0 Success + */ +static int mxc_v4l2out_streamoff(vout_data * vout) +{ + int i, retval = 0; + unsigned long lock_flag = 0; + + if (!vout) + return -EINVAL; + + if (vout->state == STATE_STREAM_OFF) { + return 0; + } + + spin_lock_irqsave(&g_lock, lock_flag); + + del_timer(&vout->output_timer); + pp_enable(0); /* Disable PP */ + + if (vout->state == STATE_STREAM_ON) { + vout->state = STATE_STREAM_STOPPING; + } + + spin_unlock_irqrestore(&g_lock, lock_flag); + + vout->ready_q.head = vout->ready_q.tail = 0; + vout->done_q.head = vout->done_q.tail = 0; + for (i = 0; i < vout->buffer_cnt; i++) { + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + + vout->state = STATE_STREAM_OFF; + + if (vout->v4l2_fb.flags == V4L2_FBUF_FLAG_OVERLAY) { + struct fb_gwinfo gwinfo; + + /* Disable graphic window */ + gwinfo.enabled = 0; + mx2_gw_set(&gwinfo); + } +#ifdef CONFIG_VIDEO_MXC_OUTPUT_FBSYNC + g_output_fb = -1; + g_fb_enabled = 0; + g_pp_ready = 0; + fb_unregister_client(&fb_event_notifier); + mx2fb_unregister_client(&mx2fb_event_notifier); +#endif + + mxc_free_buffers(vout->display_bufs, vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + return retval; +} + +/* + * Valid whether the palette is supported + * + * @param palette V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return 1 if supported, 0 if failed + */ +static inline int valid_mode(u32 palette) +{ + return (palette == V4L2_PIX_FMT_YUV420); +} + +/* + * Returns bits per pixel for given pixel format + * + * @param pixelformat V4L2_PIX_FMT_RGB565, V4L2_PIX_FMT_BGR24 or V4L2_PIX_FMT_BGR32 + * + * @return bits per pixel of pixelformat + */ +static u32 fmt_to_bpp(u32 pixelformat) +{ + u32 bpp; + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565: + bpp = 16; + break; + case V4L2_PIX_FMT_BGR24: + case V4L2_PIX_FMT_RGB24: + bpp = 24; + break; + case V4L2_PIX_FMT_BGR32: + case V4L2_PIX_FMT_RGB32: + bpp = 32; + break; + default: + bpp = 8; + break; + } + return bpp; +} + +/* + * V4L2 - Handles VIDIOC_G_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_g_fmt(vout_data * vout, struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + return -EINVAL; + } + *f = vout->v2f; + return 0; +} + +/* + * V4L2 - Handles VIDIOC_S_FMT Ioctl + * + * @param vout structure vout_data * + * + * @param v4l2_format structure v4l2_format * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_v4l2out_s_fmt(vout_data * vout, struct v4l2_format *f) +{ + int retval = 0; + u32 size = 0; + u32 bytesperline; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + goto err0; + } + if (!valid_mode(f->fmt.pix.pixelformat)) { + pr_debug("pixel format not supported\n"); + retval = -EINVAL; + goto err0; + } + + bytesperline = (f->fmt.pix.width * fmt_to_bpp(f->fmt.pix.pixelformat)) / + 8; + if (f->fmt.pix.bytesperline < bytesperline) { + f->fmt.pix.bytesperline = bytesperline; + } else { + bytesperline = f->fmt.pix.bytesperline; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV422P: + /* byteperline for YUV planar formats is for + Y plane only */ + size = bytesperline * f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_YUV420: + size = (bytesperline * f->fmt.pix.height * 3) / 2; + break; + default: + size = bytesperline * f->fmt.pix.height; + break; + } + + /* Return the actual size of the image to the app */ + f->fmt.pix.sizeimage = size; + + vout->v2f.fmt.pix.sizeimage = size; + vout->v2f.fmt.pix.width = f->fmt.pix.width; + vout->v2f.fmt.pix.height = f->fmt.pix.height; + vout->v2f.fmt.pix.pixelformat = f->fmt.pix.pixelformat; + vout->v2f.fmt.pix.bytesperline = f->fmt.pix.bytesperline; + + retval = 0; + err0: + return retval; +} + +/* + * V4L2 - Handles VIDIOC_G_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_get_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + return (vout->rotate & IPU_ROTATE_HORIZ_FLIP) ? 1 : 0; + case V4L2_CID_VFLIP: + return (vout->rotate & IPU_ROTATE_VERT_FLIP) ? 1 : 0; + case (V4L2_CID_PRIVATE_BASE + 1): + return vout->rotate; + default: + return -EINVAL; + } +} + +/* + * V4L2 - Handles VIDIOC_S_CTRL Ioctl + * + * @param vout structure vout_data * + * + * @param c structure v4l2_control * + * + * @return status 0 success, EINVAL failed + */ +static int mxc_set_v42lout_control(vout_data * vout, struct v4l2_control *c) +{ + switch (c->id) { + case V4L2_CID_HFLIP: + case V4L2_CID_VFLIP: + case V4L2_CID_MXC_ROT: + return 0; + default: + return -EINVAL; + } + return 0; +} + +/*! + * V4L2 interface - open function + * + * @param inode structure inode * + * + * @param file structure file * + * + * @return status 0 success, ENODEV invalid device instance, + * ENODEV timeout, ERESTARTSYS interrupted by user + */ +static int mxc_v4l2out_open(struct inode *inode, struct file *file) +{ + struct video_device *dev = video_devdata(file); + vout_data *vout = video_get_drvdata(dev); + int err; + + if (!vout) { + pr_info("Internal error, vout_data not found!\n"); + return -ENODEV; + } + + down(&vout->busy_lock); + + err = -EINTR; + if (signal_pending(current)) + goto oops; + + if (vout->open_count++ == 0) { + pp_init(vout); + + init_waitqueue_head(&vout->v4l_bufq); + + init_timer(&vout->output_timer); + vout->output_timer.function = mxc_v4l2out_timer_handler; + vout->output_timer.data = (unsigned long)vout; + + vout->state = STATE_STREAM_OFF; + g_irq_cnt = g_buf_output_cnt = g_buf_q_cnt = g_buf_dq_cnt = 0; + + } + + file->private_data = dev; + up(&vout->busy_lock); + return 0; + + oops: + up(&vout->busy_lock); + return err; +} + +/*! + * V4L2 interface - close function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @return 0 success + */ +static int mxc_v4l2out_close(struct inode *inode, struct file *file) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + if (--vout->open_count == 0) { + pr_debug("release resource\n"); + + pp_exit(vout); + if (vout->state != STATE_STREAM_OFF) + mxc_v4l2out_streamoff(vout); + + file->private_data = NULL; + + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, vout->queue_buf_size); + vout->buffer_cnt = 0; + mxc_free_buffers(vout->display_bufs, + vout->display_bufs_vaddr, + 2, vout->display_buf_size); + + /* capture off */ + wake_up_interruptible(&vout->v4l_bufq); + } + + return 0; +} + +/*! + * V4L2 interface - ioctl function + * + * @param inode struct inode * + * + * @param file struct file * + * + * @param ioctlnr unsigned int + * + * @param arg void * + * + * @return 0 success, ENODEV for invalid device instance, + * -1 for other errors. + */ +static int +mxc_v4l2out_do_ioctl(struct inode *inode, struct file *file, + unsigned int ioctlnr, void *arg) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + int retval = 0; + int i = 0; + + if (!vout) + return -EBADF; + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EBUSY; + + switch (ioctlnr) { + case VIDIOC_QUERYCAP: + { + struct v4l2_capability *cap = arg; + strcpy(cap->driver, "mxc_v4l2_output"); + cap->version = 0; + cap->capabilities = + V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + cap->card[0] = '\0'; + cap->bus_info[0] = '\0'; + retval = 0; + break; + } + case VIDIOC_G_FMT: + { + struct v4l2_format *gf = arg; + retval = mxc_v4l2out_g_fmt(vout, gf); + break; + } + case VIDIOC_S_FMT: + { + struct v4l2_format *sf = arg; + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + retval = mxc_v4l2out_s_fmt(vout, sf); + break; + } + case VIDIOC_REQBUFS: + { + struct v4l2_requestbuffers *req = arg; + if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (req->memory != V4L2_MEMORY_MMAP)) { + pr_debug + ("VIDIOC_REQBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + + if (req->count == 0) + mxc_v4l2out_streamoff(vout); + + if (vout->state == STATE_STREAM_OFF) { + if (vout->queue_buf_paddr[0] != 0) { + mxc_free_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + pr_debug + ("VIDIOC_REQBUFS: freed buffers\n"); + } + vout->buffer_cnt = 0; + } else { + pr_debug("VIDIOC_REQBUFS: Buffer is in use\n"); + retval = -EBUSY; + break; + } + + if (req->count == 0) + break; + + if (req->count < MIN_FRAME_NUM) { + req->count = MIN_FRAME_NUM; + } else if (req->count > MAX_FRAME_NUM) { + req->count = MAX_FRAME_NUM; + } + vout->buffer_cnt = req->count; + vout->queue_buf_size = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + + retval = mxc_allocate_buffers(vout->queue_buf_paddr, + vout->queue_buf_vaddr, + vout->buffer_cnt, + vout->queue_buf_size); + if (retval < 0) + break; + + /* Init buffer queues */ + vout->done_q.head = 0; + vout->done_q.tail = 0; + vout->ready_q.head = 0; + vout->ready_q.tail = 0; + + for (i = 0; i < vout->buffer_cnt; i++) { + memset(&(vout->v4l2_bufs[i]), 0, + sizeof(vout->v4l2_bufs[i])); + vout->v4l2_bufs[i].flags = 0; + vout->v4l2_bufs[i].memory = V4L2_MEMORY_MMAP; + vout->v4l2_bufs[i].index = i; + vout->v4l2_bufs[i].type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + vout->v4l2_bufs[i].length = + PAGE_ALIGN(vout->v2f.fmt.pix.sizeimage); + vout->v4l2_bufs[i].m.offset = + (unsigned long)vout->queue_buf_paddr[i]; + vout->v4l2_bufs[i].timestamp.tv_sec = 0; + vout->v4l2_bufs[i].timestamp.tv_usec = 0; + } + break; + } + case VIDIOC_QUERYBUF: + { + struct v4l2_buffer *buf = arg; + u32 type = buf->type; + int index = buf->index; + + if ((type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt)) { + pr_debug + ("VIDIOC_QUERYBUFS: incorrect buffer type\n"); + retval = -EINVAL; + break; + } + down(&vout->param_lock); + memcpy(buf, &(vout->v4l2_bufs[index]), sizeof(*buf)); + up(&vout->param_lock); + break; + } + case VIDIOC_QBUF: + { + struct v4l2_buffer *buf = arg; + int index = buf->index; + unsigned long lock_flags; + unsigned long timeout; + + if ((buf->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || + (index >= vout->buffer_cnt) || (buf->flags != 0)) { + retval = -EINVAL; + break; + } + + pr_debug("VIDIOC_QBUF: %d\n", buf->index); + + spin_lock_irqsave(&g_lock, lock_flags); + + memcpy(&(vout->v4l2_bufs[index]), buf, sizeof(*buf)); + vout->v4l2_bufs[index].flags |= V4L2_BUF_FLAG_QUEUED; + + g_buf_q_cnt++; + queue_buf(&vout->ready_q, index); + + if (vout->state == STATE_STREAM_PAUSED) { + index = peek_next_buf(&vout->ready_q); + + /* if timestamp is 0, then default to 30fps */ + if ((vout->v4l2_bufs[index].timestamp.tv_sec == + 0) + && (vout->v4l2_bufs[index].timestamp. + tv_usec == 0)) + timeout = + vout->start_jiffies + + vout->frame_count * HZ / 30; + else + timeout = + get_jiffies(&vout->v4l2_bufs[index]. + timestamp); + + if (jiffies >= timeout) { + pr_debug + ("warning: timer timeout already expired.\n"); + } + + vout->output_timer.expires = timeout; + pr_debug + ("QBUF:Add timer %d timeout @ %lu jiffies, " + "current = %lu\n", index, timeout, + jiffies); + add_timer(&vout->output_timer); + vout->state = STATE_STREAM_ON; + } + + spin_unlock_irqrestore(&g_lock, lock_flags); + break; + } + case VIDIOC_DQBUF: + { + struct v4l2_buffer *buf = arg; + int idx; + + pr_debug("VIDIOC_DQBUF: q size = %d\n", + queue_size(&vout->done_q)); + + if ((queue_size(&vout->done_q) == 0) && + (file->f_flags & O_NONBLOCK)) { + retval = -EAGAIN; + break; + } + + if (!wait_event_interruptible_timeout(vout->v4l_bufq, + queue_size(&vout-> + done_q) + != 0, 10 * HZ)) { + pr_debug("VIDIOC_DQBUF: timeout\n"); + retval = -ETIME; + break; + } else if (signal_pending(current)) { + pr_debug("VIDIOC_DQBUF: interrupt received\n"); + retval = -ERESTARTSYS; + break; + } + idx = dequeue_buf(&vout->done_q); + if (idx == -1) { /* No frame free */ + pr_debug + ("VIDIOC_DQBUF: no free buffers, returning\n"); + retval = -EAGAIN; + break; + } + if ((vout->v4l2_bufs[idx].flags & V4L2_BUF_FLAG_DONE) == + 0) + pr_debug + ("VIDIOC_DQBUF: buffer in done q, but not " + "flagged as done\n"); + + vout->v4l2_bufs[idx].flags = 0; + memcpy(buf, &(vout->v4l2_bufs[idx]), sizeof(*buf)); + pr_debug("VIDIOC_DQBUF: %d\n", buf->index); + break; + } + case VIDIOC_STREAMON: + { + retval = mxc_v4l2out_streamon(vout); + break; + } + case VIDIOC_STREAMOFF: + { + retval = mxc_v4l2out_streamoff(vout); + break; + } + case VIDIOC_G_CTRL: + { + retval = mxc_get_v42lout_control(vout, arg); + break; + } + case VIDIOC_S_CTRL: + { + retval = mxc_set_v42lout_control(vout, arg); + break; + } + case VIDIOC_CROPCAP: + { + struct v4l2_cropcap *cap = arg; + + if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + cap->bounds = vout->crop_bounds[vout->cur_disp_output]; + cap->defrect = vout->crop_bounds[vout->cur_disp_output]; + retval = 0; + break; + } + case VIDIOC_G_CROP: + { + struct v4l2_crop *crop = arg; + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + crop->c = vout->crop_current; + break; + } + case VIDIOC_S_CROP: + { + struct v4l2_crop *crop = arg; + struct v4l2_rect *b = + &(vout->crop_bounds[vout->cur_disp_output]); + + if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) { + retval = -EINVAL; + break; + } + if (crop->c.height < 0) { + retval = -EINVAL; + break; + } + if (crop->c.width < 0) { + retval = -EINVAL; + break; + } + + if (crop->c.top < b->top) + crop->c.top = b->top; + if (crop->c.top > b->top + b->height) + crop->c.top = b->top + b->height; + if (crop->c.height > b->top - crop->c.top + b->height) + crop->c.height = + b->top - crop->c.top + b->height; + + if (crop->c.left < b->left) + crop->c.top = b->left; + if (crop->c.left > b->left + b->width) + crop->c.top = b->left + b->width; + if (crop->c.width > b->left - crop->c.left + b->width) + crop->c.width = + b->left - crop->c.left + b->width; + + /* stride line limitation */ + crop->c.height -= crop->c.height % 8; + crop->c.width -= crop->c.width % 8; + + vout->crop_current = crop->c; + + vout->display_buf_size = vout->crop_current.width * + vout->crop_current.height; + vout->display_buf_size *= + fmt_to_bpp(SDC_FG_FB_FORMAT) / 8; + break; + } + case VIDIOC_ENUMOUTPUT: + { + struct v4l2_output *output = arg; + + if ((output->index >= 2) || + (vout->output_enabled[output->index] == false)) { + retval = -EINVAL; + break; + } + + *output = mxc_outputs[0]; + output->name[4] = '0' + output->index; + break; + } + case VIDIOC_G_OUTPUT: + { + int *p_output_num = arg; + + *p_output_num = vout->cur_disp_output; + break; + } + case VIDIOC_S_OUTPUT: + { + int *p_output_num = arg; + + if ((*p_output_num >= 2) || + (vout->output_enabled[*p_output_num] == false)) { + retval = -EINVAL; + break; + } + + if (vout->state != STATE_STREAM_OFF) { + retval = -EBUSY; + break; + } + + vout->cur_disp_output = *p_output_num; + break; + } + case VIDIOC_G_FBUF: + { + struct v4l2_framebuffer *fb = arg; + *fb = vout->v4l2_fb; + break; + } + case VIDIOC_S_FBUF: + { + struct v4l2_framebuffer *fb = arg; + vout->v4l2_fb = *fb; + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + break; + } + case VIDIOC_ENUM_FMT: + case VIDIOC_TRY_FMT: + case VIDIOC_QUERYCTRL: + case VIDIOC_G_PARM: + case VIDIOC_ENUMSTD: + case VIDIOC_G_STD: + case VIDIOC_S_STD: + case VIDIOC_G_TUNER: + case VIDIOC_S_TUNER: + case VIDIOC_G_FREQUENCY: + case VIDIOC_S_FREQUENCY: + default: + retval = -EINVAL; + break; + } + + up(&vout->busy_lock); + return retval; +} + +/* + * V4L2 interface - ioctl function + * + * @return None + */ +static int +mxc_v4l2out_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return video_usercopy(inode, file, cmd, arg, mxc_v4l2out_do_ioctl); +} + +/*! + * V4L2 interface - mmap function + * + * @param file structure file * + * + * @param vma structure vm_area_struct * + * + * @return status 0 Success, EINTR busy lock error, + * ENOBUFS remap_page error + */ +static int mxc_v4l2out_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct video_device *dev = file->private_data; + unsigned long start = vma->vm_start; + unsigned long size = vma->vm_end - vma->vm_start; + int res = 0; + vout_data *vout = video_get_drvdata(dev); + + /* make this _really_ smp-safe */ + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + /* make buffers write-thru cacheable */ + vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) & + ~L_PTE_BUFFERABLE); + + if (remap_pfn_range(vma, start, vma->vm_pgoff, size, vma->vm_page_prot)) { + pr_debug("mxc_mmap(V4L)i - remap_pfn_range failed\n"); + res = -ENOBUFS; + goto mxc_mmap_exit; + } + + mxc_mmap_exit: + up(&vout->busy_lock); + return res; +} + +/*! + * V4L2 interface - poll function + * + * @param file structure file * + * + * @param wait structure poll_table * + * + * @return status POLLIN | POLLRDNORM + */ +static unsigned int mxc_v4l2out_poll(struct file *file, poll_table * wait) +{ + struct video_device *dev = file->private_data; + vout_data *vout = video_get_drvdata(dev); + + wait_queue_head_t *queue = NULL; + int res = POLLIN | POLLRDNORM; + + if (down_interruptible(&vout->busy_lock)) + return -EINTR; + + queue = &vout->v4l_bufq; + poll_wait(file, queue, wait); + + up(&vout->busy_lock); + return res; +} + +static struct file_operations mxc_v4l2out_fops = { + .owner = THIS_MODULE, + .open = mxc_v4l2out_open, + .release = mxc_v4l2out_close, + .ioctl = mxc_v4l2out_ioctl, + .mmap = mxc_v4l2out_mmap, + .poll = mxc_v4l2out_poll, +}; + +static struct video_device mxc_v4l2out_template = { + .name = "MXC Video Output", + .vfl_type = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING, + .fops = &mxc_v4l2out_fops, + .release = video_device_release, +}; + +/*! + * Probe routine for the framebuffer driver. It is called during the + * driver binding process. The following functions are performed in + * this routine: Framebuffer initialization, Memory allocation and + * mapping, Framebuffer registration, IPU initialization. + * + * @return Appropriate error code to the kernel common code + */ +static int mxc_v4l2out_probe(struct platform_device *pdev) +{ + int i; + vout_data *vout; + + /* + * Allocate sufficient memory for the fb structure + */ + g_vout = vout = kmalloc(sizeof(vout_data), GFP_KERNEL); + + if (!vout) + return 0; + + memset(vout, 0, sizeof(vout_data)); + + vout->video_dev = video_device_alloc(); + if (vout->video_dev == NULL) + return -1; + vout->video_dev->minor = -1; + + *(vout->video_dev) = mxc_v4l2out_template; + + /* register v4l device */ + if (video_register_device(vout->video_dev, + VFL_TYPE_GRABBER, video_nr) == -1) { + pr_debug("video_register_device failed\n"); + return 0; + } + pr_debug("mxc_v4l2out: registered device video%d\n", + vout->video_dev->minor & 0x1f); + + video_set_drvdata(vout->video_dev, vout); + + init_MUTEX(&vout->param_lock); + init_MUTEX(&vout->busy_lock); + + /* setup outputs and cropping */ + vout->cur_disp_output = -1; + for (i = 0; i < num_registered_fb; i++) { + char *idstr = registered_fb[i]->fix.id; + if (strncmp(idstr, "DISP", 4) == 0) { + int disp_num = i; + vout->crop_bounds[disp_num].left = 0; + vout->crop_bounds[disp_num].top = 0; + vout->crop_bounds[disp_num].width = + registered_fb[i]->var.xres; + vout->crop_bounds[disp_num].height = + registered_fb[i]->var.yres; + vout->output_enabled[disp_num] = true; + vout->output_fb_num[disp_num] = i; + if (vout->cur_disp_output == -1) + vout->cur_disp_output = disp_num; + } + + } + vout->crop_current = vout->crop_bounds[vout->cur_disp_output]; + + /* Setup framebuffer parameters */ + vout->v4l2_fb.capability = V4L2_FBUF_CAP_EXTERNOVERLAY; + vout->v4l2_fb.flags = V4L2_FBUF_FLAG_PRIMARY; + + return 0; +} + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct platform_driver mxc_v4l2out_driver = { + .driver = { + .name = "MXC Video Output", + .owner = THIS_MODULE, + .bus = &platform_bus_type, + }, + .probe = mxc_v4l2out_probe, + .remove = NULL, +}; + +static void camera_platform_release(struct device *device) +{ +} + +static struct platform_device mxc_v4l2out_device = { + .name = "MXC Video Output", + .dev = { + .release = camera_platform_release, + }, + .id = 0, +}; + +/*! + * mxc v4l2 init function + * + */ +static int mxc_v4l2out_init(void) +{ + u8 err = 0; + + err = platform_driver_register(&mxc_v4l2out_driver); + if (err == 0) { + platform_device_register(&mxc_v4l2out_device); + } + return err; +} + +/*! + * mxc v4l2 cleanup function + * + */ +static void mxc_v4l2out_clean(void) +{ + pr_debug("unregistering video\n"); + + video_unregister_device(g_vout->video_dev); + + platform_driver_unregister(&mxc_v4l2out_driver); + platform_device_unregister(&mxc_v4l2out_device); + kfree(g_vout); + g_vout = NULL; +} + +module_init(mxc_v4l2out_init); +module_exit(mxc_v4l2out_clean); + +module_param(video_nr, int, 0444); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("V4L2-driver for MXC video output"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("video"); |