/* * drivers/media/video/omap/v4gfx.c * * Copyright (C) 2010 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* private ioctls */ #include #include #include #include #include #include "v4gfx.h" #include "gfx_bc.h" #define V4GFX_WAIT_DEQUE 1 /* Poll buffer sync status during dq */ #define V4GFX_WAIT_UNLOCK 2 /* Poll buffer sync status from render loop */ /* * V4GFX_WAITMETHOD is used to select between how we wait for SGX to release * buffers sent to it. */ /* #define V4GFX_WAITMETHOD V4GFX_WAIT_DEQUE */ #define V4GFX_WAITMETHOD V4GFX_WAIT_UNLOCK #define VID_MAX_WIDTH 2048 /* Largest width */ #define VID_MAX_HEIGHT 2048 /* Largest height */ #define VID_MIN_WIDTH 0 #define VID_MIN_HEIGHT 0 #define V4GFX_FRAME_UNLOCK_TIMEOUT 16 /* ms */ /* * This will enable dumping of the mappings obtain */ #ifdef V4L2GFX_DUMPMMAP #define DUMPMMAP(msg, k, vma, m, pos, p) \ printk(KERN_NOTICE \ "%s: vm_start+%d = 0x%lx, dma->vmalloc+%d = 0x%lx, w=0x%x\n", \ msg, k, vma->vm_start + k, m, (pos + m), p); #else #define DUMPMMAP(msg, k, vma, m, pos, p) #endif static struct videobuf_queue_ops video_vbq_ops; static u32 v4gfx_calc_buffer_size( int bpp, u32 width, u32 height, u32 pixelformat); static u32 v4gfx_calc_stride(int bpp, u32 width); /* * List of image formats supported by the SGX buffer-class api */ static const struct v4l2_fmtdesc gfx_bc_formats[] = { { /* Note: V4L2 defines RGB565 as: * * Byte 0 Byte 1 * g2 g1 g0 r4 r3 r2 r1 r0 b4 b3 b2 b1 b0 g5 g4 g3 * * OMAP video pipelines interpret RGB565 as: * * Byte 0 Byte 1 * g2 g1 g0 b4 b3 b2 b1 b0 r4 r3 r2 r1 r0 g5 g4 g3 * * GFX ?? TODO */ .description = "RGB565, le", .pixelformat = V4L2_PIX_FMT_RGB565, }, { .description = "RGB32, le", .pixelformat = V4L2_PIX_FMT_RGB32, }, { .description = "YUYV (YUV 4:2:2), packed", .pixelformat = V4L2_PIX_FMT_YUYV, }, { .description = "UYVY, packed", .pixelformat = V4L2_PIX_FMT_UYVY, }, { .description = "NV12 - YUV420 format", .pixelformat = V4L2_PIX_FMT_NV12, }, }; #define NUM_OUTPUT_FORMATS (ARRAY_SIZE(gfx_bc_formats)) int v4gfx_try_format(struct v4l2_pix_format *pix) { int ifmt, bpp = 0; pix->height = clamp(pix->height, (u32)VID_MIN_HEIGHT, (u32)VID_MAX_HEIGHT); pix->width = clamp(pix->width, (u32)VID_MIN_WIDTH, (u32)VID_MAX_WIDTH); for (ifmt = 0; ifmt < NUM_OUTPUT_FORMATS; ifmt++) { if (pix->pixelformat == gfx_bc_formats[ifmt].pixelformat) break; } if (ifmt >= NUM_OUTPUT_FORMATS) ifmt = 0; /* Default V4L2_PIX_FMT_RGB565 */ pix->pixelformat = gfx_bc_formats[ifmt].pixelformat; pix->field = V4L2_FIELD_ANY; pix->priv = 0; switch (pix->pixelformat) { case V4L2_PIX_FMT_YUYV: case V4L2_PIX_FMT_UYVY: default: pix->colorspace = V4L2_COLORSPACE_JPEG; bpp = YUYV_BPP; break; case V4L2_PIX_FMT_RGB565: case V4L2_PIX_FMT_RGB565X: pix->colorspace = V4L2_COLORSPACE_SRGB; bpp = RGB565_BPP; break; case V4L2_PIX_FMT_RGB24: pix->colorspace = V4L2_COLORSPACE_SRGB; bpp = RGB24_BPP; break; case V4L2_PIX_FMT_RGB32: case V4L2_PIX_FMT_BGR32: pix->colorspace = V4L2_COLORSPACE_SRGB; bpp = RGB32_BPP; break; case V4L2_PIX_FMT_NV12: pix->colorspace = V4L2_COLORSPACE_JPEG; bpp = 1; /* 12bits per pixel, 1 byte for Y */ break; } pix->bytesperline = v4gfx_calc_stride(bpp, pix->width); pix->sizeimage = v4gfx_calc_buffer_size(bpp, pix->width, pix->height, pix->pixelformat); if (V4L2_PIX_FMT_NV12 == pix->pixelformat) pix->sizeimage += pix->sizeimage >> 1; return bpp; } void v4gfx_acquire_timer(unsigned long arg) { struct v4gfx_device *vout = (struct v4gfx_device *)arg; set_bit(1, &vout->acquire_timedout); } #if V4GFX_WAITMETHOD == V4GFX_WAIT_DEQUE static struct videobuf_buffer *v4gfx_get_next_syncframe( struct v4gfx_device *vout) { struct videobuf_buffer *buf; mutex_lock(&vout->lock); if (list_empty(&vout->sync_queue)) { mutex_unlock(&vout->lock); return NULL; } buf = list_entry(vout->sync_queue.next, struct videobuf_buffer, queue); mutex_unlock(&vout->lock); return buf; } static int v4gfx_wait_on_pending(struct v4gfx_device *vout, int bufidx) { int dqable = 0; int iteration = 0; do { dqable = bc_sync_status(0, bufidx); if (!dqable) { /* printk("w-on %d [%d]\n", bufidx, iteration); */ if (iteration++ < V4GFX_FRAME_UNLOCK_TIMEOUT) { msleep(1); /* milliseconds */ } else { /*printk("t-o %d\n", bufidx); */ break; /* Timed out */ } } /* else { printk("dq-o %d\n", bufidx); } */ } while (!dqable); return dqable; } static void v4gfx_done_syncframe(struct v4gfx_device *vout, struct videobuf_buffer *sync_frame) { struct timeval timevalue = {0}; unsigned long flags; mutex_lock(&vout->lock); spin_lock_irqsave(&vout->vbq_lock, flags); list_del(&sync_frame->queue); do_gettimeofday(&timevalue); sync_frame->ts = timevalue; sync_frame->state = VIDEOBUF_DONE; wake_up_interruptible(&sync_frame->done); spin_unlock_irqrestore(&vout->vbq_lock, flags); mutex_unlock(&vout->lock); } #endif /* V4GFX_WAIT_DEQUE */ static u32 v4gfx_calc_stride(int bpp, u32 width) { return PAGE_ALIGN(width * bpp); } static u32 v4gfx_calc_buffer_size( int bpp, u32 width, u32 height, u32 pixelformat) { int stride; stride = v4gfx_calc_stride(bpp, width); /* i is the block-width - either 4K or 8K, depending upon input width*/ /* for NV12 format, buffer is height + height / 2*/ if (V4L2_PIX_FMT_NV12 == pixelformat) return height * 3/2 * stride; else return height * stride; } void v4gfx_buffer_array_free(struct v4gfx_device *vout, int cnt) { /* Fn should be robust and callable with args in a dubious state */ int i; if (!vout || !cnt) return; if (vout->buf_phys_addr_array) { for (i = 0; i < cnt; i++) kfree(vout->buf_phys_addr_array[i]); kfree(vout->buf_phys_addr_array); vout->buf_phys_addr_array = NULL; } } /* * Allocate a buffer array for all the requested buffers * If there is an allocation failure the function will clean up after itself */ static int v4gfx_buffer_array_realloc(struct v4gfx_device *vout, int oldcnt, int newcnt) { int i; if (vout->buf_phys_addr_array) v4gfx_buffer_array_free(vout, oldcnt); vout->buf_phys_addr_array = kzalloc(sizeof(unsigned long *) * newcnt, GFP_KERNEL); if (!vout->buf_phys_addr_array) return -ENOMEM; /* 2048 is the max image height, 2 = (2048 * 4) / CPU_PAGE_SIZE */ for (i = 0; i < newcnt; i++) { vout->buf_phys_addr_array[i] = kmalloc(sizeof(unsigned long) * 2048 * 2, GFP_KERNEL); if (!vout->buf_phys_addr_array[i]) { v4gfx_buffer_array_free(vout, newcnt); return -ENOMEM; } } return 0; } static void v4gfx_buffer_array_fill( struct v4gfx_device *vout, int bufno, unsigned long tiler_paddr_in, unsigned long tiler_paddr_uv_in) { int buf_phys_idx = 0; int m = 0, i; int cpu_pgwidth; int tiler_increment; v4gfx_tiler_image_incr(vout, &cpu_pgwidth, &tiler_increment); for (i = 0; i < vout->pix.height; i++) { unsigned long pg, pgend, tiler_paddr; tiler_paddr = tiler_paddr_in+m; pg = tiler_paddr; pgend = pg + cpu_pgwidth; do { GFXLOGA(2, "%d %d: = %lx\n", bufno, buf_phys_idx, (long)pg); vout->buf_phys_addr_array[bufno][buf_phys_idx] = pg; pg += 4096; buf_phys_idx++; } while (pg < pgend); m += tiler_increment; } if (V4L2_PIX_FMT_NV12 == vout->pix.pixelformat) { m = 0; v4gfx_tiler_image_incr_uv(vout, &tiler_increment); /* UV buffer is height / 2 */ for (i = 0; i < vout->pix.height / 2; i++) { unsigned long pg; pg = tiler_paddr_uv_in+m; vout->buf_phys_addr_array[bufno][buf_phys_idx] = pg; m += tiler_increment; buf_phys_idx++; } GFXLOGA(1, "nv12 uv: 0x%lx\n", tiler_paddr_uv_in); m += tiler_increment; } } static int v4gfx_frame_lock(struct v4gfx_device *vout, int *bufid) { struct videobuf_buffer *oldbuf = NULL; #if V4GFX_WAITMETHOD == V4GFX_WAIT_UNLOCK struct timeval timevalue = {0}; #else /* V4GFX_WAIT_DEQUE */ int oldbufid = -1; #endif unsigned long flags; int rv = 0; mutex_lock(&vout->lock); spin_lock_irqsave(&vout->vbq_lock, flags); if (!vout->streaming || !vout->cur_frm) { GFXLOG(1, V4L2DEV(vout), "%s: ERROR: device not streaming yet\n", __func__); rv = -EAGAIN; goto unlock; } /* vout->cur_frm must be set if streaming */ if (vout->cur_frm == vout->locked_frm) { /* * If this frame has been locked before we will * attempt to get the next buffer in the dma queue. * If there is a next buffer, mark the locked * buffer as done and then promote the next buffer * to the current buffer whilst locking it in the * process. */ if (list_empty(&vout->dma_queue)) { *bufid = vout->cur_frm->i; /* * We can't do anything else here, it will be upto * the consumer application to decide whether it wants * to re-render the texture which depends on what the * app is doing. */ goto unlock; } /* Deactivate the cur_frm */ oldbuf = vout->cur_frm; vout->cur_frm = list_entry(vout->dma_queue.next, struct videobuf_buffer, queue); list_del(&vout->cur_frm->queue); vout->cur_frm->state = VIDEOBUF_ACTIVE; GFXLOG(2, V4L2DEV(vout), "Active frame %d\n", vout->cur_frm->i); vout->locked_frm = vout->cur_frm; #if V4GFX_WAITMETHOD == V4GFX_WAIT_UNLOCK /* * Mark the previous current buffer done and release it for * dequeue */ do_gettimeofday(&timevalue); oldbuf->ts = timevalue; oldbuf->state = VIDEOBUF_DONE; wake_up_interruptible(&oldbuf->done); #else /* V4GFX_WAIT_DEQUE */ oldbufid = oldbuf->i; list_add_tail(&oldbuf->queue, &vout->sync_queue); wake_up_interruptible(&vout->sync_done); #endif } else { /* First time we've tried to lock this frame */ vout->locked_frm = vout->cur_frm; /* We be marked for dequeue next time */ } *bufid = vout->locked_frm->i; unlock: spin_unlock_irqrestore(&vout->vbq_lock, flags); mutex_unlock(&vout->lock); #if V4GFX_WAITMETHOD == V4GFX_WAIT_DEQUE /* if (oldbufid != -1) printk("sync_queue + %d\n", oldbufid); */ #endif return rv; } static int v4gfx_frame_unlock(struct v4gfx_device *vout, int bufidx) { struct videobuf_buffer *vbuf; int rv = 0; #if V4GFX_WAITMETHOD == V4GFX_WAIT_UNLOCK int iteration = 0; #endif mutex_lock(&vout->lock); vbuf = vout->locked_frm; if (!vbuf) { GFXLOG(1, V4L2DEV(vout), "%s: ERROR: trying to unlock a non-existent frame\n", __func__); rv = -EINVAL; } else if (vbuf->i != bufidx) { GFXLOG(1, V4L2DEV(vout), "%s: ERROR: trying to unlock wrong frame %d %d\n", __func__, vbuf->i, bufidx); rv = -EINVAL; } mutex_unlock(&vout->lock); #if V4GFX_WAITMETHOD == V4GFX_WAIT_UNLOCK if (rv != 0) goto end; do { /* * Interrogate the buffer class synch data buffer to see if SGX * is done with this buffer */ rv = bc_sync_status(0, bufidx); if (rv == 0) { if (iteration++ < V4GFX_FRAME_UNLOCK_TIMEOUT) msleep(1); /* milliseconds */ } } while (rv == 0); if (iteration >= V4GFX_FRAME_UNLOCK_TIMEOUT) { printk("%s: INFO: timed out\n", __func__); rv = -ETIMEDOUT; } else rv = 0; end: #endif /* V4GFX_WAIT_UNLOCK */ return rv; } /* * 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 vbq_ops_buf_setup(struct videobuf_queue *q, unsigned int *count, unsigned int *size) { struct v4gfx_device *vout = q->priv_data; int rv = 0; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); if (!vout || (V4L2_BUF_TYPE_VIDEO_OUTPUT != q->type)) { rv = -EINVAL; goto end; } *size = vout->buffer_size = v4gfx_calc_buffer_size( vout->bpp, vout->pix.width, vout->pix.height, vout->pix.pixelformat); GFXLOG(1, V4L2DEV(vout), "height=%d, size=%d\n", vout->pix.height, *size); if (v4gfx_tiler_buffer_setup(vout, count, 0, &vout->pix)) { rv = -ENOMEM; goto end; } end: GFXLOG(1, V4L2DEV(vout), "Exiting %s\n", __func__); return rv; } /* * 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. */ static int vbq_ops_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, enum v4l2_field field) { struct v4gfx_device *vout = q->priv_data; if (VIDEOBUF_NEEDS_INIT == vb->state) { vb->width = vout->pix.width; vb->height = vout->pix.height; vb->size = vb->width * vb->height * vout->bpp; vb->field = field; } vb->state = VIDEOBUF_PREPARED; return 0; } /* * Buffer queue function 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 vbq_ops_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) { struct v4gfx_device *vout = q->priv_data; list_add_tail(&vb->queue, &vout->dma_queue); vb->state = VIDEOBUF_QUEUED; } /* * Buffer release function is called from videobuf layer to release buffer * which are already allocated */ static void vbq_ops_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) { struct v4gfx_device *vout = q->priv_data; vb->state = VIDEOBUF_NEEDS_INIT; if (V4L2_MEMORY_MMAP != vout->memory) return; } /* * File operations */ static void v4gfx_vm_open(struct vm_area_struct *vma) { struct v4gfx_device *vout = vma->vm_private_data; GFXLOG(1, V4L2DEV(vout), "vm_open [vma=%08lx-%08lx]\n", vma->vm_start, vma->vm_end); vout->mmap_count++; } static void v4gfx_vm_close(struct vm_area_struct *vma) { struct v4gfx_device *vout = vma->vm_private_data; GFXLOG(1, V4L2DEV(vout), "vm_close [vma=%08lx-%08lx]\n", vma->vm_start, vma->vm_end); vout->mmap_count--; } static struct vm_operations_struct v4gfx_vm_ops = { .open = v4gfx_vm_open, .close = v4gfx_vm_close, }; static int vidfop_mmap(struct file *file, struct vm_area_struct *vma) { struct v4gfx_device *vout = file->private_data; struct videobuf_queue *q = &vout->vbq; int i; void *pos; int j = 0, k = 0, m = 0, p = 0, m_increment = 0; GFXLOG(1, V4L2DEV(vout), "Entering %s\n", __func__); /* 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) { GFXLOG(1, V4L2DEV(vout), "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_noncached(vma->vm_page_prot); vma->vm_ops = &v4gfx_vm_ops; vma->vm_private_data = (void *) vout; pos = (void *)vout->buf_phy_addr[i]; /* get line width */ v4gfx_tiler_image_incr(vout, &p, &m_increment); for (j = 0; j < vout->pix.height; j++) { /* map each page of the line */ DUMPMMAP("Y buffer", k, vma, m, pos, p); 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 (V4L2_PIX_FMT_NV12 == vout->pix.pixelformat) { pos = (void *)vout->buf_phy_uv_addr[i]; v4gfx_tiler_image_incr_uv(vout, &m_increment); /* UV buffer is height / 2 */ for (j = 0; j < vout->pix.height / 2; j++) { /* map each page of the line */ DUMPMMAP("UV buffer", k, vma, m, pos, p); 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 */ vout->mmap_count++; GFXLOG(1, V4L2DEV(vout), "Exiting %s\n", __func__); return 0; } static int vidfop_release(struct file *file) { struct v4gfx_device *vout = file->private_data; struct videobuf_queue *q; unsigned int r = 0; GFXLOG(1, V4L2DEV(vout), "Entering %s\n", __func__); GFXLOG(1, V4L2DEV(vout), "current process id/pid is %d\n", current->pid); if (!vout) goto end; vout->opened = vout->opened ? vout->opened - 1 : 0; if (vout->opened) { r = 0; goto end; } clear_bit(1, &vout->producer_ready); q = &vout->vbq; if (vout->streaming) { del_timer_sync(&vout->acquire_timer); clear_bit(1, &vout->acquire_timedout); vout->streaming = false; videobuf_streamoff(q); videobuf_queue_cancel(q); } if (q->bufs[0] && (V4L2_MEMORY_MMAP == q->bufs[0]->memory)) videobuf_mmap_free(q); vout->mmap_count = 0; /* Free buffers */ if (vout->buffer_allocated) { v4gfx_tiler_buffer_free(vout, vout->buffer_allocated, 0); vout->buffer_allocated = 0; } memset(&vout->crop, 0, sizeof(vout->crop)); memset(&vout->pix, 0, sizeof(vout->pix)); file->private_data = NULL; end: GFXLOG(1, V4L2DEV(vout), "Exiting %s\n", __func__); return r; } static int vidfop_open(struct file *file) { struct v4gfx_device *vout = NULL; struct videobuf_queue *q; int rv = 0; vout = video_drvdata(file); if (vout == NULL) { rv = -ENODEV; goto end; } GFXLOG(1, V4L2DEV(vout), "Entering %s : %x\n", __func__, (int)vout); GFXLOG(1, V4L2DEV(vout), "current pid is %d\n", current->pid); vout->opened += 1; file->private_data = vout; if (vout->opened > 1) { GFXLOG(1, V4L2DEV(vout), "Another opening....\n"); goto end; } clear_bit(1, &vout->producer_ready); q = &vout->vbq; video_vbq_ops.buf_setup = vbq_ops_buf_setup; video_vbq_ops.buf_prepare = vbq_ops_buf_prepare; video_vbq_ops.buf_release = vbq_ops_buf_release; video_vbq_ops.buf_queue = vbq_ops_buf_queue; videobuf_queue_dma_contig_init(q, &video_vbq_ops, q->dev, &vout->vbq_lock, vout->type, V4L2_FIELD_NONE, sizeof(struct videobuf_buffer), vout); end: GFXLOG(1, V4L2DEV(vout), "Exiting %s :%d\n", __func__, rv); return rv; } /* V4L2 ioctls */ static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { struct v4gfx_device *vout = fh; GFXLOG(1, V4L2DEV(vout), "Entering %s\n", __func__); strlcpy(cap->driver, VOUT_NAME, sizeof(cap->driver)); strlcpy(cap->card, vout->vfd->name, sizeof(cap->card)); cap->bus_info[0] = '\0'; cap->version = VOUT_VERSION; cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_OUTPUT; return 0; } static int vidioc_log_status(struct file *file, void *fh) { /* struct v4gfx_device *vout = fh; */ printk(KERN_INFO "\n"); printk(KERN_INFO "============== START LOG STATUS ================\n"); printk(KERN_INFO "=============== END LOG STATUS =================\n"); printk(KERN_INFO "\n"); return 0; } static int vidioc_enum_fmt_vid_out(struct file *file, void *fh, struct v4l2_fmtdesc *fmt) { struct v4gfx_device *vout = fh; int index = fmt->index; enum v4l2_buf_type type = fmt->type; int rv = 0; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); fmt->index = index; fmt->type = type; if (index >= NUM_OUTPUT_FORMATS) { rv = -EINVAL; goto end; } fmt->flags = gfx_bc_formats[index].flags; strlcpy(fmt->description, gfx_bc_formats[index].description, sizeof(fmt->description)); fmt->pixelformat = gfx_bc_formats[index].pixelformat; end: GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_g_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *f) { struct v4gfx_device *vout = fh; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); f->fmt.pix = vout->pix; GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, 0); return 0; } /* * VIDIOC_TRY_FMT ioctl is equivalent to VIDIOC_S_FMT with one * exception: it does not change driver state. It can also be called at any * time, never returning EBUSY. */ static int vidioc_try_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *f) { int r; struct v4gfx_device *vout = fh; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); r = v4gfx_try_format(&f->fmt.pix); GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, r); return (r >= 0) ? 0 : r; } static int vidioc_s_fmt_vid_out(struct file *file, void *fh, struct v4l2_format *f) { struct v4gfx_device *vout = fh; int rv = 0; int bpp; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); mutex_lock(&vout->lock); if (vout->streaming) { rv = -EBUSY; goto end; } bpp = v4gfx_try_format(&f->fmt.pix); if (bpp <= 0) { rv = bpp; goto end; } /* try & set the new output format */ vout->bpp = bpp; vout->pix = f->fmt.pix; end: mutex_unlock(&vout->lock); GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *req) { struct bc_buf_params2 bc_params; struct v4gfx_device *vout = fh; struct videobuf_queue *q = &vout->vbq; unsigned int i; int rv = 0; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); if ((req->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) || (req->count < 0) || (req->memory != V4L2_MEMORY_MMAP) ) { rv = -EINVAL; goto end; } mutex_lock(&vout->lock); /* Cannot be requested when streaming is on */ if (vout->streaming) { mutex_unlock(&vout->lock); rv = -EBUSY; goto end; } /* * TODO A count value of zero frees all buffers, after aborting or * finishing any DMA in progress, an implicit VIDIOC_STREAMOFF. */ /* If buffers are already allocated free them */ if (q->bufs[0] && (V4L2_MEMORY_MMAP == q->bufs[0]->memory)) { if (vout->mmap_count) { mutex_unlock(&vout->lock); rv = -EBUSY; goto end; } v4gfx_tiler_buffer_free(vout, vout->buffer_allocated, 0); vout->buffer_allocated = 0; videobuf_mmap_free(q); } bc_params.count = req->count; bc_params.width = vout->pix.width; bc_params.height = vout->pix.height; bc_params.pixel_fmt = vout->pix.pixelformat; /* bc_params.stride = vout->pix.bytesperline; */ rv = bc_setup(0, &bc_params); if (rv < 0) { GFXLOG(1, V4L2DEV(vout), "+%s bc_setup() failed %d\n", __func__, rv); goto end; } /* * Note that the actual buffer allocation is done in * vbq_ops_buf_setup */ rv = videobuf_reqbufs(q, req); if (rv < 0) { mutex_unlock(&vout->lock); goto end; } INIT_LIST_HEAD(&vout->dma_queue); INIT_LIST_HEAD(&vout->sync_queue); /* * The realloc will free the old array and allocate a new one */ rv = v4gfx_buffer_array_realloc(vout, vout->buffer_allocated, req->count); if (rv < 0) { mutex_unlock(&vout->lock); goto end; } vout->memory = req->memory; vout->buffer_allocated = req->count; for (i = 0; i < req->count; i++) { v4gfx_buffer_array_fill(vout, i, vout->buf_phy_addr[i], V4L2_PIX_FMT_NV12 == vout->pix.pixelformat ? vout->buf_phy_uv_addr[i] : 0); bc_setup_buffer(0, &bc_params, vout->buf_phys_addr_array[i]); } bc_setup_complete(0, &bc_params); mutex_unlock(&vout->lock); end: GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) { struct v4gfx_device *vout = fh; int rv; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); rv = videobuf_querybuf(&vout->vbq, b); GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf) { struct v4gfx_device *vout = fh; struct videobuf_queue *q = &vout->vbq; int rv = 0; GFXLOG(1, V4L2DEV(vout), "qbuf buf: %d\n", buf->index); if ((V4L2_BUF_TYPE_VIDEO_OUTPUT != buf->type) || (buf->index >= vout->buffer_allocated) || (q->bufs[buf->index]->memory != buf->memory)) { return -EINVAL; } if (V4L2_MEMORY_USERPTR == buf->memory) { if ((buf->length < vout->pix.sizeimage) || (0 == buf->m.userptr)) { return -EINVAL; } } rv = videobuf_qbuf(q, buf); mutex_lock(&vout->lock); if (vout->streaming && vout->acquire_timeout_ms) { del_timer(&vout->acquire_timer); mod_timer(&vout->acquire_timer, jiffies + msecs_to_jiffies(vout->acquire_timeout_ms)); } mutex_unlock(&vout->lock); GFXLOG(2, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) { struct v4gfx_device *vout = fh; struct videobuf_queue *q = &vout->vbq; int rv = 0; int nonblocking = file->f_flags & O_NONBLOCK ? 1 : 0; GFXLOG(2, V4L2DEV(vout), "dqbuf buf: %x (%d)\n", (int)buf, nonblocking); mutex_lock(&vout->lock); if (!vout->streaming) { mutex_unlock(&vout->lock); return -EINVAL; } mutex_unlock(&vout->lock); #if V4GFX_WAITMETHOD == V4GFX_WAIT_DEQUE { struct videobuf_buffer *sync_frame = NULL; wait_event_interruptible(vout->sync_done, !list_empty(&vout->sync_queue)); sync_frame = v4gfx_get_next_syncframe(vout); if (sync_frame) { (void)v4gfx_wait_on_pending(vout, sync_frame->i); v4gfx_done_syncframe(vout, sync_frame); } else { /* Can be from an interrupted task */ printk(KERN_INFO "No sync frame\n"); } } #endif rv = videobuf_dqbuf(q, buf, nonblocking); GFXLOG(2, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_streamon(struct file *file, void *fh, enum v4l2_buf_type i) { struct v4gfx_device *vout = fh; struct videobuf_queue *q = &vout->vbq; int rv = 0; GFXLOG(1, V4L2DEV(vout), "+%s\n", __func__); mutex_lock(&vout->lock); if (vout->streaming) { rv = -EBUSY; goto end_unlock; } vout->cur_frm = NULL; vout->locked_frm = NULL; rv = videobuf_streamon(q); if (rv < 0) goto end_unlock; if (list_empty(&vout->dma_queue)) { rv = -EIO; goto end_unlock; } vout->streaming = true; /* Activate the next current buffer */ vout->cur_frm = list_entry(vout->dma_queue.next, struct videobuf_buffer, queue); list_del(&vout->cur_frm->queue); vout->cur_frm->state = VIDEOBUF_ACTIVE; set_bit(1, &vout->producer_ready); wake_up_interruptible(&vout->consumer_wait); end_unlock: mutex_unlock(&vout->lock); GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_streamoff(struct file *file, void *fh, enum v4l2_buf_type i) { struct v4gfx_device *vout = fh; int rv; mutex_lock(&vout->lock); if (!vout->streaming) { rv = -EINVAL; goto end; } del_timer_sync(&vout->acquire_timer); clear_bit(1, &vout->acquire_timedout); clear_bit(1, &vout->producer_ready); vout->streaming = false; INIT_LIST_HEAD(&vout->dma_queue); INIT_LIST_HEAD(&vout->sync_queue); videobuf_streamoff(&vout->vbq); videobuf_queue_cancel(&vout->vbq); end: mutex_unlock(&vout->lock); GFXLOG(1, V4L2DEV(vout), "-%s [%d]\n", __func__, rv); return rv; } static int vidioc_cropcap(struct file *file, void *fh, struct v4l2_cropcap *cropcap) { struct v4gfx_device *vout = fh; struct v4l2_pix_format *pix = &vout->pix; if (cropcap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) return -EINVAL; /* Width and height are always even */ cropcap->bounds.width = pix->width & ~1; cropcap->bounds.height = pix->height & ~1; cropcap->pixelaspect.numerator = 1; cropcap->pixelaspect.denominator = 1; return 0; } static int vidioc_g_crop(struct file *file, void *fh, struct v4l2_crop *crop) { struct v4gfx_device *vout = fh; if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) return -EINVAL; crop->c = vout->crop; GFXLOG(1, V4L2DEV(vout), "g_crop w:%d,h:%d\n", crop->c.width, crop->c.height); return 0; } static int vidioc_s_crop(struct file *file, void *fh, struct v4l2_crop *crop) { struct v4gfx_device *vout = fh; GFXLOG(1, V4L2DEV(vout), "Entering %s\n", __func__); vout->crop = crop->c; return 0; } static long vidioc_default(struct file *file, void *fh, int cmd, void *arg) { int rv = 0; struct v4gfx_device *vout = fh; GFXLOG(1, V4L2DEV(vout), "Entering %s (c=0x%x)\n", __func__, cmd); switch (cmd) { case V4L2_GFX_IOC_CONSUMER: { struct v4l2_gfx_consumer_params *parms = (struct v4l2_gfx_consumer_params *)arg; if (parms->type != V4L2_GFX_CONSUMER_WAITSTREAM) return -EINVAL; clear_bit(1, &vout->acquire_timedout); rv = wait_event_interruptible(vout->consumer_wait, test_bit(1, &vout->producer_ready)); mutex_lock(&vout->lock); if (rv == -ERESTARTSYS) { /* * This condition is hit when the user process * generates a signal, when we return this value the * process will continue to block on the ioctl */ GFXLOG(1, V4L2DEV(vout), "Woke by signal: %d\n", ERESTARTSYS); } else { vout->acquire_timeout_ms = parms->acquire_timeout_ms; } mutex_unlock(&vout->lock); break; } case V4L2_GFX_IOC_INFO: { struct v4l2_gfx_info_params *parms = (struct v4l2_gfx_info_params *)arg; parms->opencnt = vout->opened; break; } case V4L2_GFX_IOC_PRODUCER: { struct v4l2_gfx_producer_params *parms = (struct v4l2_gfx_producer_params *)arg; vout->producer_flags = parms->flags; if (!(vout->producer_flags & V4L2_GFX_PRODUCER_MASK_OPEN)) { /* * We decrement the count here because the Android * mediaserver threads won't close the V4L2 device */ if (vout->opened) vout->opened--; } break; } case V4L2_GFX_IOC_ACQ: { struct v4l2_gfx_buf_params *parms = (struct v4l2_gfx_buf_params *)arg; int bufid = -1; int timedout; rv = v4gfx_frame_lock(vout, &bufid); if (!rv) { parms->bufid = bufid; parms->crop_top = vout->crop.top; parms->crop_left = vout->crop.left; parms->crop_width = vout->crop.width; parms->crop_height = vout->crop.height; GFXLOG(3, V4L2DEV(vout), "%d:%d:%d:%d:%d\n", parms->bufid , parms->crop_top , parms->crop_left , parms->crop_width , parms->crop_height); } timedout = test_and_clear_bit(1, &vout->acquire_timedout); if (timedout) { GFXLOG(1, V4L2DEV(vout), "ACQ Timed out\n"); rv = -ETIMEDOUT; } mutex_lock(&vout->lock); if (!vout->streaming) { GFXLOG(1, V4L2DEV(vout), "ACQ stream off\n"); rv = -ENODEV; } mutex_unlock(&vout->lock); break; } case V4L2_GFX_IOC_REL: { struct v4l2_gfx_buf_params *parms = (struct v4l2_gfx_buf_params *)arg; int bufid = parms->bufid; rv = v4gfx_frame_unlock(vout, bufid); break; } default: rv = -EINVAL; } GFXLOG(1, V4L2DEV(vout), "Leaving %s (%d)\n", __func__, rv); return rv; } static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *a) { struct v4gfx_device *vout = fh; GFXLOG(1, V4L2DEV(vout), "%s: %d\n", __func__, a->id); return 0; } struct v4l2_ioctl_ops v4gfx_ioctl_ops = { .vidioc_querycap = vidioc_querycap, .vidioc_log_status = vidioc_log_status, .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, .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_cropcap = vidioc_cropcap, .vidioc_g_crop = vidioc_g_crop, .vidioc_s_crop = vidioc_s_crop, .vidioc_default = vidioc_default, .vidioc_s_ctrl = vidioc_s_ctrl, }; const struct v4l2_file_operations v4gfx_fops = { .owner = THIS_MODULE, .ioctl = video_ioctl2, .mmap = vidfop_mmap, .open = vidfop_open, .release = vidfop_release, };