| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <media/drv-intf/saa7146_vv.h> |
| #include <media/v4l2-event.h> |
| #include <media/v4l2-ctrls.h> |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| |
| /* format descriptions for capture and preview */ |
| static struct saa7146_format formats[] = { |
| { |
| .pixelformat = V4L2_PIX_FMT_RGB332, |
| .trans = RGB08_COMPOSED, |
| .depth = 8, |
| .flags = 0, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_RGB565, |
| .trans = RGB16_COMPOSED, |
| .depth = 16, |
| .flags = 0, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_BGR24, |
| .trans = RGB24_COMPOSED, |
| .depth = 24, |
| .flags = 0, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_BGR32, |
| .trans = RGB32_COMPOSED, |
| .depth = 32, |
| .flags = 0, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_RGB32, |
| .trans = RGB32_COMPOSED, |
| .depth = 32, |
| .flags = 0, |
| .swap = 0x2, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_GREY, |
| .trans = Y8, |
| .depth = 8, |
| .flags = 0, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_YUV422P, |
| .trans = YUV422_DECOMPOSED, |
| .depth = 16, |
| .flags = FORMAT_IS_PLANAR, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_YVU420, |
| .trans = YUV420_DECOMPOSED, |
| .depth = 12, |
| .flags = FORMAT_BYTE_SWAP|FORMAT_IS_PLANAR, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_YUV420, |
| .trans = YUV420_DECOMPOSED, |
| .depth = 12, |
| .flags = FORMAT_IS_PLANAR, |
| }, { |
| .pixelformat = V4L2_PIX_FMT_UYVY, |
| .trans = YUV422_COMPOSED, |
| .depth = 16, |
| .flags = 0, |
| } |
| }; |
| |
| /* unfortunately, the saa7146 contains a bug which prevents it from doing on-the-fly byte swaps. |
| due to this, it's impossible to provide additional *packed* formats, which are simply byte swapped |
| (like V4L2_PIX_FMT_YUYV) ... 8-( */ |
| |
| struct saa7146_format* saa7146_format_by_fourcc(struct saa7146_dev *dev, int fourcc) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(formats); i++) { |
| if (formats[i].pixelformat == fourcc) { |
| return formats+i; |
| } |
| } |
| |
| DEB_D("unknown pixelformat:'%4.4s'\n", (char *)&fourcc); |
| return NULL; |
| } |
| |
| /********************************************************************************/ |
| /* common pagetable functions */ |
| |
| static int saa7146_pgtable_build(struct saa7146_dev *dev, struct saa7146_buf *buf) |
| { |
| struct saa7146_vv *vv = dev->vv_data; |
| struct pci_dev *pci = dev->pci; |
| struct sg_table *sgt = vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0); |
| struct scatterlist *list = sgt->sgl; |
| int length = sgt->nents; |
| struct v4l2_pix_format *pix = &vv->video_fmt; |
| struct saa7146_format *sfmt = saa7146_format_by_fourcc(dev, pix->pixelformat); |
| |
| DEB_EE("dev:%p, buf:%p, sg_len:%d\n", dev, buf, length); |
| |
| if( 0 != IS_PLANAR(sfmt->trans)) { |
| struct saa7146_pgtable *pt1 = &buf->pt[0]; |
| struct saa7146_pgtable *pt2 = &buf->pt[1]; |
| struct saa7146_pgtable *pt3 = &buf->pt[2]; |
| struct sg_dma_page_iter dma_iter; |
| __le32 *ptr1, *ptr2, *ptr3; |
| __le32 fill; |
| |
| int size = pix->width * pix->height; |
| int i, m1, m2, m3, o1, o2; |
| |
| switch( sfmt->depth ) { |
| case 12: { |
| /* create some offsets inside the page table */ |
| m1 = ((size + PAGE_SIZE) / PAGE_SIZE) - 1; |
| m2 = ((size + (size / 4) + PAGE_SIZE) / PAGE_SIZE) - 1; |
| m3 = ((size + (size / 2) + PAGE_SIZE) / PAGE_SIZE) - 1; |
| o1 = size % PAGE_SIZE; |
| o2 = (size + (size / 4)) % PAGE_SIZE; |
| DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", |
| size, m1, m2, m3, o1, o2); |
| break; |
| } |
| case 16: { |
| /* create some offsets inside the page table */ |
| m1 = ((size + PAGE_SIZE) / PAGE_SIZE) - 1; |
| m2 = ((size + (size / 2) + PAGE_SIZE) / PAGE_SIZE) - 1; |
| m3 = ((2 * size + PAGE_SIZE) / PAGE_SIZE) - 1; |
| o1 = size % PAGE_SIZE; |
| o2 = (size + (size / 2)) % PAGE_SIZE; |
| DEB_CAP("size:%d, m1:%d, m2:%d, m3:%d, o1:%d, o2:%d\n", |
| size, m1, m2, m3, o1, o2); |
| break; |
| } |
| default: { |
| return -1; |
| } |
| } |
| |
| ptr1 = pt1->cpu; |
| ptr2 = pt2->cpu; |
| ptr3 = pt3->cpu; |
| |
| for_each_sg_dma_page(list, &dma_iter, length, 0) |
| *ptr1++ = cpu_to_le32(sg_page_iter_dma_address(&dma_iter) - list->offset); |
| |
| /* if we have a user buffer, the first page may not be |
| aligned to a page boundary. */ |
| pt1->offset = sgt->sgl->offset; |
| pt2->offset = pt1->offset + o1; |
| pt3->offset = pt1->offset + o2; |
| |
| /* create video-dma2 page table */ |
| ptr1 = pt1->cpu; |
| for (i = m1; i <= m2; i++, ptr2++) |
| *ptr2 = ptr1[i]; |
| fill = *(ptr2 - 1); |
| for (; i < 1024; i++, ptr2++) |
| *ptr2 = fill; |
| /* create video-dma3 page table */ |
| ptr1 = pt1->cpu; |
| for (i = m2; i <= m3; i++, ptr3++) |
| *ptr3 = ptr1[i]; |
| fill = *(ptr3 - 1); |
| for (; i < 1024; i++, ptr3++) |
| *ptr3 = fill; |
| /* finally: finish up video-dma1 page table */ |
| ptr1 = pt1->cpu + m1; |
| fill = pt1->cpu[m1]; |
| for (i = m1; i < 1024; i++, ptr1++) |
| *ptr1 = fill; |
| } else { |
| struct saa7146_pgtable *pt = &buf->pt[0]; |
| |
| return saa7146_pgtable_build_single(pci, pt, list, length); |
| } |
| |
| return 0; |
| } |
| |
| |
| /********************************************************************************/ |
| /* file operations */ |
| |
| static int video_begin(struct saa7146_dev *dev) |
| { |
| struct saa7146_vv *vv = dev->vv_data; |
| struct saa7146_format *fmt = NULL; |
| unsigned int resource; |
| int ret = 0; |
| |
| DEB_EE("dev:%p\n", dev); |
| |
| fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); |
| /* we need to have a valid format set here */ |
| if (!fmt) |
| return -EINVAL; |
| |
| if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { |
| resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; |
| } else { |
| resource = RESOURCE_DMA1_HPS; |
| } |
| |
| ret = saa7146_res_get(dev, resource); |
| if (0 == ret) { |
| DEB_S("cannot get capture resource %d\n", resource); |
| return -EBUSY; |
| } |
| |
| /* clear out beginning of streaming bit (rps register 0)*/ |
| saa7146_write(dev, MC2, MASK_27 ); |
| |
| /* enable rps0 irqs */ |
| SAA7146_IER_ENABLE(dev, MASK_27); |
| |
| return 0; |
| } |
| |
| static void video_end(struct saa7146_dev *dev) |
| { |
| struct saa7146_vv *vv = dev->vv_data; |
| struct saa7146_format *fmt = NULL; |
| unsigned long flags; |
| unsigned int resource; |
| u32 dmas = 0; |
| DEB_EE("dev:%p\n", dev); |
| |
| fmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); |
| /* we need to have a valid format set here */ |
| if (!fmt) |
| return; |
| |
| if (0 != (fmt->flags & FORMAT_IS_PLANAR)) { |
| resource = RESOURCE_DMA1_HPS|RESOURCE_DMA2_CLP|RESOURCE_DMA3_BRS; |
| dmas = MASK_22 | MASK_21 | MASK_20; |
| } else { |
| resource = RESOURCE_DMA1_HPS; |
| dmas = MASK_22; |
| } |
| spin_lock_irqsave(&dev->slock,flags); |
| |
| /* disable rps0 */ |
| saa7146_write(dev, MC1, MASK_28); |
| |
| /* disable rps0 irqs */ |
| SAA7146_IER_DISABLE(dev, MASK_27); |
| |
| /* shut down all used video dma transfers */ |
| saa7146_write(dev, MC1, dmas); |
| |
| spin_unlock_irqrestore(&dev->slock, flags); |
| |
| saa7146_res_free(dev, resource); |
| } |
| |
| static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| |
| strscpy((char *)cap->driver, "saa7146 v4l2", sizeof(cap->driver)); |
| strscpy((char *)cap->card, dev->ext->name, sizeof(cap->card)); |
| cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | |
| V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | |
| V4L2_CAP_DEVICE_CAPS; |
| cap->capabilities |= dev->ext_vv_data->capabilities; |
| return 0; |
| } |
| |
| static int vidioc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f) |
| { |
| if (f->index >= ARRAY_SIZE(formats)) |
| return -EINVAL; |
| f->pixelformat = formats[f->index].pixelformat; |
| return 0; |
| } |
| |
| int saa7146_s_ctrl(struct v4l2_ctrl *ctrl) |
| { |
| struct saa7146_dev *dev = container_of(ctrl->handler, |
| struct saa7146_dev, ctrl_handler); |
| struct saa7146_vv *vv = dev->vv_data; |
| u32 val; |
| |
| switch (ctrl->id) { |
| case V4L2_CID_BRIGHTNESS: |
| val = saa7146_read(dev, BCS_CTRL); |
| val &= 0x00ffffff; |
| val |= (ctrl->val << 24); |
| saa7146_write(dev, BCS_CTRL, val); |
| saa7146_write(dev, MC2, MASK_22 | MASK_06); |
| break; |
| |
| case V4L2_CID_CONTRAST: |
| val = saa7146_read(dev, BCS_CTRL); |
| val &= 0xff00ffff; |
| val |= (ctrl->val << 16); |
| saa7146_write(dev, BCS_CTRL, val); |
| saa7146_write(dev, MC2, MASK_22 | MASK_06); |
| break; |
| |
| case V4L2_CID_SATURATION: |
| val = saa7146_read(dev, BCS_CTRL); |
| val &= 0xffffff00; |
| val |= (ctrl->val << 0); |
| saa7146_write(dev, BCS_CTRL, val); |
| saa7146_write(dev, MC2, MASK_22 | MASK_06); |
| break; |
| |
| case V4L2_CID_HFLIP: |
| /* fixme: we can support changing VFLIP and HFLIP here... */ |
| if (vb2_is_busy(&vv->video_dmaq.q)) |
| return -EBUSY; |
| vv->hflip = ctrl->val; |
| break; |
| |
| case V4L2_CID_VFLIP: |
| if (vb2_is_busy(&vv->video_dmaq.q)) |
| return -EBUSY; |
| vv->vflip = ctrl->val; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int vidioc_g_parm(struct file *file, void *fh, |
| struct v4l2_streamparm *parm) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| struct saa7146_vv *vv = dev->vv_data; |
| |
| if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) |
| return -EINVAL; |
| parm->parm.capture.readbuffers = 1; |
| v4l2_video_std_frame_period(vv->standard->id, |
| &parm->parm.capture.timeperframe); |
| return 0; |
| } |
| |
| static int vidioc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| struct saa7146_vv *vv = dev->vv_data; |
| |
| f->fmt.pix = vv->video_fmt; |
| return 0; |
| } |
| |
| static int vidioc_g_fmt_vbi_cap(struct file *file, void *fh, struct v4l2_format *f) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| struct saa7146_vv *vv = dev->vv_data; |
| |
| f->fmt.vbi = vv->vbi_fmt; |
| return 0; |
| } |
| |
| static int vidioc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| struct saa7146_vv *vv = dev->vv_data; |
| struct saa7146_format *fmt; |
| enum v4l2_field field; |
| int maxw, maxh; |
| int calc_bpl; |
| |
| DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p, fh:%p\n", dev, fh); |
| |
| fmt = saa7146_format_by_fourcc(dev, f->fmt.pix.pixelformat); |
| if (NULL == fmt) |
| return -EINVAL; |
| |
| field = f->fmt.pix.field; |
| maxw = vv->standard->h_max_out; |
| maxh = vv->standard->v_max_out; |
| |
| if (V4L2_FIELD_ANY == field) { |
| field = (f->fmt.pix.height > maxh / 2) |
| ? V4L2_FIELD_INTERLACED |
| : V4L2_FIELD_BOTTOM; |
| } |
| switch (field) { |
| case V4L2_FIELD_ALTERNATE: |
| case V4L2_FIELD_TOP: |
| case V4L2_FIELD_BOTTOM: |
| maxh = maxh / 2; |
| break; |
| default: |
| field = V4L2_FIELD_INTERLACED; |
| break; |
| } |
| |
| f->fmt.pix.field = field; |
| f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; |
| if (f->fmt.pix.width < 48) |
| f->fmt.pix.width = 48; |
| if (f->fmt.pix.height < 32) |
| f->fmt.pix.height = 32; |
| if (f->fmt.pix.width > maxw) |
| f->fmt.pix.width = maxw; |
| if (f->fmt.pix.height > maxh) |
| f->fmt.pix.height = maxh; |
| |
| calc_bpl = (f->fmt.pix.width * fmt->depth) / 8; |
| |
| if (f->fmt.pix.bytesperline < calc_bpl) |
| f->fmt.pix.bytesperline = calc_bpl; |
| |
| if (f->fmt.pix.bytesperline > (2 * PAGE_SIZE * fmt->depth) / 8) /* arbitrary constraint */ |
| f->fmt.pix.bytesperline = calc_bpl; |
| |
| f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; |
| DEB_D("w:%d, h:%d, bytesperline:%d, sizeimage:%d\n", |
| f->fmt.pix.width, f->fmt.pix.height, |
| f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); |
| |
| return 0; |
| } |
| |
| static int vidioc_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| struct saa7146_vv *vv = dev->vv_data; |
| int err; |
| |
| DEB_EE("V4L2_BUF_TYPE_VIDEO_CAPTURE: dev:%p\n", dev); |
| if (vb2_is_busy(&vv->video_dmaq.q)) { |
| DEB_EE("streaming capture is active\n"); |
| return -EBUSY; |
| } |
| err = vidioc_try_fmt_vid_cap(file, fh, f); |
| if (0 != err) |
| return err; |
| switch (f->fmt.pix.field) { |
| case V4L2_FIELD_ALTERNATE: |
| vv->last_field = V4L2_FIELD_TOP; |
| break; |
| default: |
| vv->last_field = V4L2_FIELD_INTERLACED; |
| break; |
| } |
| vv->video_fmt = f->fmt.pix; |
| DEB_EE("set to pixelformat '%4.4s'\n", |
| (char *)&vv->video_fmt.pixelformat); |
| return 0; |
| } |
| |
| static int vidioc_g_std(struct file *file, void *fh, v4l2_std_id *norm) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| struct saa7146_vv *vv = dev->vv_data; |
| |
| *norm = vv->standard->id; |
| return 0; |
| } |
| |
| static int vidioc_s_std(struct file *file, void *fh, v4l2_std_id id) |
| { |
| struct saa7146_dev *dev = video_drvdata(file); |
| struct saa7146_vv *vv = dev->vv_data; |
| int found = 0; |
| int i; |
| |
| DEB_EE("VIDIOC_S_STD\n"); |
| |
| for (i = 0; i < dev->ext_vv_data->num_stds; i++) |
| if (id & dev->ext_vv_data->stds[i].id) |
| break; |
| |
| if (i != dev->ext_vv_data->num_stds && |
| vv->standard == &dev->ext_vv_data->stds[i]) |
| return 0; |
| |
| if (vb2_is_busy(&vv->video_dmaq.q) || vb2_is_busy(&vv->vbi_dmaq.q)) { |
| DEB_D("cannot change video standard while streaming capture is active\n"); |
| return -EBUSY; |
| } |
| |
| if (i != dev->ext_vv_data->num_stds) { |
| vv->standard = &dev->ext_vv_data->stds[i]; |
| if (NULL != dev->ext_vv_data->std_callback) |
| dev->ext_vv_data->std_callback(dev, vv->standard); |
| found = 1; |
| } |
| |
| if (!found) { |
| DEB_EE("VIDIOC_S_STD: standard not found\n"); |
| return -EINVAL; |
| } |
| |
| DEB_EE("VIDIOC_S_STD: set to standard to '%s'\n", vv->standard->name); |
| return 0; |
| } |
| |
| const struct v4l2_ioctl_ops saa7146_video_ioctl_ops = { |
| .vidioc_querycap = vidioc_querycap, |
| .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, |
| .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, |
| .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, |
| .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, |
| .vidioc_g_std = vidioc_g_std, |
| .vidioc_s_std = vidioc_s_std, |
| .vidioc_g_parm = vidioc_g_parm, |
| .vidioc_reqbufs = vb2_ioctl_reqbufs, |
| .vidioc_create_bufs = vb2_ioctl_create_bufs, |
| .vidioc_querybuf = vb2_ioctl_querybuf, |
| .vidioc_qbuf = vb2_ioctl_qbuf, |
| .vidioc_dqbuf = vb2_ioctl_dqbuf, |
| .vidioc_streamon = vb2_ioctl_streamon, |
| .vidioc_streamoff = vb2_ioctl_streamoff, |
| .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
| .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
| }; |
| |
| const struct v4l2_ioctl_ops saa7146_vbi_ioctl_ops = { |
| .vidioc_querycap = vidioc_querycap, |
| .vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, |
| .vidioc_try_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, |
| .vidioc_s_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, |
| .vidioc_g_std = vidioc_g_std, |
| .vidioc_s_std = vidioc_s_std, |
| .vidioc_g_parm = vidioc_g_parm, |
| .vidioc_reqbufs = vb2_ioctl_reqbufs, |
| .vidioc_create_bufs = vb2_ioctl_create_bufs, |
| .vidioc_querybuf = vb2_ioctl_querybuf, |
| .vidioc_qbuf = vb2_ioctl_qbuf, |
| .vidioc_dqbuf = vb2_ioctl_dqbuf, |
| .vidioc_streamon = vb2_ioctl_streamon, |
| .vidioc_streamoff = vb2_ioctl_streamoff, |
| .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, |
| .vidioc_unsubscribe_event = v4l2_event_unsubscribe, |
| }; |
| |
| /*********************************************************************************/ |
| /* buffer handling functions */ |
| |
| static int buffer_activate (struct saa7146_dev *dev, |
| struct saa7146_buf *buf, |
| struct saa7146_buf *next) |
| { |
| struct saa7146_vv *vv = dev->vv_data; |
| |
| saa7146_set_capture(dev,buf,next); |
| |
| mod_timer(&vv->video_dmaq.timeout, jiffies+BUFFER_TIMEOUT); |
| return 0; |
| } |
| |
| static void release_all_pagetables(struct saa7146_dev *dev, struct saa7146_buf *buf) |
| { |
| saa7146_pgtable_free(dev->pci, &buf->pt[0]); |
| saa7146_pgtable_free(dev->pci, &buf->pt[1]); |
| saa7146_pgtable_free(dev->pci, &buf->pt[2]); |
| } |
| |
| static int queue_setup(struct vb2_queue *q, |
| unsigned int *num_buffers, unsigned int *num_planes, |
| unsigned int sizes[], struct device *alloc_devs[]) |
| { |
| struct saa7146_dev *dev = vb2_get_drv_priv(q); |
| unsigned int size = dev->vv_data->video_fmt.sizeimage; |
| |
| if (*num_planes) |
| return sizes[0] < size ? -EINVAL : 0; |
| *num_planes = 1; |
| sizes[0] = size; |
| |
| return 0; |
| } |
| |
| static void buf_queue(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct saa7146_dev *dev = vb2_get_drv_priv(vq); |
| struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb); |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->slock, flags); |
| |
| saa7146_buffer_queue(dev, &dev->vv_data->video_dmaq, buf); |
| spin_unlock_irqrestore(&dev->slock, flags); |
| } |
| |
| static int buf_init(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb); |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct saa7146_dev *dev = vb2_get_drv_priv(vq); |
| struct saa7146_vv *vv = dev->vv_data; |
| struct saa7146_format *sfmt; |
| int ret; |
| |
| buf->activate = buffer_activate; |
| sfmt = saa7146_format_by_fourcc(dev, vv->video_fmt.pixelformat); |
| |
| if (IS_PLANAR(sfmt->trans)) { |
| saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); |
| saa7146_pgtable_alloc(dev->pci, &buf->pt[1]); |
| saa7146_pgtable_alloc(dev->pci, &buf->pt[2]); |
| } else { |
| saa7146_pgtable_alloc(dev->pci, &buf->pt[0]); |
| } |
| |
| ret = saa7146_pgtable_build(dev, buf); |
| if (ret) |
| release_all_pagetables(dev, buf); |
| return ret; |
| } |
| |
| static int buf_prepare(struct vb2_buffer *vb) |
| { |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct saa7146_dev *dev = vb2_get_drv_priv(vq); |
| struct saa7146_vv *vv = dev->vv_data; |
| unsigned int size = vv->video_fmt.sizeimage; |
| |
| if (vb2_plane_size(vb, 0) < size) |
| return -EINVAL; |
| vb2_set_plane_payload(vb, 0, size); |
| return 0; |
| } |
| |
| static void buf_cleanup(struct vb2_buffer *vb) |
| { |
| struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); |
| struct saa7146_buf *buf = container_of(vbuf, struct saa7146_buf, vb); |
| struct vb2_queue *vq = vb->vb2_queue; |
| struct saa7146_dev *dev = vb2_get_drv_priv(vq); |
| |
| release_all_pagetables(dev, buf); |
| } |
| |
| static void return_buffers(struct vb2_queue *q, int state) |
| { |
| struct saa7146_dev *dev = vb2_get_drv_priv(q); |
| struct saa7146_dmaqueue *dq = &dev->vv_data->video_dmaq; |
| struct saa7146_buf *buf; |
| |
| if (dq->curr) { |
| buf = dq->curr; |
| dq->curr = NULL; |
| vb2_buffer_done(&buf->vb.vb2_buf, state); |
| } |
| while (!list_empty(&dq->queue)) { |
| buf = list_entry(dq->queue.next, struct saa7146_buf, list); |
| list_del(&buf->list); |
| vb2_buffer_done(&buf->vb.vb2_buf, state); |
| } |
| } |
| |
| static int start_streaming(struct vb2_queue *q, unsigned int count) |
| { |
| struct saa7146_dev *dev = vb2_get_drv_priv(q); |
| int ret; |
| |
| if (!vb2_is_streaming(&dev->vv_data->video_dmaq.q)) |
| dev->vv_data->seqnr = 0; |
| ret = video_begin(dev); |
| if (ret) |
| return_buffers(q, VB2_BUF_STATE_QUEUED); |
| return ret; |
| } |
| |
| static void stop_streaming(struct vb2_queue *q) |
| { |
| struct saa7146_dev *dev = vb2_get_drv_priv(q); |
| struct saa7146_dmaqueue *dq = &dev->vv_data->video_dmaq; |
| |
| del_timer(&dq->timeout); |
| video_end(dev); |
| return_buffers(q, VB2_BUF_STATE_ERROR); |
| } |
| |
| const struct vb2_ops video_qops = { |
| .queue_setup = queue_setup, |
| .buf_queue = buf_queue, |
| .buf_init = buf_init, |
| .buf_prepare = buf_prepare, |
| .buf_cleanup = buf_cleanup, |
| .start_streaming = start_streaming, |
| .stop_streaming = stop_streaming, |
| .wait_prepare = vb2_ops_wait_prepare, |
| .wait_finish = vb2_ops_wait_finish, |
| }; |
| |
| /********************************************************************************/ |
| /* file operations */ |
| |
| static void video_init(struct saa7146_dev *dev, struct saa7146_vv *vv) |
| { |
| INIT_LIST_HEAD(&vv->video_dmaq.queue); |
| |
| timer_setup(&vv->video_dmaq.timeout, saa7146_buffer_timeout, 0); |
| vv->video_dmaq.dev = dev; |
| |
| /* set some default values */ |
| vv->standard = &dev->ext_vv_data->stds[0]; |
| |
| /* FIXME: what's this? */ |
| vv->current_hps_source = SAA7146_HPS_SOURCE_PORT_A; |
| vv->current_hps_sync = SAA7146_HPS_SYNC_PORT_A; |
| } |
| |
| static void video_irq_done(struct saa7146_dev *dev, unsigned long st) |
| { |
| struct saa7146_vv *vv = dev->vv_data; |
| struct saa7146_dmaqueue *q = &vv->video_dmaq; |
| |
| spin_lock(&dev->slock); |
| DEB_CAP("called\n"); |
| |
| /* only finish the buffer if we have one... */ |
| if (q->curr) |
| saa7146_buffer_finish(dev, q, VB2_BUF_STATE_DONE); |
| saa7146_buffer_next(dev,q,0); |
| |
| spin_unlock(&dev->slock); |
| } |
| |
| const struct saa7146_use_ops saa7146_video_uops = { |
| .init = video_init, |
| .irq_done = video_irq_done, |
| }; |