Commit 588b9e85 authored by Michael Grzeschik's avatar Michael Grzeschik Committed by Greg Kroah-Hartman

usb: gadget: uvc: add v4l2 enumeration api calls

This patch adds support to the v4l2 VIDIOCs for enum_format,
enum_framesizes and enum_frameintervals. This way, the userspace
application can use these VIDIOCS to query the via configfs exported
frame capabilities. With thes callbacks the userspace doesn't have to
bring its own configfs parser.
Signed-off-by: default avatarMichael Grzeschik <m.grzeschik@pengutronix.de>
Link: https://lore.kernel.org/r/20220909221335.15033-4-m.grzeschik@pengutronix.deSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 6b028df7
...@@ -888,6 +888,7 @@ static void uvc_free(struct usb_function *f) ...@@ -888,6 +888,7 @@ static void uvc_free(struct usb_function *f)
struct uvc_device *uvc = to_uvc(f); struct uvc_device *uvc = to_uvc(f);
struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts, struct f_uvc_opts *opts = container_of(f->fi, struct f_uvc_opts,
func_inst); func_inst);
config_item_put(&uvc->header->item);
--opts->refcnt; --opts->refcnt;
kfree(uvc); kfree(uvc);
} }
...@@ -945,6 +946,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) ...@@ -945,6 +946,7 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
struct uvc_device *uvc; struct uvc_device *uvc;
struct f_uvc_opts *opts; struct f_uvc_opts *opts;
struct uvc_descriptor_header **strm_cls; struct uvc_descriptor_header **strm_cls;
struct config_item *streaming, *header, *h;
uvc = kzalloc(sizeof(*uvc), GFP_KERNEL); uvc = kzalloc(sizeof(*uvc), GFP_KERNEL);
if (uvc == NULL) if (uvc == NULL)
...@@ -977,6 +979,29 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) ...@@ -977,6 +979,29 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
uvc->desc.fs_streaming = opts->fs_streaming; uvc->desc.fs_streaming = opts->fs_streaming;
uvc->desc.hs_streaming = opts->hs_streaming; uvc->desc.hs_streaming = opts->hs_streaming;
uvc->desc.ss_streaming = opts->ss_streaming; uvc->desc.ss_streaming = opts->ss_streaming;
streaming = config_group_find_item(&opts->func_inst.group, "streaming");
if (!streaming)
goto err_config;
header = config_group_find_item(to_config_group(streaming), "header");
config_item_put(streaming);
if (!header)
goto err_config;
h = config_group_find_item(to_config_group(header), "h");
config_item_put(header);
if (!h)
goto err_config;
uvc->header = to_uvcg_streaming_header(h);
config_item_put(h);
if (!uvc->header->linked) {
mutex_unlock(&opts->lock);
kfree(uvc);
return ERR_PTR(-EBUSY);
}
++opts->refcnt; ++opts->refcnt;
mutex_unlock(&opts->lock); mutex_unlock(&opts->lock);
...@@ -992,6 +1017,11 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi) ...@@ -992,6 +1017,11 @@ static struct usb_function *uvc_alloc(struct usb_function_instance *fi)
uvc->func.bind_deactivated = true; uvc->func.bind_deactivated = true;
return &uvc->func; return &uvc->func;
err_config:
mutex_unlock(&opts->lock);
kfree(uvc);
return ERR_PTR(-ENOENT);
} }
DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc); DECLARE_USB_FUNCTION_INIT(uvc, uvc_alloc_inst, uvc_alloc);
......
...@@ -134,6 +134,8 @@ struct uvc_device { ...@@ -134,6 +134,8 @@ struct uvc_device {
bool func_connected; bool func_connected;
wait_queue_head_t func_connected_queue; wait_queue_head_t func_connected_queue;
struct uvcg_streaming_header *header;
/* Descriptors */ /* Descriptors */
struct { struct {
const struct uvc_descriptor_header * const *fs_control; const struct uvc_descriptor_header * const *fs_control;
......
...@@ -18,12 +18,92 @@ ...@@ -18,12 +18,92 @@
#include <media/v4l2-dev.h> #include <media/v4l2-dev.h>
#include <media/v4l2-event.h> #include <media/v4l2-event.h>
#include <media/v4l2-ioctl.h> #include <media/v4l2-ioctl.h>
#include <media/v4l2-uvc.h>
#include "f_uvc.h" #include "f_uvc.h"
#include "uvc.h" #include "uvc.h"
#include "uvc_queue.h" #include "uvc_queue.h"
#include "uvc_video.h" #include "uvc_video.h"
#include "uvc_v4l2.h" #include "uvc_v4l2.h"
#include "uvc_configfs.h"
static struct uvc_format_desc *to_uvc_format(struct uvcg_format *uformat)
{
char guid[16] = UVC_GUID_FORMAT_MJPEG;
struct uvc_format_desc *format;
struct uvcg_uncompressed *unc;
if (uformat->type == UVCG_UNCOMPRESSED) {
unc = to_uvcg_uncompressed(&uformat->group.cg_item);
if (!unc)
return ERR_PTR(-EINVAL);
memcpy(guid, unc->desc.guidFormat, sizeof(guid));
}
format = uvc_format_by_guid(guid);
if (!format)
return ERR_PTR(-EINVAL);
return format;
}
static struct uvcg_format *find_format_by_index(struct uvc_device *uvc, int index)
{
struct uvcg_format_ptr *format;
struct uvcg_format *uformat = NULL;
int i = 1;
list_for_each_entry(format, &uvc->header->formats, entry) {
if (index == i) {
uformat = format->fmt;
break;
}
i++;
}
return uformat;
}
static struct uvcg_frame *find_frame_by_index(struct uvc_device *uvc,
struct uvcg_format *uformat,
int index)
{
struct uvcg_format_ptr *format;
struct uvcg_frame_ptr *frame;
struct uvcg_frame *uframe = NULL;
list_for_each_entry(format, &uvc->header->formats, entry) {
if (format->fmt->type != uformat->type)
continue;
list_for_each_entry(frame, &format->fmt->frames, entry) {
if (index == frame->frm->frame.b_frame_index) {
uframe = frame->frm;
break;
}
}
}
return uframe;
}
static struct uvcg_format *find_format_by_pix(struct uvc_device *uvc,
u32 pixelformat)
{
struct uvcg_format_ptr *format;
struct uvcg_format *uformat = NULL;
list_for_each_entry(format, &uvc->header->formats, entry) {
struct uvc_format_desc *fmtdesc = to_uvc_format(format->fmt);
if (fmtdesc->fcc == pixelformat) {
uformat = format->fmt;
break;
}
}
return uformat;
}
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
* Requests handling * Requests handling
...@@ -134,6 +214,99 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt) ...@@ -134,6 +214,99 @@ uvc_v4l2_set_format(struct file *file, void *fh, struct v4l2_format *fmt)
return 0; return 0;
} }
static int
uvc_v4l2_enum_frameintervals(struct file *file, void *fh,
struct v4l2_frmivalenum *fival)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvcg_format *uformat = NULL;
struct uvcg_frame *uframe = NULL;
struct uvcg_frame_ptr *frame;
uformat = find_format_by_pix(uvc, fival->pixel_format);
if (!uformat)
return -EINVAL;
list_for_each_entry(frame, &uformat->frames, entry) {
if (frame->frm->frame.w_width == fival->width &&
frame->frm->frame.w_height == fival->height) {
uframe = frame->frm;
break;
}
}
if (!uframe)
return -EINVAL;
if (fival->index >= uframe->frame.b_frame_interval_type)
return -EINVAL;
fival->discrete.numerator =
uframe->dw_frame_interval[fival->index];
/* TODO: handle V4L2_FRMIVAL_TYPE_STEPWISE */
fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
fival->discrete.denominator = 10000000;
v4l2_simplify_fraction(&fival->discrete.numerator,
&fival->discrete.denominator, 8, 333);
return 0;
}
static int
uvc_v4l2_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvcg_format *uformat = NULL;
struct uvcg_frame *uframe = NULL;
uformat = find_format_by_pix(uvc, fsize->pixel_format);
if (!uformat)
return -EINVAL;
if (fsize->index >= uformat->num_frames)
return -EINVAL;
uframe = find_frame_by_index(uvc, uformat, fsize->index + 1);
if (!uframe)
return -EINVAL;
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
fsize->discrete.width = uframe->frame.w_width;
fsize->discrete.height = uframe->frame.w_height;
return 0;
}
static int
uvc_v4l2_enum_format(struct file *file, void *fh, struct v4l2_fmtdesc *f)
{
struct video_device *vdev = video_devdata(file);
struct uvc_device *uvc = video_get_drvdata(vdev);
struct uvc_format_desc *fmtdesc;
struct uvcg_format *uformat;
if (f->index >= uvc->header->num_fmt)
return -EINVAL;
uformat = find_format_by_index(uvc, f->index + 1);
if (!uformat)
return -EINVAL;
if (uformat->type != UVCG_UNCOMPRESSED)
f->flags |= V4L2_FMT_FLAG_COMPRESSED;
fmtdesc = to_uvc_format(uformat);
f->pixelformat = fmtdesc->fcc;
strscpy(f->description, fmtdesc->name, sizeof(f->description));
f->description[strlen(fmtdesc->name) - 1] = 0;
return 0;
}
static int static int
uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b) uvc_v4l2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *b)
{ {
...@@ -300,6 +473,9 @@ const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = { ...@@ -300,6 +473,9 @@ const struct v4l2_ioctl_ops uvc_v4l2_ioctl_ops = {
.vidioc_querycap = uvc_v4l2_querycap, .vidioc_querycap = uvc_v4l2_querycap,
.vidioc_g_fmt_vid_out = uvc_v4l2_get_format, .vidioc_g_fmt_vid_out = uvc_v4l2_get_format,
.vidioc_s_fmt_vid_out = uvc_v4l2_set_format, .vidioc_s_fmt_vid_out = uvc_v4l2_set_format,
.vidioc_enum_frameintervals = uvc_v4l2_enum_frameintervals,
.vidioc_enum_framesizes = uvc_v4l2_enum_framesizes,
.vidioc_enum_fmt_vid_out = uvc_v4l2_enum_format,
.vidioc_reqbufs = uvc_v4l2_reqbufs, .vidioc_reqbufs = uvc_v4l2_reqbufs,
.vidioc_querybuf = uvc_v4l2_querybuf, .vidioc_querybuf = uvc_v4l2_querybuf,
.vidioc_qbuf = uvc_v4l2_qbuf, .vidioc_qbuf = uvc_v4l2_qbuf,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment