From 14fbff6b4e48a529c90e771598ac12bffd445ff4 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 25 Apr 2016 18:03:08 +0200 Subject: samples: connector: from Documentation to samples directory A small bug with the new autoksyms support showed that there are two kernel modules in the Documentation directory that qualify as samples, while all other samples are in the samples/ directory. This patch was originally meant as a workaround for that bug, but it has now been solved in a different way. However, I still think it makes sense as a cleanup to consolidate all sample code in one place. Signed-off-by: Arnd Bergmann Signed-off-by: Jonathan Corbet --- samples/Kconfig | 9 ++ samples/Makefile | 2 +- samples/connector/.gitignore | 1 + samples/connector/Makefile | 16 +++ samples/connector/cn_test.c | 201 ++++++++++++++++++++++++++++++++++ samples/connector/ucon.c | 250 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 samples/connector/.gitignore create mode 100644 samples/connector/Makefile create mode 100644 samples/connector/cn_test.c create mode 100644 samples/connector/ucon.c (limited to 'samples') diff --git a/samples/Kconfig b/samples/Kconfig index d54f28c6dc5e..559a58baff6e 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -76,4 +76,13 @@ config SAMPLE_CONFIGFS help Builds a sample configfs interface. +config SAMPLE_CONNECTOR + tristate "Build connector sample -- loadable modules only" + depends on CONNECTOR && m + help + When enabled, this builds both a sample kernel module for + the connector interface and a user space tool to communicate + with it. + See also Documentation/connector/connector.txt + endif # SAMPLES diff --git a/samples/Makefile b/samples/Makefile index 48001d7e23f0..594ef7d9fa2a 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -2,4 +2,4 @@ obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \ - configfs/ + configfs/ connector/ diff --git a/samples/connector/.gitignore b/samples/connector/.gitignore new file mode 100644 index 000000000000..d2b9c32accd4 --- /dev/null +++ b/samples/connector/.gitignore @@ -0,0 +1 @@ +ucon diff --git a/samples/connector/Makefile b/samples/connector/Makefile new file mode 100644 index 000000000000..04b9622b6f51 --- /dev/null +++ b/samples/connector/Makefile @@ -0,0 +1,16 @@ +obj-$(CONFIG_SAMPLE_CONNECTOR) += cn_test.o + +# List of programs to build +ifdef CONFIG_SAMPLE_CONNECTOR +hostprogs-y := ucon +endif + +# Tell kbuild to always build the programs +always := $(hostprogs-y) + +HOSTCFLAGS_ucon.o += -I$(objtree)/usr/include + +all: modules + +modules clean: + $(MAKE) -C ../.. SUBDIRS=$(PWD) $@ diff --git a/samples/connector/cn_test.c b/samples/connector/cn_test.c new file mode 100644 index 000000000000..d12cc944b696 --- /dev/null +++ b/samples/connector/cn_test.c @@ -0,0 +1,201 @@ +/* + * cn_test.c + * + * 2004+ Copyright (c) Evgeniy Polyakov + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) "cn_test: " fmt + +#include +#include +#include +#include +#include +#include + +#include + +static struct cb_id cn_test_id = { CN_NETLINK_USERS + 3, 0x456 }; +static char cn_test_name[] = "cn_test"; +static struct sock *nls; +static struct timer_list cn_test_timer; + +static void cn_test_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) +{ + pr_info("%s: %lu: idx=%x, val=%x, seq=%u, ack=%u, len=%d: %s.\n", + __func__, jiffies, msg->id.idx, msg->id.val, + msg->seq, msg->ack, msg->len, + msg->len ? (char *)msg->data : ""); +} + +/* + * Do not remove this function even if no one is using it as + * this is an example of how to get notifications about new + * connector user registration + */ +#if 0 +static int cn_test_want_notify(void) +{ + struct cn_ctl_msg *ctl; + struct cn_notify_req *req; + struct cn_msg *msg = NULL; + int size, size0; + struct sk_buff *skb; + struct nlmsghdr *nlh; + u32 group = 1; + + size0 = sizeof(*msg) + sizeof(*ctl) + 3 * sizeof(*req); + + size = NLMSG_SPACE(size0); + + skb = alloc_skb(size, GFP_ATOMIC); + if (!skb) { + pr_err("failed to allocate new skb with size=%u\n", size); + return -ENOMEM; + } + + nlh = nlmsg_put(skb, 0, 0x123, NLMSG_DONE, size - sizeof(*nlh), 0); + if (!nlh) { + kfree_skb(skb); + return -EMSGSIZE; + } + + msg = nlmsg_data(nlh); + + memset(msg, 0, size0); + + msg->id.idx = -1; + msg->id.val = -1; + msg->seq = 0x123; + msg->ack = 0x345; + msg->len = size0 - sizeof(*msg); + + ctl = (struct cn_ctl_msg *)(msg + 1); + + ctl->idx_notify_num = 1; + ctl->val_notify_num = 2; + ctl->group = group; + ctl->len = msg->len - sizeof(*ctl); + + req = (struct cn_notify_req *)(ctl + 1); + + /* + * Idx. + */ + req->first = cn_test_id.idx; + req->range = 10; + + /* + * Val 0. + */ + req++; + req->first = cn_test_id.val; + req->range = 10; + + /* + * Val 1. + */ + req++; + req->first = cn_test_id.val + 20; + req->range = 10; + + NETLINK_CB(skb).dst_group = ctl->group; + //netlink_broadcast(nls, skb, 0, ctl->group, GFP_ATOMIC); + netlink_unicast(nls, skb, 0, 0); + + pr_info("request was sent: group=0x%x\n", ctl->group); + + return 0; +} +#endif + +static u32 cn_test_timer_counter; +static void cn_test_timer_func(unsigned long __data) +{ + struct cn_msg *m; + char data[32]; + + pr_debug("%s: timer fired with data %lu\n", __func__, __data); + + m = kzalloc(sizeof(*m) + sizeof(data), GFP_ATOMIC); + if (m) { + + memcpy(&m->id, &cn_test_id, sizeof(m->id)); + m->seq = cn_test_timer_counter; + m->len = sizeof(data); + + m->len = + scnprintf(data, sizeof(data), "counter = %u", + cn_test_timer_counter) + 1; + + memcpy(m + 1, data, m->len); + + cn_netlink_send(m, 0, 0, GFP_ATOMIC); + kfree(m); + } + + cn_test_timer_counter++; + + mod_timer(&cn_test_timer, jiffies + msecs_to_jiffies(1000)); +} + +static int cn_test_init(void) +{ + int err; + + err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback); + if (err) + goto err_out; + cn_test_id.val++; + err = cn_add_callback(&cn_test_id, cn_test_name, cn_test_callback); + if (err) { + cn_del_callback(&cn_test_id); + goto err_out; + } + + setup_timer(&cn_test_timer, cn_test_timer_func, 0); + mod_timer(&cn_test_timer, jiffies + msecs_to_jiffies(1000)); + + pr_info("initialized with id={%u.%u}\n", + cn_test_id.idx, cn_test_id.val); + + return 0; + + err_out: + if (nls && nls->sk_socket) + sock_release(nls->sk_socket); + + return err; +} + +static void cn_test_fini(void) +{ + del_timer_sync(&cn_test_timer); + cn_del_callback(&cn_test_id); + cn_test_id.val--; + cn_del_callback(&cn_test_id); + if (nls && nls->sk_socket) + sock_release(nls->sk_socket); +} + +module_init(cn_test_init); +module_exit(cn_test_fini); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Evgeniy Polyakov "); +MODULE_DESCRIPTION("Connector's test module"); diff --git a/samples/connector/ucon.c b/samples/connector/ucon.c new file mode 100644 index 000000000000..8a4da64e02a8 --- /dev/null +++ b/samples/connector/ucon.c @@ -0,0 +1,250 @@ +/* + * ucon.c + * + * Copyright (c) 2004+ Evgeniy Polyakov + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DEBUG +#define NETLINK_CONNECTOR 11 + +/* Hopefully your userspace connector.h matches this kernel */ +#define CN_TEST_IDX CN_NETLINK_USERS + 3 +#define CN_TEST_VAL 0x456 + +#ifdef DEBUG +#define ulog(f, a...) fprintf(stdout, f, ##a) +#else +#define ulog(f, a...) do {} while (0) +#endif + +static int need_exit; +static __u32 seq; + +static int netlink_send(int s, struct cn_msg *msg) +{ + struct nlmsghdr *nlh; + unsigned int size; + int err; + char buf[128]; + struct cn_msg *m; + + size = NLMSG_SPACE(sizeof(struct cn_msg) + msg->len); + + nlh = (struct nlmsghdr *)buf; + nlh->nlmsg_seq = seq++; + nlh->nlmsg_pid = getpid(); + nlh->nlmsg_type = NLMSG_DONE; + nlh->nlmsg_len = size; + nlh->nlmsg_flags = 0; + + m = NLMSG_DATA(nlh); +#if 0 + ulog("%s: [%08x.%08x] len=%u, seq=%u, ack=%u.\n", + __func__, msg->id.idx, msg->id.val, msg->len, msg->seq, msg->ack); +#endif + memcpy(m, msg, sizeof(*m) + msg->len); + + err = send(s, nlh, size, 0); + if (err == -1) + ulog("Failed to send: %s [%d].\n", + strerror(errno), errno); + + return err; +} + +static void usage(void) +{ + printf( + "Usage: ucon [options] [output file]\n" + "\n" + "\t-h\tthis help screen\n" + "\t-s\tsend buffers to the test module\n" + "\n" + "The default behavior of ucon is to subscribe to the test module\n" + "and wait for state messages. Any ones received are dumped to the\n" + "specified output file (or stdout). The test module is assumed to\n" + "have an id of {%u.%u}\n" + "\n" + "If you get no output, then verify the cn_test module id matches\n" + "the expected id above.\n" + , CN_TEST_IDX, CN_TEST_VAL + ); +} + +int main(int argc, char *argv[]) +{ + int s; + char buf[1024]; + int len; + struct nlmsghdr *reply; + struct sockaddr_nl l_local; + struct cn_msg *data; + FILE *out; + time_t tm; + struct pollfd pfd; + bool send_msgs = false; + + while ((s = getopt(argc, argv, "hs")) != -1) { + switch (s) { + case 's': + send_msgs = true; + break; + + case 'h': + usage(); + return 0; + + default: + /* getopt() outputs an error for us */ + usage(); + return 1; + } + } + + if (argc != optind) { + out = fopen(argv[optind], "a+"); + if (!out) { + ulog("Unable to open %s for writing: %s\n", + argv[1], strerror(errno)); + out = stdout; + } + } else + out = stdout; + + memset(buf, 0, sizeof(buf)); + + s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); + if (s == -1) { + perror("socket"); + return -1; + } + + l_local.nl_family = AF_NETLINK; + l_local.nl_groups = -1; /* bitmask of requested groups */ + l_local.nl_pid = 0; + + ulog("subscribing to %u.%u\n", CN_TEST_IDX, CN_TEST_VAL); + + if (bind(s, (struct sockaddr *)&l_local, sizeof(struct sockaddr_nl)) == -1) { + perror("bind"); + close(s); + return -1; + } + +#if 0 + { + int on = 0x57; /* Additional group number */ + setsockopt(s, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &on, sizeof(on)); + } +#endif + if (send_msgs) { + int i, j; + + memset(buf, 0, sizeof(buf)); + + data = (struct cn_msg *)buf; + + data->id.idx = CN_TEST_IDX; + data->id.val = CN_TEST_VAL; + data->seq = seq++; + data->ack = 0; + data->len = 0; + + for (j=0; j<10; ++j) { + for (i=0; i<1000; ++i) { + len = netlink_send(s, data); + } + + ulog("%d messages have been sent to %08x.%08x.\n", i, data->id.idx, data->id.val); + } + + return 0; + } + + + pfd.fd = s; + + while (!need_exit) { + pfd.events = POLLIN; + pfd.revents = 0; + switch (poll(&pfd, 1, -1)) { + case 0: + need_exit = 1; + break; + case -1: + if (errno != EINTR) { + need_exit = 1; + break; + } + continue; + } + if (need_exit) + break; + + memset(buf, 0, sizeof(buf)); + len = recv(s, buf, sizeof(buf), 0); + if (len == -1) { + perror("recv buf"); + close(s); + return -1; + } + reply = (struct nlmsghdr *)buf; + + switch (reply->nlmsg_type) { + case NLMSG_ERROR: + fprintf(out, "Error message received.\n"); + fflush(out); + break; + case NLMSG_DONE: + data = (struct cn_msg *)NLMSG_DATA(reply); + + time(&tm); + fprintf(out, "%.24s : [%x.%x] [%08u.%08u].\n", + ctime(&tm), data->id.idx, data->id.val, data->seq, data->ack); + fflush(out); + break; + default: + break; + } + } + + close(s); + return 0; +} -- cgit v1.2.3-59-g8ed1b From 934275c4805ce0bbb0df633bd6015dd455ff6cad Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 25 Apr 2016 18:03:09 +0200 Subject: samples: v4l: from Documentation to samples directory A small bug with the new autoksyms support showed that there are two kernel modules in the Documentation directory that qualify as samples, while all other samples are in the samples/ directory. This patch was originally meant as a workaround for that bug, but it has now been solved in a different way. However, I still think it makes sense as a cleanup to consolidate all sample code in one place. Signed-off-by: Arnd Bergmann Acked-by: Hans Verkuil Acked-by: Mauro Carvalho Chehab Signed-off-by: Jonathan Corbet --- Documentation/Makefile | 3 +- Documentation/video4linux/Makefile | 1 - Documentation/video4linux/v4l2-framework.txt | 2 +- Documentation/video4linux/v4l2-pci-skeleton.c | 923 -------------------------- samples/Makefile | 2 +- samples/v4l/Makefile | 1 + samples/v4l/v4l2-pci-skeleton.c | 923 ++++++++++++++++++++++++++ 7 files changed, 927 insertions(+), 928 deletions(-) delete mode 100644 Documentation/video4linux/Makefile delete mode 100644 Documentation/video4linux/v4l2-pci-skeleton.c create mode 100644 samples/v4l/Makefile create mode 100644 samples/v4l/v4l2-pci-skeleton.c (limited to 'samples') diff --git a/Documentation/Makefile b/Documentation/Makefile index 13b5ae1b87aa..de955e151af8 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -1,4 +1,3 @@ subdir-y := accounting auxdisplay blackfin \ filesystems filesystems ia64 laptops mic misc-devices \ - networking pcmcia prctl ptp timers vDSO video4linux \ - watchdog + networking pcmcia prctl ptp timers vDSO watchdog diff --git a/Documentation/video4linux/Makefile b/Documentation/video4linux/Makefile deleted file mode 100644 index 65a351d75c95..000000000000 --- a/Documentation/video4linux/Makefile +++ /dev/null @@ -1 +0,0 @@ -obj-$(CONFIG_VIDEO_PCI_SKELETON) := v4l2-pci-skeleton.o diff --git a/Documentation/video4linux/v4l2-framework.txt b/Documentation/video4linux/v4l2-framework.txt index fa41608ab2b4..cbefc7902f5f 100644 --- a/Documentation/video4linux/v4l2-framework.txt +++ b/Documentation/video4linux/v4l2-framework.txt @@ -35,7 +35,7 @@ need and this same framework should make it much easier to refactor common code into utility functions shared by all drivers. A good example to look at as a reference is the v4l2-pci-skeleton.c -source that is available in this directory. It is a skeleton driver for +source that is available in samples/v4l/. It is a skeleton driver for a PCI capture card, and demonstrates how to use the V4L2 driver framework. It can be used as a template for real PCI video capture driver. diff --git a/Documentation/video4linux/v4l2-pci-skeleton.c b/Documentation/video4linux/v4l2-pci-skeleton.c deleted file mode 100644 index 79af0c041056..000000000000 --- a/Documentation/video4linux/v4l2-pci-skeleton.c +++ /dev/null @@ -1,923 +0,0 @@ -/* - * This is a V4L2 PCI Skeleton Driver. It gives an initial skeleton source - * for use with other PCI drivers. - * - * This skeleton PCI driver assumes that the card has an S-Video connector as - * input 0 and an HDMI connector as input 1. - * - * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. - * - * This program is free software; you may redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; version 2 of the License. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -MODULE_DESCRIPTION("V4L2 PCI Skeleton Driver"); -MODULE_AUTHOR("Hans Verkuil"); -MODULE_LICENSE("GPL v2"); - -/** - * struct skeleton - All internal data for one instance of device - * @pdev: PCI device - * @v4l2_dev: top-level v4l2 device struct - * @vdev: video node structure - * @ctrl_handler: control handler structure - * @lock: ioctl serialization mutex - * @std: current SDTV standard - * @timings: current HDTV timings - * @format: current pix format - * @input: current video input (0 = SDTV, 1 = HDTV) - * @queue: vb2 video capture queue - * @alloc_ctx: vb2 contiguous DMA context - * @qlock: spinlock controlling access to buf_list and sequence - * @buf_list: list of buffers queued for DMA - * @sequence: frame sequence counter - */ -struct skeleton { - struct pci_dev *pdev; - struct v4l2_device v4l2_dev; - struct video_device vdev; - struct v4l2_ctrl_handler ctrl_handler; - struct mutex lock; - v4l2_std_id std; - struct v4l2_dv_timings timings; - struct v4l2_pix_format format; - unsigned input; - - struct vb2_queue queue; - struct vb2_alloc_ctx *alloc_ctx; - - spinlock_t qlock; - struct list_head buf_list; - unsigned field; - unsigned sequence; -}; - -struct skel_buffer { - struct vb2_buffer vb; - struct list_head list; -}; - -static inline struct skel_buffer *to_skel_buffer(struct vb2_buffer *vb2) -{ - return container_of(vb2, struct skel_buffer, vb); -} - -static const struct pci_device_id skeleton_pci_tbl[] = { - /* { PCI_DEVICE(PCI_VENDOR_ID_, PCI_DEVICE_ID_) }, */ - { 0, } -}; -MODULE_DEVICE_TABLE(pci, skeleton_pci_tbl); - -/* - * HDTV: this structure has the capabilities of the HDTV receiver. - * It is used to constrain the huge list of possible formats based - * upon the hardware capabilities. - */ -static const struct v4l2_dv_timings_cap skel_timings_cap = { - .type = V4L2_DV_BT_656_1120, - /* keep this initialization for compatibility with GCC < 4.4.6 */ - .reserved = { 0 }, - V4L2_INIT_BT_TIMINGS( - 720, 1920, /* min/max width */ - 480, 1080, /* min/max height */ - 27000000, 74250000, /* min/max pixelclock*/ - V4L2_DV_BT_STD_CEA861, /* Supported standards */ - /* capabilities */ - V4L2_DV_BT_CAP_INTERLACED | V4L2_DV_BT_CAP_PROGRESSIVE - ) -}; - -/* - * Supported SDTV standards. This does the same job as skel_timings_cap, but - * for standard TV formats. - */ -#define SKEL_TVNORMS V4L2_STD_ALL - -/* - * Interrupt handler: typically interrupts happen after a new frame has been - * captured. It is the job of the handler to remove the new frame from the - * internal list and give it back to the vb2 framework, updating the sequence - * counter, field and timestamp at the same time. - */ -static irqreturn_t skeleton_irq(int irq, void *dev_id) -{ -#ifdef TODO - struct skeleton *skel = dev_id; - - /* handle interrupt */ - - /* Once a new frame has been captured, mark it as done like this: */ - if (captured_new_frame) { - ... - spin_lock(&skel->qlock); - list_del(&new_buf->list); - spin_unlock(&skel->qlock); - v4l2_get_timestamp(&new_buf->vb.v4l2_buf.timestamp); - new_buf->vb.v4l2_buf.sequence = skel->sequence++; - new_buf->vb.v4l2_buf.field = skel->field; - if (skel->format.field == V4L2_FIELD_ALTERNATE) { - if (skel->field == V4L2_FIELD_BOTTOM) - skel->field = V4L2_FIELD_TOP; - else if (skel->field == V4L2_FIELD_TOP) - skel->field = V4L2_FIELD_BOTTOM; - } - vb2_buffer_done(&new_buf->vb, VB2_BUF_STATE_DONE); - } -#endif - return IRQ_HANDLED; -} - -/* - * Setup the constraints of the queue: besides setting the number of planes - * per buffer and the size and allocation context of each plane, it also - * checks if sufficient buffers have been allocated. Usually 3 is a good - * minimum number: many DMA engines need a minimum of 2 buffers in the - * queue and you need to have another available for userspace processing. - */ -static int queue_setup(struct vb2_queue *vq, - unsigned int *nbuffers, unsigned int *nplanes, - unsigned int sizes[], void *alloc_ctxs[]) -{ - struct skeleton *skel = vb2_get_drv_priv(vq); - - skel->field = skel->format.field; - if (skel->field == V4L2_FIELD_ALTERNATE) { - /* - * You cannot use read() with FIELD_ALTERNATE since the field - * information (TOP/BOTTOM) cannot be passed back to the user. - */ - if (vb2_fileio_is_active(vq)) - return -EINVAL; - skel->field = V4L2_FIELD_TOP; - } - - if (vq->num_buffers + *nbuffers < 3) - *nbuffers = 3 - vq->num_buffers; - alloc_ctxs[0] = skel->alloc_ctx; - - if (*nplanes) - return sizes[0] < skel->format.sizeimage ? -EINVAL : 0; - *nplanes = 1; - sizes[0] = skel->format.sizeimage; - return 0; -} - -/* - * Prepare the buffer for queueing to the DMA engine: check and set the - * payload size. - */ -static int buffer_prepare(struct vb2_buffer *vb) -{ - struct skeleton *skel = vb2_get_drv_priv(vb->vb2_queue); - unsigned long size = skel->format.sizeimage; - - if (vb2_plane_size(vb, 0) < size) { - dev_err(&skel->pdev->dev, "buffer too small (%lu < %lu)\n", - vb2_plane_size(vb, 0), size); - return -EINVAL; - } - - vb2_set_plane_payload(vb, 0, size); - return 0; -} - -/* - * Queue this buffer to the DMA engine. - */ -static void buffer_queue(struct vb2_buffer *vb) -{ - struct skeleton *skel = vb2_get_drv_priv(vb->vb2_queue); - struct skel_buffer *buf = to_skel_buffer(vb); - unsigned long flags; - - spin_lock_irqsave(&skel->qlock, flags); - list_add_tail(&buf->list, &skel->buf_list); - - /* TODO: Update any DMA pointers if necessary */ - - spin_unlock_irqrestore(&skel->qlock, flags); -} - -static void return_all_buffers(struct skeleton *skel, - enum vb2_buffer_state state) -{ - struct skel_buffer *buf, *node; - unsigned long flags; - - spin_lock_irqsave(&skel->qlock, flags); - list_for_each_entry_safe(buf, node, &skel->buf_list, list) { - vb2_buffer_done(&buf->vb, state); - list_del(&buf->list); - } - spin_unlock_irqrestore(&skel->qlock, flags); -} - -/* - * Start streaming. First check if the minimum number of buffers have been - * queued. If not, then return -ENOBUFS and the vb2 framework will call - * this function again the next time a buffer has been queued until enough - * buffers are available to actually start the DMA engine. - */ -static int start_streaming(struct vb2_queue *vq, unsigned int count) -{ - struct skeleton *skel = vb2_get_drv_priv(vq); - int ret = 0; - - skel->sequence = 0; - - /* TODO: start DMA */ - - if (ret) { - /* - * In case of an error, return all active buffers to the - * QUEUED state - */ - return_all_buffers(skel, VB2_BUF_STATE_QUEUED); - } - return ret; -} - -/* - * Stop the DMA engine. Any remaining buffers in the DMA queue are dequeued - * and passed on to the vb2 framework marked as STATE_ERROR. - */ -static void stop_streaming(struct vb2_queue *vq) -{ - struct skeleton *skel = vb2_get_drv_priv(vq); - - /* TODO: stop DMA */ - - /* Release all active buffers */ - return_all_buffers(skel, VB2_BUF_STATE_ERROR); -} - -/* - * The vb2 queue ops. Note that since q->lock is set we can use the standard - * vb2_ops_wait_prepare/finish helper functions. If q->lock would be NULL, - * then this driver would have to provide these ops. - */ -static struct vb2_ops skel_qops = { - .queue_setup = queue_setup, - .buf_prepare = buffer_prepare, - .buf_queue = buffer_queue, - .start_streaming = start_streaming, - .stop_streaming = stop_streaming, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, -}; - -/* - * Required ioctl querycap. Note that the version field is prefilled with - * the version of the kernel. - */ -static int skeleton_querycap(struct file *file, void *priv, - struct v4l2_capability *cap) -{ - struct skeleton *skel = video_drvdata(file); - - strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); - strlcpy(cap->card, "V4L2 PCI Skeleton", sizeof(cap->card)); - snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", - pci_name(skel->pdev)); - cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | - V4L2_CAP_STREAMING; - cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; - return 0; -} - -/* - * Helper function to check and correct struct v4l2_pix_format. It's used - * not only in VIDIOC_TRY/S_FMT, but also elsewhere if changes to the SDTV - * standard, HDTV timings or the video input would require updating the - * current format. - */ -static void skeleton_fill_pix_format(struct skeleton *skel, - struct v4l2_pix_format *pix) -{ - pix->pixelformat = V4L2_PIX_FMT_YUYV; - if (skel->input == 0) { - /* S-Video input */ - pix->width = 720; - pix->height = (skel->std & V4L2_STD_525_60) ? 480 : 576; - pix->field = V4L2_FIELD_INTERLACED; - pix->colorspace = V4L2_COLORSPACE_SMPTE170M; - } else { - /* HDMI input */ - pix->width = skel->timings.bt.width; - pix->height = skel->timings.bt.height; - if (skel->timings.bt.interlaced) { - pix->field = V4L2_FIELD_ALTERNATE; - pix->height /= 2; - } else { - pix->field = V4L2_FIELD_NONE; - } - pix->colorspace = V4L2_COLORSPACE_REC709; - } - - /* - * The YUYV format is four bytes for every two pixels, so bytesperline - * is width * 2. - */ - pix->bytesperline = pix->width * 2; - pix->sizeimage = pix->bytesperline * pix->height; - pix->priv = 0; -} - -static int skeleton_try_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) -{ - struct skeleton *skel = video_drvdata(file); - struct v4l2_pix_format *pix = &f->fmt.pix; - - /* - * Due to historical reasons providing try_fmt with an unsupported - * pixelformat will return -EINVAL for video receivers. Webcam drivers, - * however, will silently correct the pixelformat. Some video capture - * applications rely on this behavior... - */ - if (pix->pixelformat != V4L2_PIX_FMT_YUYV) - return -EINVAL; - skeleton_fill_pix_format(skel, pix); - return 0; -} - -static int skeleton_s_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) -{ - struct skeleton *skel = video_drvdata(file); - int ret; - - ret = skeleton_try_fmt_vid_cap(file, priv, f); - if (ret) - return ret; - - /* - * It is not allowed to change the format while buffers for use with - * streaming have already been allocated. - */ - if (vb2_is_busy(&skel->queue)) - return -EBUSY; - - /* TODO: change format */ - skel->format = f->fmt.pix; - return 0; -} - -static int skeleton_g_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) -{ - struct skeleton *skel = video_drvdata(file); - - f->fmt.pix = skel->format; - return 0; -} - -static int skeleton_enum_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_fmtdesc *f) -{ - if (f->index != 0) - return -EINVAL; - - f->pixelformat = V4L2_PIX_FMT_YUYV; - return 0; -} - -static int skeleton_s_std(struct file *file, void *priv, v4l2_std_id std) -{ - struct skeleton *skel = video_drvdata(file); - - /* S_STD is not supported on the HDMI input */ - if (skel->input) - return -ENODATA; - - /* - * No change, so just return. Some applications call S_STD again after - * the buffers for streaming have been set up, so we have to allow for - * this behavior. - */ - if (std == skel->std) - return 0; - - /* - * Changing the standard implies a format change, which is not allowed - * while buffers for use with streaming have already been allocated. - */ - if (vb2_is_busy(&skel->queue)) - return -EBUSY; - - /* TODO: handle changing std */ - - skel->std = std; - - /* Update the internal format */ - skeleton_fill_pix_format(skel, &skel->format); - return 0; -} - -static int skeleton_g_std(struct file *file, void *priv, v4l2_std_id *std) -{ - struct skeleton *skel = video_drvdata(file); - - /* G_STD is not supported on the HDMI input */ - if (skel->input) - return -ENODATA; - - *std = skel->std; - return 0; -} - -/* - * Query the current standard as seen by the hardware. This function shall - * never actually change the standard, it just detects and reports. - * The framework will initially set *std to tvnorms (i.e. the set of - * supported standards by this input), and this function should just AND - * this value. If there is no signal, then *std should be set to 0. - */ -static int skeleton_querystd(struct file *file, void *priv, v4l2_std_id *std) -{ - struct skeleton *skel = video_drvdata(file); - - /* QUERY_STD is not supported on the HDMI input */ - if (skel->input) - return -ENODATA; - -#ifdef TODO - /* - * Query currently seen standard. Initial value of *std is - * V4L2_STD_ALL. This function should look something like this: - */ - get_signal_info(); - if (no_signal) { - *std = 0; - return 0; - } - /* Use signal information to reduce the number of possible standards */ - if (signal_has_525_lines) - *std &= V4L2_STD_525_60; - else - *std &= V4L2_STD_625_50; -#endif - return 0; -} - -static int skeleton_s_dv_timings(struct file *file, void *_fh, - struct v4l2_dv_timings *timings) -{ - struct skeleton *skel = video_drvdata(file); - - /* S_DV_TIMINGS is not supported on the S-Video input */ - if (skel->input == 0) - return -ENODATA; - - /* Quick sanity check */ - if (!v4l2_valid_dv_timings(timings, &skel_timings_cap, NULL, NULL)) - return -EINVAL; - - /* Check if the timings are part of the CEA-861 timings. */ - if (!v4l2_find_dv_timings_cap(timings, &skel_timings_cap, - 0, NULL, NULL)) - return -EINVAL; - - /* Return 0 if the new timings are the same as the current timings. */ - if (v4l2_match_dv_timings(timings, &skel->timings, 0, false)) - return 0; - - /* - * Changing the timings implies a format change, which is not allowed - * while buffers for use with streaming have already been allocated. - */ - if (vb2_is_busy(&skel->queue)) - return -EBUSY; - - /* TODO: Configure new timings */ - - /* Save timings */ - skel->timings = *timings; - - /* Update the internal format */ - skeleton_fill_pix_format(skel, &skel->format); - return 0; -} - -static int skeleton_g_dv_timings(struct file *file, void *_fh, - struct v4l2_dv_timings *timings) -{ - struct skeleton *skel = video_drvdata(file); - - /* G_DV_TIMINGS is not supported on the S-Video input */ - if (skel->input == 0) - return -ENODATA; - - *timings = skel->timings; - return 0; -} - -static int skeleton_enum_dv_timings(struct file *file, void *_fh, - struct v4l2_enum_dv_timings *timings) -{ - struct skeleton *skel = video_drvdata(file); - - /* ENUM_DV_TIMINGS is not supported on the S-Video input */ - if (skel->input == 0) - return -ENODATA; - - return v4l2_enum_dv_timings_cap(timings, &skel_timings_cap, - NULL, NULL); -} - -/* - * Query the current timings as seen by the hardware. This function shall - * never actually change the timings, it just detects and reports. - * If no signal is detected, then return -ENOLINK. If the hardware cannot - * lock to the signal, then return -ENOLCK. If the signal is out of range - * of the capabilities of the system (e.g., it is possible that the receiver - * can lock but that the DMA engine it is connected to cannot handle - * pixelclocks above a certain frequency), then -ERANGE is returned. - */ -static int skeleton_query_dv_timings(struct file *file, void *_fh, - struct v4l2_dv_timings *timings) -{ - struct skeleton *skel = video_drvdata(file); - - /* QUERY_DV_TIMINGS is not supported on the S-Video input */ - if (skel->input == 0) - return -ENODATA; - -#ifdef TODO - /* - * Query currently seen timings. This function should look - * something like this: - */ - detect_timings(); - if (no_signal) - return -ENOLINK; - if (cannot_lock_to_signal) - return -ENOLCK; - if (signal_out_of_range_of_capabilities) - return -ERANGE; - - /* Useful for debugging */ - v4l2_print_dv_timings(skel->v4l2_dev.name, "query_dv_timings:", - timings, true); -#endif - return 0; -} - -static int skeleton_dv_timings_cap(struct file *file, void *fh, - struct v4l2_dv_timings_cap *cap) -{ - struct skeleton *skel = video_drvdata(file); - - /* DV_TIMINGS_CAP is not supported on the S-Video input */ - if (skel->input == 0) - return -ENODATA; - *cap = skel_timings_cap; - return 0; -} - -static int skeleton_enum_input(struct file *file, void *priv, - struct v4l2_input *i) -{ - if (i->index > 1) - return -EINVAL; - - i->type = V4L2_INPUT_TYPE_CAMERA; - if (i->index == 0) { - i->std = SKEL_TVNORMS; - strlcpy(i->name, "S-Video", sizeof(i->name)); - i->capabilities = V4L2_IN_CAP_STD; - } else { - i->std = 0; - strlcpy(i->name, "HDMI", sizeof(i->name)); - i->capabilities = V4L2_IN_CAP_DV_TIMINGS; - } - return 0; -} - -static int skeleton_s_input(struct file *file, void *priv, unsigned int i) -{ - struct skeleton *skel = video_drvdata(file); - - if (i > 1) - return -EINVAL; - - /* - * Changing the input implies a format change, which is not allowed - * while buffers for use with streaming have already been allocated. - */ - if (vb2_is_busy(&skel->queue)) - return -EBUSY; - - skel->input = i; - /* - * Update tvnorms. The tvnorms value is used by the core to implement - * VIDIOC_ENUMSTD so it has to be correct. If tvnorms == 0, then - * ENUMSTD will return -ENODATA. - */ - skel->vdev.tvnorms = i ? 0 : SKEL_TVNORMS; - - /* Update the internal format */ - skeleton_fill_pix_format(skel, &skel->format); - return 0; -} - -static int skeleton_g_input(struct file *file, void *priv, unsigned int *i) -{ - struct skeleton *skel = video_drvdata(file); - - *i = skel->input; - return 0; -} - -/* The control handler. */ -static int skeleton_s_ctrl(struct v4l2_ctrl *ctrl) -{ - /*struct skeleton *skel = - container_of(ctrl->handler, struct skeleton, ctrl_handler);*/ - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - /* TODO: set brightness to ctrl->val */ - break; - case V4L2_CID_CONTRAST: - /* TODO: set contrast to ctrl->val */ - break; - case V4L2_CID_SATURATION: - /* TODO: set saturation to ctrl->val */ - break; - case V4L2_CID_HUE: - /* TODO: set hue to ctrl->val */ - break; - default: - return -EINVAL; - } - return 0; -} - -/* ------------------------------------------------------------------ - File operations for the device - ------------------------------------------------------------------*/ - -static const struct v4l2_ctrl_ops skel_ctrl_ops = { - .s_ctrl = skeleton_s_ctrl, -}; - -/* - * The set of all supported ioctls. Note that all the streaming ioctls - * use the vb2 helper functions that take care of all the locking and - * that also do ownership tracking (i.e. only the filehandle that requested - * the buffers can call the streaming ioctls, all other filehandles will - * receive -EBUSY if they attempt to call the same streaming ioctls). - * - * The last three ioctls also use standard helper functions: these implement - * standard behavior for drivers with controls. - */ -static const struct v4l2_ioctl_ops skel_ioctl_ops = { - .vidioc_querycap = skeleton_querycap, - .vidioc_try_fmt_vid_cap = skeleton_try_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = skeleton_s_fmt_vid_cap, - .vidioc_g_fmt_vid_cap = skeleton_g_fmt_vid_cap, - .vidioc_enum_fmt_vid_cap = skeleton_enum_fmt_vid_cap, - - .vidioc_g_std = skeleton_g_std, - .vidioc_s_std = skeleton_s_std, - .vidioc_querystd = skeleton_querystd, - - .vidioc_s_dv_timings = skeleton_s_dv_timings, - .vidioc_g_dv_timings = skeleton_g_dv_timings, - .vidioc_enum_dv_timings = skeleton_enum_dv_timings, - .vidioc_query_dv_timings = skeleton_query_dv_timings, - .vidioc_dv_timings_cap = skeleton_dv_timings_cap, - - .vidioc_enum_input = skeleton_enum_input, - .vidioc_g_input = skeleton_g_input, - .vidioc_s_input = skeleton_s_input, - - .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_expbuf = vb2_ioctl_expbuf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, - - .vidioc_log_status = v4l2_ctrl_log_status, - .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -/* - * The set of file operations. Note that all these ops are standard core - * helper functions. - */ -static const struct v4l2_file_operations skel_fops = { - .owner = THIS_MODULE, - .open = v4l2_fh_open, - .release = vb2_fop_release, - .unlocked_ioctl = video_ioctl2, - .read = vb2_fop_read, - .mmap = vb2_fop_mmap, - .poll = vb2_fop_poll, -}; - -/* - * The initial setup of this device instance. Note that the initial state of - * the driver should be complete. So the initial format, standard, timings - * and video input should all be initialized to some reasonable value. - */ -static int skeleton_probe(struct pci_dev *pdev, const struct pci_device_id *ent) -{ - /* The initial timings are chosen to be 720p60. */ - static const struct v4l2_dv_timings timings_def = - V4L2_DV_BT_CEA_1280X720P60; - struct skeleton *skel; - struct video_device *vdev; - struct v4l2_ctrl_handler *hdl; - struct vb2_queue *q; - int ret; - - /* Enable PCI */ - ret = pci_enable_device(pdev); - if (ret) - return ret; - ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); - if (ret) { - dev_err(&pdev->dev, "no suitable DMA available.\n"); - goto disable_pci; - } - - /* Allocate a new instance */ - skel = devm_kzalloc(&pdev->dev, sizeof(struct skeleton), GFP_KERNEL); - if (!skel) - return -ENOMEM; - - /* Allocate the interrupt */ - ret = devm_request_irq(&pdev->dev, pdev->irq, - skeleton_irq, 0, KBUILD_MODNAME, skel); - if (ret) { - dev_err(&pdev->dev, "request_irq failed\n"); - goto disable_pci; - } - skel->pdev = pdev; - - /* Fill in the initial format-related settings */ - skel->timings = timings_def; - skel->std = V4L2_STD_625_50; - skeleton_fill_pix_format(skel, &skel->format); - - /* Initialize the top-level structure */ - ret = v4l2_device_register(&pdev->dev, &skel->v4l2_dev); - if (ret) - goto disable_pci; - - mutex_init(&skel->lock); - - /* Add the controls */ - hdl = &skel->ctrl_handler; - v4l2_ctrl_handler_init(hdl, 4); - v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, - V4L2_CID_BRIGHTNESS, 0, 255, 1, 127); - v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, - V4L2_CID_CONTRAST, 0, 255, 1, 16); - v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, - V4L2_CID_SATURATION, 0, 255, 1, 127); - v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, - V4L2_CID_HUE, -128, 127, 1, 0); - if (hdl->error) { - ret = hdl->error; - goto free_hdl; - } - skel->v4l2_dev.ctrl_handler = hdl; - - /* Initialize the vb2 queue */ - q = &skel->queue; - q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; - q->drv_priv = skel; - q->buf_struct_size = sizeof(struct skel_buffer); - q->ops = &skel_qops; - q->mem_ops = &vb2_dma_contig_memops; - q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - /* - * Assume that this DMA engine needs to have at least two buffers - * available before it can be started. The start_streaming() op - * won't be called until at least this many buffers are queued up. - */ - q->min_buffers_needed = 2; - /* - * The serialization lock for the streaming ioctls. This is the same - * as the main serialization lock, but if some of the non-streaming - * ioctls could take a long time to execute, then you might want to - * have a different lock here to prevent VIDIOC_DQBUF from being - * blocked while waiting for another action to finish. This is - * generally not needed for PCI devices, but USB devices usually do - * want a separate lock here. - */ - q->lock = &skel->lock; - /* - * Since this driver can only do 32-bit DMA we must make sure that - * the vb2 core will allocate the buffers in 32-bit DMA memory. - */ - q->gfp_flags = GFP_DMA32; - ret = vb2_queue_init(q); - if (ret) - goto free_hdl; - - skel->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); - if (IS_ERR(skel->alloc_ctx)) { - dev_err(&pdev->dev, "Can't allocate buffer context"); - ret = PTR_ERR(skel->alloc_ctx); - goto free_hdl; - } - INIT_LIST_HEAD(&skel->buf_list); - spin_lock_init(&skel->qlock); - - /* Initialize the video_device structure */ - vdev = &skel->vdev; - strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); - /* - * There is nothing to clean up, so release is set to an empty release - * function. The release callback must be non-NULL. - */ - vdev->release = video_device_release_empty; - vdev->fops = &skel_fops, - vdev->ioctl_ops = &skel_ioctl_ops, - /* - * The main serialization lock. All ioctls are serialized by this - * lock. Exception: if q->lock is set, then the streaming ioctls - * are serialized by that separate lock. - */ - vdev->lock = &skel->lock; - vdev->queue = q; - vdev->v4l2_dev = &skel->v4l2_dev; - /* Supported SDTV standards, if any */ - vdev->tvnorms = SKEL_TVNORMS; - video_set_drvdata(vdev, skel); - - ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); - if (ret) - goto free_ctx; - - dev_info(&pdev->dev, "V4L2 PCI Skeleton Driver loaded\n"); - return 0; - -free_ctx: - vb2_dma_contig_cleanup_ctx(skel->alloc_ctx); -free_hdl: - v4l2_ctrl_handler_free(&skel->ctrl_handler); - v4l2_device_unregister(&skel->v4l2_dev); -disable_pci: - pci_disable_device(pdev); - return ret; -} - -static void skeleton_remove(struct pci_dev *pdev) -{ - struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); - struct skeleton *skel = container_of(v4l2_dev, struct skeleton, v4l2_dev); - - video_unregister_device(&skel->vdev); - v4l2_ctrl_handler_free(&skel->ctrl_handler); - vb2_dma_contig_cleanup_ctx(skel->alloc_ctx); - v4l2_device_unregister(&skel->v4l2_dev); - pci_disable_device(skel->pdev); -} - -static struct pci_driver skeleton_driver = { - .name = KBUILD_MODNAME, - .probe = skeleton_probe, - .remove = skeleton_remove, - .id_table = skeleton_pci_tbl, -}; - -module_pci_driver(skeleton_driver); diff --git a/samples/Makefile b/samples/Makefile index 594ef7d9fa2a..2e3b523d7097 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -2,4 +2,4 @@ obj-$(CONFIG_SAMPLES) += kobject/ kprobes/ trace_events/ livepatch/ \ hw_breakpoint/ kfifo/ kdb/ hidraw/ rpmsg/ seccomp/ \ - configfs/ connector/ + configfs/ connector/ v4l/ diff --git a/samples/v4l/Makefile b/samples/v4l/Makefile new file mode 100644 index 000000000000..65a351d75c95 --- /dev/null +++ b/samples/v4l/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_VIDEO_PCI_SKELETON) := v4l2-pci-skeleton.o diff --git a/samples/v4l/v4l2-pci-skeleton.c b/samples/v4l/v4l2-pci-skeleton.c new file mode 100644 index 000000000000..79af0c041056 --- /dev/null +++ b/samples/v4l/v4l2-pci-skeleton.c @@ -0,0 +1,923 @@ +/* + * This is a V4L2 PCI Skeleton Driver. It gives an initial skeleton source + * for use with other PCI drivers. + * + * This skeleton PCI driver assumes that the card has an S-Video connector as + * input 0 and an HDMI connector as input 1. + * + * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. + * + * This program is free software; you may redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("V4L2 PCI Skeleton Driver"); +MODULE_AUTHOR("Hans Verkuil"); +MODULE_LICENSE("GPL v2"); + +/** + * struct skeleton - All internal data for one instance of device + * @pdev: PCI device + * @v4l2_dev: top-level v4l2 device struct + * @vdev: video node structure + * @ctrl_handler: control handler structure + * @lock: ioctl serialization mutex + * @std: current SDTV standard + * @timings: current HDTV timings + * @format: current pix format + * @input: current video input (0 = SDTV, 1 = HDTV) + * @queue: vb2 video capture queue + * @alloc_ctx: vb2 contiguous DMA context + * @qlock: spinlock controlling access to buf_list and sequence + * @buf_list: list of buffers queued for DMA + * @sequence: frame sequence counter + */ +struct skeleton { + struct pci_dev *pdev; + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct v4l2_ctrl_handler ctrl_handler; + struct mutex lock; + v4l2_std_id std; + struct v4l2_dv_timings timings; + struct v4l2_pix_format format; + unsigned input; + + struct vb2_queue queue; + struct vb2_alloc_ctx *alloc_ctx; + + spinlock_t qlock; + struct list_head buf_list; + unsigned field; + unsigned sequence; +}; + +struct skel_buffer { + struct vb2_buffer vb; + struct list_head list; +}; + +static inline struct skel_buffer *to_skel_buffer(struct vb2_buffer *vb2) +{ + return container_of(vb2, struct skel_buffer, vb); +} + +static const struct pci_device_id skeleton_pci_tbl[] = { + /* { PCI_DEVICE(PCI_VENDOR_ID_, PCI_DEVICE_ID_) }, */ + { 0, } +}; +MODULE_DEVICE_TABLE(pci, skeleton_pci_tbl); + +/* + * HDTV: this structure has the capabilities of the HDTV receiver. + * It is used to constrain the huge list of possible formats based + * upon the hardware capabilities. + */ +static const struct v4l2_dv_timings_cap skel_timings_cap = { + .type = V4L2_DV_BT_656_1120, + /* keep this initialization for compatibility with GCC < 4.4.6 */ + .reserved = { 0 }, + V4L2_INIT_BT_TIMINGS( + 720, 1920, /* min/max width */ + 480, 1080, /* min/max height */ + 27000000, 74250000, /* min/max pixelclock*/ + V4L2_DV_BT_STD_CEA861, /* Supported standards */ + /* capabilities */ + V4L2_DV_BT_CAP_INTERLACED | V4L2_DV_BT_CAP_PROGRESSIVE + ) +}; + +/* + * Supported SDTV standards. This does the same job as skel_timings_cap, but + * for standard TV formats. + */ +#define SKEL_TVNORMS V4L2_STD_ALL + +/* + * Interrupt handler: typically interrupts happen after a new frame has been + * captured. It is the job of the handler to remove the new frame from the + * internal list and give it back to the vb2 framework, updating the sequence + * counter, field and timestamp at the same time. + */ +static irqreturn_t skeleton_irq(int irq, void *dev_id) +{ +#ifdef TODO + struct skeleton *skel = dev_id; + + /* handle interrupt */ + + /* Once a new frame has been captured, mark it as done like this: */ + if (captured_new_frame) { + ... + spin_lock(&skel->qlock); + list_del(&new_buf->list); + spin_unlock(&skel->qlock); + v4l2_get_timestamp(&new_buf->vb.v4l2_buf.timestamp); + new_buf->vb.v4l2_buf.sequence = skel->sequence++; + new_buf->vb.v4l2_buf.field = skel->field; + if (skel->format.field == V4L2_FIELD_ALTERNATE) { + if (skel->field == V4L2_FIELD_BOTTOM) + skel->field = V4L2_FIELD_TOP; + else if (skel->field == V4L2_FIELD_TOP) + skel->field = V4L2_FIELD_BOTTOM; + } + vb2_buffer_done(&new_buf->vb, VB2_BUF_STATE_DONE); + } +#endif + return IRQ_HANDLED; +} + +/* + * Setup the constraints of the queue: besides setting the number of planes + * per buffer and the size and allocation context of each plane, it also + * checks if sufficient buffers have been allocated. Usually 3 is a good + * minimum number: many DMA engines need a minimum of 2 buffers in the + * queue and you need to have another available for userspace processing. + */ +static int queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], void *alloc_ctxs[]) +{ + struct skeleton *skel = vb2_get_drv_priv(vq); + + skel->field = skel->format.field; + if (skel->field == V4L2_FIELD_ALTERNATE) { + /* + * You cannot use read() with FIELD_ALTERNATE since the field + * information (TOP/BOTTOM) cannot be passed back to the user. + */ + if (vb2_fileio_is_active(vq)) + return -EINVAL; + skel->field = V4L2_FIELD_TOP; + } + + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + alloc_ctxs[0] = skel->alloc_ctx; + + if (*nplanes) + return sizes[0] < skel->format.sizeimage ? -EINVAL : 0; + *nplanes = 1; + sizes[0] = skel->format.sizeimage; + return 0; +} + +/* + * Prepare the buffer for queueing to the DMA engine: check and set the + * payload size. + */ +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct skeleton *skel = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = skel->format.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(&skel->pdev->dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + return 0; +} + +/* + * Queue this buffer to the DMA engine. + */ +static void buffer_queue(struct vb2_buffer *vb) +{ + struct skeleton *skel = vb2_get_drv_priv(vb->vb2_queue); + struct skel_buffer *buf = to_skel_buffer(vb); + unsigned long flags; + + spin_lock_irqsave(&skel->qlock, flags); + list_add_tail(&buf->list, &skel->buf_list); + + /* TODO: Update any DMA pointers if necessary */ + + spin_unlock_irqrestore(&skel->qlock, flags); +} + +static void return_all_buffers(struct skeleton *skel, + enum vb2_buffer_state state) +{ + struct skel_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&skel->qlock, flags); + list_for_each_entry_safe(buf, node, &skel->buf_list, list) { + vb2_buffer_done(&buf->vb, state); + list_del(&buf->list); + } + spin_unlock_irqrestore(&skel->qlock, flags); +} + +/* + * Start streaming. First check if the minimum number of buffers have been + * queued. If not, then return -ENOBUFS and the vb2 framework will call + * this function again the next time a buffer has been queued until enough + * buffers are available to actually start the DMA engine. + */ +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct skeleton *skel = vb2_get_drv_priv(vq); + int ret = 0; + + skel->sequence = 0; + + /* TODO: start DMA */ + + if (ret) { + /* + * In case of an error, return all active buffers to the + * QUEUED state + */ + return_all_buffers(skel, VB2_BUF_STATE_QUEUED); + } + return ret; +} + +/* + * Stop the DMA engine. Any remaining buffers in the DMA queue are dequeued + * and passed on to the vb2 framework marked as STATE_ERROR. + */ +static void stop_streaming(struct vb2_queue *vq) +{ + struct skeleton *skel = vb2_get_drv_priv(vq); + + /* TODO: stop DMA */ + + /* Release all active buffers */ + return_all_buffers(skel, VB2_BUF_STATE_ERROR); +} + +/* + * The vb2 queue ops. Note that since q->lock is set we can use the standard + * vb2_ops_wait_prepare/finish helper functions. If q->lock would be NULL, + * then this driver would have to provide these ops. + */ +static struct vb2_ops skel_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* + * Required ioctl querycap. Note that the version field is prefilled with + * the version of the kernel. + */ +static int skeleton_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct skeleton *skel = video_drvdata(file); + + strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strlcpy(cap->card, "V4L2 PCI Skeleton", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s", + pci_name(skel->pdev)); + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + return 0; +} + +/* + * Helper function to check and correct struct v4l2_pix_format. It's used + * not only in VIDIOC_TRY/S_FMT, but also elsewhere if changes to the SDTV + * standard, HDTV timings or the video input would require updating the + * current format. + */ +static void skeleton_fill_pix_format(struct skeleton *skel, + struct v4l2_pix_format *pix) +{ + pix->pixelformat = V4L2_PIX_FMT_YUYV; + if (skel->input == 0) { + /* S-Video input */ + pix->width = 720; + pix->height = (skel->std & V4L2_STD_525_60) ? 480 : 576; + pix->field = V4L2_FIELD_INTERLACED; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + } else { + /* HDMI input */ + pix->width = skel->timings.bt.width; + pix->height = skel->timings.bt.height; + if (skel->timings.bt.interlaced) { + pix->field = V4L2_FIELD_ALTERNATE; + pix->height /= 2; + } else { + pix->field = V4L2_FIELD_NONE; + } + pix->colorspace = V4L2_COLORSPACE_REC709; + } + + /* + * The YUYV format is four bytes for every two pixels, so bytesperline + * is width * 2. + */ + pix->bytesperline = pix->width * 2; + pix->sizeimage = pix->bytesperline * pix->height; + pix->priv = 0; +} + +static int skeleton_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct skeleton *skel = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + + /* + * Due to historical reasons providing try_fmt with an unsupported + * pixelformat will return -EINVAL for video receivers. Webcam drivers, + * however, will silently correct the pixelformat. Some video capture + * applications rely on this behavior... + */ + if (pix->pixelformat != V4L2_PIX_FMT_YUYV) + return -EINVAL; + skeleton_fill_pix_format(skel, pix); + return 0; +} + +static int skeleton_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct skeleton *skel = video_drvdata(file); + int ret; + + ret = skeleton_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + /* + * It is not allowed to change the format while buffers for use with + * streaming have already been allocated. + */ + if (vb2_is_busy(&skel->queue)) + return -EBUSY; + + /* TODO: change format */ + skel->format = f->fmt.pix; + return 0; +} + +static int skeleton_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct skeleton *skel = video_drvdata(file); + + f->fmt.pix = skel->format; + return 0; +} + +static int skeleton_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index != 0) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_YUYV; + return 0; +} + +static int skeleton_s_std(struct file *file, void *priv, v4l2_std_id std) +{ + struct skeleton *skel = video_drvdata(file); + + /* S_STD is not supported on the HDMI input */ + if (skel->input) + return -ENODATA; + + /* + * No change, so just return. Some applications call S_STD again after + * the buffers for streaming have been set up, so we have to allow for + * this behavior. + */ + if (std == skel->std) + return 0; + + /* + * Changing the standard implies a format change, which is not allowed + * while buffers for use with streaming have already been allocated. + */ + if (vb2_is_busy(&skel->queue)) + return -EBUSY; + + /* TODO: handle changing std */ + + skel->std = std; + + /* Update the internal format */ + skeleton_fill_pix_format(skel, &skel->format); + return 0; +} + +static int skeleton_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct skeleton *skel = video_drvdata(file); + + /* G_STD is not supported on the HDMI input */ + if (skel->input) + return -ENODATA; + + *std = skel->std; + return 0; +} + +/* + * Query the current standard as seen by the hardware. This function shall + * never actually change the standard, it just detects and reports. + * The framework will initially set *std to tvnorms (i.e. the set of + * supported standards by this input), and this function should just AND + * this value. If there is no signal, then *std should be set to 0. + */ +static int skeleton_querystd(struct file *file, void *priv, v4l2_std_id *std) +{ + struct skeleton *skel = video_drvdata(file); + + /* QUERY_STD is not supported on the HDMI input */ + if (skel->input) + return -ENODATA; + +#ifdef TODO + /* + * Query currently seen standard. Initial value of *std is + * V4L2_STD_ALL. This function should look something like this: + */ + get_signal_info(); + if (no_signal) { + *std = 0; + return 0; + } + /* Use signal information to reduce the number of possible standards */ + if (signal_has_525_lines) + *std &= V4L2_STD_525_60; + else + *std &= V4L2_STD_625_50; +#endif + return 0; +} + +static int skeleton_s_dv_timings(struct file *file, void *_fh, + struct v4l2_dv_timings *timings) +{ + struct skeleton *skel = video_drvdata(file); + + /* S_DV_TIMINGS is not supported on the S-Video input */ + if (skel->input == 0) + return -ENODATA; + + /* Quick sanity check */ + if (!v4l2_valid_dv_timings(timings, &skel_timings_cap, NULL, NULL)) + return -EINVAL; + + /* Check if the timings are part of the CEA-861 timings. */ + if (!v4l2_find_dv_timings_cap(timings, &skel_timings_cap, + 0, NULL, NULL)) + return -EINVAL; + + /* Return 0 if the new timings are the same as the current timings. */ + if (v4l2_match_dv_timings(timings, &skel->timings, 0, false)) + return 0; + + /* + * Changing the timings implies a format change, which is not allowed + * while buffers for use with streaming have already been allocated. + */ + if (vb2_is_busy(&skel->queue)) + return -EBUSY; + + /* TODO: Configure new timings */ + + /* Save timings */ + skel->timings = *timings; + + /* Update the internal format */ + skeleton_fill_pix_format(skel, &skel->format); + return 0; +} + +static int skeleton_g_dv_timings(struct file *file, void *_fh, + struct v4l2_dv_timings *timings) +{ + struct skeleton *skel = video_drvdata(file); + + /* G_DV_TIMINGS is not supported on the S-Video input */ + if (skel->input == 0) + return -ENODATA; + + *timings = skel->timings; + return 0; +} + +static int skeleton_enum_dv_timings(struct file *file, void *_fh, + struct v4l2_enum_dv_timings *timings) +{ + struct skeleton *skel = video_drvdata(file); + + /* ENUM_DV_TIMINGS is not supported on the S-Video input */ + if (skel->input == 0) + return -ENODATA; + + return v4l2_enum_dv_timings_cap(timings, &skel_timings_cap, + NULL, NULL); +} + +/* + * Query the current timings as seen by the hardware. This function shall + * never actually change the timings, it just detects and reports. + * If no signal is detected, then return -ENOLINK. If the hardware cannot + * lock to the signal, then return -ENOLCK. If the signal is out of range + * of the capabilities of the system (e.g., it is possible that the receiver + * can lock but that the DMA engine it is connected to cannot handle + * pixelclocks above a certain frequency), then -ERANGE is returned. + */ +static int skeleton_query_dv_timings(struct file *file, void *_fh, + struct v4l2_dv_timings *timings) +{ + struct skeleton *skel = video_drvdata(file); + + /* QUERY_DV_TIMINGS is not supported on the S-Video input */ + if (skel->input == 0) + return -ENODATA; + +#ifdef TODO + /* + * Query currently seen timings. This function should look + * something like this: + */ + detect_timings(); + if (no_signal) + return -ENOLINK; + if (cannot_lock_to_signal) + return -ENOLCK; + if (signal_out_of_range_of_capabilities) + return -ERANGE; + + /* Useful for debugging */ + v4l2_print_dv_timings(skel->v4l2_dev.name, "query_dv_timings:", + timings, true); +#endif + return 0; +} + +static int skeleton_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) +{ + struct skeleton *skel = video_drvdata(file); + + /* DV_TIMINGS_CAP is not supported on the S-Video input */ + if (skel->input == 0) + return -ENODATA; + *cap = skel_timings_cap; + return 0; +} + +static int skeleton_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index > 1) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + if (i->index == 0) { + i->std = SKEL_TVNORMS; + strlcpy(i->name, "S-Video", sizeof(i->name)); + i->capabilities = V4L2_IN_CAP_STD; + } else { + i->std = 0; + strlcpy(i->name, "HDMI", sizeof(i->name)); + i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + } + return 0; +} + +static int skeleton_s_input(struct file *file, void *priv, unsigned int i) +{ + struct skeleton *skel = video_drvdata(file); + + if (i > 1) + return -EINVAL; + + /* + * Changing the input implies a format change, which is not allowed + * while buffers for use with streaming have already been allocated. + */ + if (vb2_is_busy(&skel->queue)) + return -EBUSY; + + skel->input = i; + /* + * Update tvnorms. The tvnorms value is used by the core to implement + * VIDIOC_ENUMSTD so it has to be correct. If tvnorms == 0, then + * ENUMSTD will return -ENODATA. + */ + skel->vdev.tvnorms = i ? 0 : SKEL_TVNORMS; + + /* Update the internal format */ + skeleton_fill_pix_format(skel, &skel->format); + return 0; +} + +static int skeleton_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct skeleton *skel = video_drvdata(file); + + *i = skel->input; + return 0; +} + +/* The control handler. */ +static int skeleton_s_ctrl(struct v4l2_ctrl *ctrl) +{ + /*struct skeleton *skel = + container_of(ctrl->handler, struct skeleton, ctrl_handler);*/ + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + /* TODO: set brightness to ctrl->val */ + break; + case V4L2_CID_CONTRAST: + /* TODO: set contrast to ctrl->val */ + break; + case V4L2_CID_SATURATION: + /* TODO: set saturation to ctrl->val */ + break; + case V4L2_CID_HUE: + /* TODO: set hue to ctrl->val */ + break; + default: + return -EINVAL; + } + return 0; +} + +/* ------------------------------------------------------------------ + File operations for the device + ------------------------------------------------------------------*/ + +static const struct v4l2_ctrl_ops skel_ctrl_ops = { + .s_ctrl = skeleton_s_ctrl, +}; + +/* + * The set of all supported ioctls. Note that all the streaming ioctls + * use the vb2 helper functions that take care of all the locking and + * that also do ownership tracking (i.e. only the filehandle that requested + * the buffers can call the streaming ioctls, all other filehandles will + * receive -EBUSY if they attempt to call the same streaming ioctls). + * + * The last three ioctls also use standard helper functions: these implement + * standard behavior for drivers with controls. + */ +static const struct v4l2_ioctl_ops skel_ioctl_ops = { + .vidioc_querycap = skeleton_querycap, + .vidioc_try_fmt_vid_cap = skeleton_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = skeleton_s_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = skeleton_g_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = skeleton_enum_fmt_vid_cap, + + .vidioc_g_std = skeleton_g_std, + .vidioc_s_std = skeleton_s_std, + .vidioc_querystd = skeleton_querystd, + + .vidioc_s_dv_timings = skeleton_s_dv_timings, + .vidioc_g_dv_timings = skeleton_g_dv_timings, + .vidioc_enum_dv_timings = skeleton_enum_dv_timings, + .vidioc_query_dv_timings = skeleton_query_dv_timings, + .vidioc_dv_timings_cap = skeleton_dv_timings_cap, + + .vidioc_enum_input = skeleton_enum_input, + .vidioc_g_input = skeleton_g_input, + .vidioc_s_input = skeleton_s_input, + + .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_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * The set of file operations. Note that all these ops are standard core + * helper functions. + */ +static const struct v4l2_file_operations skel_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +/* + * The initial setup of this device instance. Note that the initial state of + * the driver should be complete. So the initial format, standard, timings + * and video input should all be initialized to some reasonable value. + */ +static int skeleton_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + /* The initial timings are chosen to be 720p60. */ + static const struct v4l2_dv_timings timings_def = + V4L2_DV_BT_CEA_1280X720P60; + struct skeleton *skel; + struct video_device *vdev; + struct v4l2_ctrl_handler *hdl; + struct vb2_queue *q; + int ret; + + /* Enable PCI */ + ret = pci_enable_device(pdev); + if (ret) + return ret; + ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "no suitable DMA available.\n"); + goto disable_pci; + } + + /* Allocate a new instance */ + skel = devm_kzalloc(&pdev->dev, sizeof(struct skeleton), GFP_KERNEL); + if (!skel) + return -ENOMEM; + + /* Allocate the interrupt */ + ret = devm_request_irq(&pdev->dev, pdev->irq, + skeleton_irq, 0, KBUILD_MODNAME, skel); + if (ret) { + dev_err(&pdev->dev, "request_irq failed\n"); + goto disable_pci; + } + skel->pdev = pdev; + + /* Fill in the initial format-related settings */ + skel->timings = timings_def; + skel->std = V4L2_STD_625_50; + skeleton_fill_pix_format(skel, &skel->format); + + /* Initialize the top-level structure */ + ret = v4l2_device_register(&pdev->dev, &skel->v4l2_dev); + if (ret) + goto disable_pci; + + mutex_init(&skel->lock); + + /* Add the controls */ + hdl = &skel->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 127); + v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 16); + v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 127); + v4l2_ctrl_new_std(hdl, &skel_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + if (hdl->error) { + ret = hdl->error; + goto free_hdl; + } + skel->v4l2_dev.ctrl_handler = hdl; + + /* Initialize the vb2 queue */ + q = &skel->queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + q->drv_priv = skel; + q->buf_struct_size = sizeof(struct skel_buffer); + q->ops = &skel_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + /* + * Assume that this DMA engine needs to have at least two buffers + * available before it can be started. The start_streaming() op + * won't be called until at least this many buffers are queued up. + */ + q->min_buffers_needed = 2; + /* + * The serialization lock for the streaming ioctls. This is the same + * as the main serialization lock, but if some of the non-streaming + * ioctls could take a long time to execute, then you might want to + * have a different lock here to prevent VIDIOC_DQBUF from being + * blocked while waiting for another action to finish. This is + * generally not needed for PCI devices, but USB devices usually do + * want a separate lock here. + */ + q->lock = &skel->lock; + /* + * Since this driver can only do 32-bit DMA we must make sure that + * the vb2 core will allocate the buffers in 32-bit DMA memory. + */ + q->gfp_flags = GFP_DMA32; + ret = vb2_queue_init(q); + if (ret) + goto free_hdl; + + skel->alloc_ctx = vb2_dma_contig_init_ctx(&pdev->dev); + if (IS_ERR(skel->alloc_ctx)) { + dev_err(&pdev->dev, "Can't allocate buffer context"); + ret = PTR_ERR(skel->alloc_ctx); + goto free_hdl; + } + INIT_LIST_HEAD(&skel->buf_list); + spin_lock_init(&skel->qlock); + + /* Initialize the video_device structure */ + vdev = &skel->vdev; + strlcpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); + /* + * There is nothing to clean up, so release is set to an empty release + * function. The release callback must be non-NULL. + */ + vdev->release = video_device_release_empty; + vdev->fops = &skel_fops, + vdev->ioctl_ops = &skel_ioctl_ops, + /* + * The main serialization lock. All ioctls are serialized by this + * lock. Exception: if q->lock is set, then the streaming ioctls + * are serialized by that separate lock. + */ + vdev->lock = &skel->lock; + vdev->queue = q; + vdev->v4l2_dev = &skel->v4l2_dev; + /* Supported SDTV standards, if any */ + vdev->tvnorms = SKEL_TVNORMS; + video_set_drvdata(vdev, skel); + + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); + if (ret) + goto free_ctx; + + dev_info(&pdev->dev, "V4L2 PCI Skeleton Driver loaded\n"); + return 0; + +free_ctx: + vb2_dma_contig_cleanup_ctx(skel->alloc_ctx); +free_hdl: + v4l2_ctrl_handler_free(&skel->ctrl_handler); + v4l2_device_unregister(&skel->v4l2_dev); +disable_pci: + pci_disable_device(pdev); + return ret; +} + +static void skeleton_remove(struct pci_dev *pdev) +{ + struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev); + struct skeleton *skel = container_of(v4l2_dev, struct skeleton, v4l2_dev); + + video_unregister_device(&skel->vdev); + v4l2_ctrl_handler_free(&skel->ctrl_handler); + vb2_dma_contig_cleanup_ctx(skel->alloc_ctx); + v4l2_device_unregister(&skel->v4l2_dev); + pci_disable_device(skel->pdev); +} + +static struct pci_driver skeleton_driver = { + .name = KBUILD_MODNAME, + .probe = skeleton_probe, + .remove = skeleton_remove, + .id_table = skeleton_pci_tbl, +}; + +module_pci_driver(skeleton_driver); -- cgit v1.2.3-59-g8ed1b