diff --git a/Makefile b/Makefile index 24c56cd..242f87a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -bcwc_pcie-objs := bcwc_ddr.o bcwc_hw.o bcwc_drv.o bcwc_ringbuf.o bcwc_isp.o +bcwc_pcie-objs := bcwc_ddr.o bcwc_hw.o bcwc_drv.o bcwc_ringbuf.o bcwc_isp.o bcwc_v4l2.o obj-m := bcwc_pcie.o KVERSION := $(shell uname -r) diff --git a/bcwc_drv.c b/bcwc_drv.c index 8a8ee8e..c964824 100644 --- a/bcwc_drv.c +++ b/bcwc_drv.c @@ -30,6 +30,7 @@ #include "bcwc_hw.h" #include "bcwc_isp.h" #include "bcwc_ringbuf.h" +#include "bcwc_v4l2.h" static int bcwc_pci_reserve_mem(struct bcwc_private *dev_priv) { diff --git a/bcwc_v4l2.c b/bcwc_v4l2.c new file mode 100644 index 0000000..ded15f3 --- /dev/null +++ b/bcwc_v4l2.c @@ -0,0 +1,425 @@ + +/* + * Broadcom PCIe 1570 webcam driver + * + * Copyright (C) 2015 Sven Schnelle + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "bcwc_drv.h" +#include "bcwc_hw.h" +#include "bcwc_isp.h" +#include "bcwc_ringbuf.h" +#include "bcwc_buffer.h" + +static int bcwc_vb_buf_setup(struct videobuf_queue *q, + unsigned int *count, unsigned int *size) +{ + struct bcwc_private *priv = q->priv_data; + pr_debug("%s: %d\n", __FUNCTION__, priv->user_format.sizeimage); + *size = priv->user_format.sizeimage; + if (*count == 0 || *count > 6) + *count = 6; + return 0; +} + +static int bcwc_vb_buf_prepare(struct videobuf_queue *q, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct bcwc_private *priv = q->priv_data; + int ret; + + pr_debug("%s\n", __FUNCTION__); + + vb->size = priv->user_format.sizeimage; + vb->width = priv->user_format.width; + vb->height = priv->user_format.height; + vb->field = field; + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + ret = videobuf_iolock(q, vb, NULL); + if (ret) + return ret; + } + vb->state = VIDEOBUF_PREPARED; + return 0; +} + +static void bcwc_vb_buf_queue(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct bcwc_private *priv = q->priv_data; + pr_debug("%s\n", __FUNCTION__); + vb->state = VIDEOBUF_QUEUED; + list_add_tail(&vb->queue, &priv->buffer_queue); +} + +static void bcwc_vb_buf_release(struct videobuf_queue *q, + struct videobuf_buffer *vb) +{ + struct bcwc_private *priv = q->priv_data; + pr_debug("%s\n", __FUNCTION__); + videobuf_dma_unmap(&priv->pdev->dev, videobuf_to_dma(vb)); + videobuf_dma_free(videobuf_to_dma(vb)); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static const struct videobuf_queue_ops bcwc_vb_ops = { + .buf_setup = bcwc_vb_buf_setup, + .buf_prepare = bcwc_vb_buf_prepare, + .buf_queue = bcwc_vb_buf_queue, + .buf_release = bcwc_vb_buf_release, +}; + +static int bcwc_v4l2_open(struct file *filp) +{ + struct bcwc_private *priv = video_drvdata(filp); + pr_info("%s: %p\n", __FUNCTION__, priv); + + mutex_lock(&priv->ioctl_lock); + videobuf_queue_sg_init(&priv->vb_queue, &bcwc_vb_ops, + &priv->pdev->dev, &priv->vb_queue_lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, + sizeof(struct videobuf_buffer), priv, NULL); + + if (!priv->users++) { + bcwc_isp_cmd_channel_start(priv); + } + + mutex_unlock(&priv->ioctl_lock); + return 0; +} + +static int bcwc_v4l2_release(struct file *filp) +{ + struct bcwc_private *priv = video_drvdata(filp); + pr_info("%s\n", __FUNCTION__); + mutex_lock(&priv->ioctl_lock); + if (!--priv->users) { + bcwc_isp_cmd_channel_stop(priv); + } + + videobuf_stop(&priv->vb_queue); + videobuf_mmap_free(&priv->vb_queue); + mutex_unlock(&priv->ioctl_lock); + return 0; +} + +static ssize_t bcwc_v4l2_read(struct file *filp, char __user *buffer, + size_t len, loff_t *pos) +{ + struct bcwc_private *priv = video_drvdata(filp); + pr_info("%s: buf %p, size %d, pos %p\n", __FUNCTION__, buffer, (int)len, pos); + return videobuf_read_stream(&priv->vb_queue, buffer, len, pos, 0, + filp->f_flags & O_NONBLOCK); +} + +static unsigned int bcwc_v4l2_poll(struct file *filp, struct poll_table_struct *pt) +{ + struct bcwc_private *priv = video_drvdata(filp); + pr_debug("%s\n", __FUNCTION__); + return videobuf_poll_stream(filp, &priv->vb_queue, pt); +} + +static int bcwc_v4l2_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct bcwc_private *priv = video_drvdata(filp); + pr_debug("%s\n", __FUNCTION__); + return videobuf_mmap_mapper(&priv->vb_queue, vma); +} + +static struct v4l2_file_operations bcwc_vdev_fops = { + .owner = THIS_MODULE, + .open = bcwc_v4l2_open, + .release = bcwc_v4l2_release, + .read = bcwc_v4l2_read, + .poll = bcwc_v4l2_poll, + .mmap = bcwc_v4l2_mmap, + .unlocked_ioctl = video_ioctl2 +}; + +static int bcwc_v4l2_ioctl_enum_input(struct file *filp, void *priv, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + input->std = V4L2_STD_ALL; + strcpy(input->name, "Apple Facetime"); + return 0; +} + +static int bcwc_v4l2_ioctl_g_input(struct file *filp, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int bcwc_v4l2_ioctl_s_input(struct file *filp, void *priv, unsigned int i) +{ + if (i != 0) + return -EINVAL; + return 0; +} + +static int bcwc_v4l2_ioctl_s_std(struct file *filp, void *priv, v4l2_std_id std) +{ + return 0; +} + +static int bcwc_v4l2_ioctl_g_std(struct file *filp, void *priv, v4l2_std_id *std) +{ + *std = V4L2_STD_NTSC_M; + return 0; +} + +static int bcwc_v4l2_ioctl_querycap(struct file *filp, void *priv, + struct v4l2_capability *cap) +{ + strcpy(cap->driver, "bcwc"); + strcpy(cap->card, "facetimehd"); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_READWRITE | V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +static struct bcwc_format { + __u8 *desc; + __u32 pixelformat; + int bpp; /* Bytes per pixel */ + u32 mbus_code; +} bcwc_formats[] = { + { + .desc = "YUYV 4:2:2", + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + }, +}; +#if 0 +static struct bcwc_format *bcwc_find_format(u32 pixelformat) +{ + unsigned i; + + for (i = 0; i < ARRAY_SIZE(bcwc_formats); i++) + if (bcwc_formats[i].pixelformat == pixelformat) + return bcwc_formats + i; + /* Not found? Then return the first format. */ + return bcwc_formats; +} +#endif +static const struct v4l2_pix_format bcwc_def_pix_format = { + .width = 1280, + .height = 720, + .pixelformat = V4L2_PIX_FMT_YUYV, + .field = V4L2_FIELD_NONE, + .bytesperline = 1280 * 2, + .sizeimage = 1280 * 720 * 2, +}; + +static const u32 bcwc_def_mbus_code = MEDIA_BUS_FMT_YUYV8_2X8; + +static int bcwc_v4l2_ioctl_enum_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->index >= ARRAY_SIZE(bcwc_formats)) + return -EINVAL; + strlcpy(fmt->description, bcwc_formats[fmt->index].desc, + sizeof(fmt->description)); + fmt->pixelformat = bcwc_formats[fmt->index].pixelformat; + return 0; +} + +static int bcwc_v4l2_ioctl_try_fmt_vid_cap(struct file *filp, void *_priv, + struct v4l2_format *fmt) +{ + struct bcwc_private *priv = video_drvdata(filp); + pr_info("%s: %dx%d\n", __FUNCTION__, fmt->fmt.pix.width, fmt->fmt.pix.height); + + if (fmt->fmt.pix.height != 720 || + fmt->fmt.pix.width != 1280 || + fmt->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV) + return -EINVAL; + + priv->user_format.height = fmt->fmt.pix.height; + priv->user_format.width = fmt->fmt.pix.width; + priv->user_format.sizeimage = priv->user_format.height * priv->user_format.width * 2; + return 0; +} + +static int bcwc_v4l2_ioctl_g_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *fmt) +{ + fmt->fmt.pix.height = 720; + fmt->fmt.pix.width = 1280; + fmt->fmt.pix.sizeimage = 1280 * 720; + fmt->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; + return 0; +} + +static int bcwc_v4l2_ioctl_s_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *fmt) +{ + pr_info("%s: %dx%d\n", __FUNCTION__, fmt->fmt.pix.width, fmt->fmt.pix.height); + return 0; +} + +static int bcwc_v4l2_ioctl_reqbufs(struct file *filp, void *_priv, + struct v4l2_requestbuffers *rb) +{ + struct bcwc_private *priv = video_drvdata(filp); + return videobuf_reqbufs(&priv->vb_queue, rb); +} + +static int bcwc_v4l2_ioctl_querybuf(struct file *filp, void *_priv, + struct v4l2_buffer *buf) +{ + struct bcwc_private *priv = video_drvdata(filp); + return videobuf_querybuf(&priv->vb_queue, buf); +} + +static int bcwc_v4l2_ioctl_qbuf(struct file *filp, void *_priv, + struct v4l2_buffer *buf) +{ + struct bcwc_private *priv = video_drvdata(filp); + return videobuf_qbuf(&priv->vb_queue, buf); +} + +static int bcwc_v4l2_ioctl_dqbuf(struct file *filp, void *_priv, + struct v4l2_buffer *buf) +{ + struct bcwc_private *priv = video_drvdata(filp); + return videobuf_dqbuf(&priv->vb_queue, buf, filp->f_flags & O_NONBLOCK); +} + +static int bcwc_v4l2_ioctl_streamon(struct file *filp, void *priv, enum v4l2_buf_type t) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENODEV; +} + +static int bcwc_v4l2_ioctl_streamoff(struct file *filp, void *priv, enum v4l2_buf_type t) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENODEV; +} + +static int bcwc_v4l2_ioctl_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENODEV; +} + +static int bcwc_v4l2_ioctl_s_parm(struct file *filp, void *priv, + struct v4l2_streamparm *parm) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENODEV; +} + +static int bcwc_v4l2_ioctl_enum_framesizes(struct file *filp, void *priv, + struct v4l2_frmsizeenum *sizes) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENODEV; +} + +static int bcwc_v4l2_ioctl_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *interval) +{ + pr_debug("%s\n", __FUNCTION__); + return -ENODEV; +} + +static struct v4l2_ioctl_ops bcwc_ioctl_ops = { + .vidioc_enum_input = bcwc_v4l2_ioctl_enum_input, + .vidioc_g_input = bcwc_v4l2_ioctl_g_input, + .vidioc_s_input = bcwc_v4l2_ioctl_s_input, + .vidioc_s_std = bcwc_v4l2_ioctl_s_std, + .vidioc_g_std = bcwc_v4l2_ioctl_g_std, + .vidioc_enum_fmt_vid_cap = bcwc_v4l2_ioctl_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = bcwc_v4l2_ioctl_try_fmt_vid_cap, + + .vidioc_g_fmt_vid_cap = bcwc_v4l2_ioctl_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = bcwc_v4l2_ioctl_s_fmt_vid_cap, + .vidioc_querycap = bcwc_v4l2_ioctl_querycap, + .vidioc_reqbufs = bcwc_v4l2_ioctl_reqbufs, + + .vidioc_querybuf = bcwc_v4l2_ioctl_querybuf, + .vidioc_qbuf = bcwc_v4l2_ioctl_qbuf, + .vidioc_dqbuf = bcwc_v4l2_ioctl_dqbuf, + + .vidioc_streamon = bcwc_v4l2_ioctl_streamon, + .vidioc_streamoff = bcwc_v4l2_ioctl_streamoff, + .vidioc_g_parm = bcwc_v4l2_ioctl_g_parm, + .vidioc_s_parm = bcwc_v4l2_ioctl_s_parm, + .vidioc_enum_framesizes = bcwc_v4l2_ioctl_enum_framesizes, + .vidioc_enum_frameintervals = bcwc_v4l2_ioctl_enum_frameintervals, +}; + +int bcwc_v4l2_register(struct bcwc_private *dev_priv) +{ + struct v4l2_device *v4l2_dev = &dev_priv->v4l2_dev; + struct video_device *vdev; + int ret; + + ret = v4l2_device_register(&dev_priv->pdev->dev, v4l2_dev); + if (ret) { + pr_err("v4l2_device_register: %d\n", ret); + return ret; + } + + vdev = video_device_alloc(); + if (!vdev) { + ret = -ENOMEM; + goto fail; + } + dev_priv->videodev = vdev; + + vdev->v4l2_dev = v4l2_dev; + strcpy(vdev->name, "Apple Facetime HD"); // XXX: Length? + vdev->vfl_dir = VFL_DIR_RX; + vdev->fops = &bcwc_vdev_fops; + vdev->ioctl_ops = &bcwc_ioctl_ops; + vdev->release = video_device_release; + pr_info("%s: dev_priv %p\n", __FUNCTION__, dev_priv); + video_set_drvdata(vdev, dev_priv); + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret) { + video_device_release(vdev); + goto fail; + } + return 0; +fail: + v4l2_device_unregister(&dev_priv->v4l2_dev); + return ret; +} + +void bcwc_v4l2_unregister(struct bcwc_private *dev_priv) +{ + video_unregister_device(dev_priv->videodev); + v4l2_device_unregister(&dev_priv->v4l2_dev); +} diff --git a/bcwc_v4l2.h b/bcwc_v4l2.h new file mode 100644 index 0000000..ef4e44a --- /dev/null +++ b/bcwc_v4l2.h @@ -0,0 +1,34 @@ +/* + * Broadcom PCIe 1570 webcam driver + * + * Copyright (C) 2015 Sven Schnelle + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation. + * + */ + +#ifndef _BCWC_PCIE_H +#define _PCWC_PCIE_H + +#include +#include +#include +#include +#include +#include + +struct bcwc_private; +extern int bcwc_v4l2_register(struct bcwc_private *dev_priv); +extern void bcwc_v4l2_unregister(struct bcwc_private *dev_priv); + +#endif