forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
13 KiB
432 lines
13 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
/* |
|
* webcam.c -- USB webcam gadget driver |
|
* |
|
* Copyright (C) 2009-2010 |
|
* Laurent Pinchart ([email protected]) |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/device.h> |
|
#include <linux/module.h> |
|
#include <linux/usb/video.h> |
|
|
|
#include "u_uvc.h" |
|
|
|
USB_GADGET_COMPOSITE_OPTIONS(); |
|
|
|
/*-------------------------------------------------------------------------*/ |
|
|
|
/* module parameters specific to the Video streaming endpoint */ |
|
static unsigned int streaming_interval = 1; |
|
module_param(streaming_interval, uint, S_IRUGO|S_IWUSR); |
|
MODULE_PARM_DESC(streaming_interval, "1 - 16"); |
|
|
|
static unsigned int streaming_maxpacket = 1024; |
|
module_param(streaming_maxpacket, uint, S_IRUGO|S_IWUSR); |
|
MODULE_PARM_DESC(streaming_maxpacket, "1 - 1023 (FS), 1 - 3072 (hs/ss)"); |
|
|
|
static unsigned int streaming_maxburst; |
|
module_param(streaming_maxburst, uint, S_IRUGO|S_IWUSR); |
|
MODULE_PARM_DESC(streaming_maxburst, "0 - 15 (ss only)"); |
|
|
|
/* -------------------------------------------------------------------------- |
|
* Device descriptor |
|
*/ |
|
|
|
#define WEBCAM_VENDOR_ID 0x1d6b /* Linux Foundation */ |
|
#define WEBCAM_PRODUCT_ID 0x0102 /* Webcam A/V gadget */ |
|
#define WEBCAM_DEVICE_BCD 0x0010 /* 0.10 */ |
|
|
|
static char webcam_vendor_label[] = "Linux Foundation"; |
|
static char webcam_product_label[] = "Webcam gadget"; |
|
static char webcam_config_label[] = "Video"; |
|
|
|
/* string IDs are assigned dynamically */ |
|
|
|
#define STRING_DESCRIPTION_IDX USB_GADGET_FIRST_AVAIL_IDX |
|
|
|
static struct usb_string webcam_strings[] = { |
|
[USB_GADGET_MANUFACTURER_IDX].s = webcam_vendor_label, |
|
[USB_GADGET_PRODUCT_IDX].s = webcam_product_label, |
|
[USB_GADGET_SERIAL_IDX].s = "", |
|
[STRING_DESCRIPTION_IDX].s = webcam_config_label, |
|
{ } |
|
}; |
|
|
|
static struct usb_gadget_strings webcam_stringtab = { |
|
.language = 0x0409, /* en-us */ |
|
.strings = webcam_strings, |
|
}; |
|
|
|
static struct usb_gadget_strings *webcam_device_strings[] = { |
|
&webcam_stringtab, |
|
NULL, |
|
}; |
|
|
|
static struct usb_function_instance *fi_uvc; |
|
static struct usb_function *f_uvc; |
|
|
|
static struct usb_device_descriptor webcam_device_descriptor = { |
|
.bLength = USB_DT_DEVICE_SIZE, |
|
.bDescriptorType = USB_DT_DEVICE, |
|
/* .bcdUSB = DYNAMIC */ |
|
.bDeviceClass = USB_CLASS_MISC, |
|
.bDeviceSubClass = 0x02, |
|
.bDeviceProtocol = 0x01, |
|
.bMaxPacketSize0 = 0, /* dynamic */ |
|
.idVendor = cpu_to_le16(WEBCAM_VENDOR_ID), |
|
.idProduct = cpu_to_le16(WEBCAM_PRODUCT_ID), |
|
.bcdDevice = cpu_to_le16(WEBCAM_DEVICE_BCD), |
|
.iManufacturer = 0, /* dynamic */ |
|
.iProduct = 0, /* dynamic */ |
|
.iSerialNumber = 0, /* dynamic */ |
|
.bNumConfigurations = 0, /* dynamic */ |
|
}; |
|
|
|
DECLARE_UVC_HEADER_DESCRIPTOR(1); |
|
|
|
static const struct UVC_HEADER_DESCRIPTOR(1) uvc_control_header = { |
|
.bLength = UVC_DT_HEADER_SIZE(1), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VC_HEADER, |
|
.bcdUVC = cpu_to_le16(0x0100), |
|
.wTotalLength = 0, /* dynamic */ |
|
.dwClockFrequency = cpu_to_le32(48000000), |
|
.bInCollection = 0, /* dynamic */ |
|
.baInterfaceNr[0] = 0, /* dynamic */ |
|
}; |
|
|
|
static const struct uvc_camera_terminal_descriptor uvc_camera_terminal = { |
|
.bLength = UVC_DT_CAMERA_TERMINAL_SIZE(3), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VC_INPUT_TERMINAL, |
|
.bTerminalID = 1, |
|
.wTerminalType = cpu_to_le16(0x0201), |
|
.bAssocTerminal = 0, |
|
.iTerminal = 0, |
|
.wObjectiveFocalLengthMin = cpu_to_le16(0), |
|
.wObjectiveFocalLengthMax = cpu_to_le16(0), |
|
.wOcularFocalLength = cpu_to_le16(0), |
|
.bControlSize = 3, |
|
.bmControls[0] = 2, |
|
.bmControls[1] = 0, |
|
.bmControls[2] = 0, |
|
}; |
|
|
|
static const struct uvc_processing_unit_descriptor uvc_processing = { |
|
.bLength = UVC_DT_PROCESSING_UNIT_SIZE(2), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VC_PROCESSING_UNIT, |
|
.bUnitID = 2, |
|
.bSourceID = 1, |
|
.wMaxMultiplier = cpu_to_le16(16*1024), |
|
.bControlSize = 2, |
|
.bmControls[0] = 1, |
|
.bmControls[1] = 0, |
|
.iProcessing = 0, |
|
.bmVideoStandards = 0, |
|
}; |
|
|
|
static const struct uvc_output_terminal_descriptor uvc_output_terminal = { |
|
.bLength = UVC_DT_OUTPUT_TERMINAL_SIZE, |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VC_OUTPUT_TERMINAL, |
|
.bTerminalID = 3, |
|
.wTerminalType = cpu_to_le16(0x0101), |
|
.bAssocTerminal = 0, |
|
.bSourceID = 2, |
|
.iTerminal = 0, |
|
}; |
|
|
|
DECLARE_UVC_INPUT_HEADER_DESCRIPTOR(1, 2); |
|
|
|
static const struct UVC_INPUT_HEADER_DESCRIPTOR(1, 2) uvc_input_header = { |
|
.bLength = UVC_DT_INPUT_HEADER_SIZE(1, 2), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_INPUT_HEADER, |
|
.bNumFormats = 2, |
|
.wTotalLength = 0, /* dynamic */ |
|
.bEndpointAddress = 0, /* dynamic */ |
|
.bmInfo = 0, |
|
.bTerminalLink = 3, |
|
.bStillCaptureMethod = 0, |
|
.bTriggerSupport = 0, |
|
.bTriggerUsage = 0, |
|
.bControlSize = 1, |
|
.bmaControls[0][0] = 0, |
|
.bmaControls[1][0] = 4, |
|
}; |
|
|
|
static const struct uvc_format_uncompressed uvc_format_yuv = { |
|
.bLength = UVC_DT_FORMAT_UNCOMPRESSED_SIZE, |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_FORMAT_UNCOMPRESSED, |
|
.bFormatIndex = 1, |
|
.bNumFrameDescriptors = 2, |
|
.guidFormat = |
|
{ 'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00, |
|
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}, |
|
.bBitsPerPixel = 16, |
|
.bDefaultFrameIndex = 1, |
|
.bAspectRatioX = 0, |
|
.bAspectRatioY = 0, |
|
.bmInterfaceFlags = 0, |
|
.bCopyProtect = 0, |
|
}; |
|
|
|
DECLARE_UVC_FRAME_UNCOMPRESSED(1); |
|
DECLARE_UVC_FRAME_UNCOMPRESSED(3); |
|
|
|
static const struct UVC_FRAME_UNCOMPRESSED(3) uvc_frame_yuv_360p = { |
|
.bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(3), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_FRAME_UNCOMPRESSED, |
|
.bFrameIndex = 1, |
|
.bmCapabilities = 0, |
|
.wWidth = cpu_to_le16(640), |
|
.wHeight = cpu_to_le16(360), |
|
.dwMinBitRate = cpu_to_le32(18432000), |
|
.dwMaxBitRate = cpu_to_le32(55296000), |
|
.dwMaxVideoFrameBufferSize = cpu_to_le32(460800), |
|
.dwDefaultFrameInterval = cpu_to_le32(666666), |
|
.bFrameIntervalType = 3, |
|
.dwFrameInterval[0] = cpu_to_le32(666666), |
|
.dwFrameInterval[1] = cpu_to_le32(1000000), |
|
.dwFrameInterval[2] = cpu_to_le32(5000000), |
|
}; |
|
|
|
static const struct UVC_FRAME_UNCOMPRESSED(1) uvc_frame_yuv_720p = { |
|
.bLength = UVC_DT_FRAME_UNCOMPRESSED_SIZE(1), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_FRAME_UNCOMPRESSED, |
|
.bFrameIndex = 2, |
|
.bmCapabilities = 0, |
|
.wWidth = cpu_to_le16(1280), |
|
.wHeight = cpu_to_le16(720), |
|
.dwMinBitRate = cpu_to_le32(29491200), |
|
.dwMaxBitRate = cpu_to_le32(29491200), |
|
.dwMaxVideoFrameBufferSize = cpu_to_le32(1843200), |
|
.dwDefaultFrameInterval = cpu_to_le32(5000000), |
|
.bFrameIntervalType = 1, |
|
.dwFrameInterval[0] = cpu_to_le32(5000000), |
|
}; |
|
|
|
static const struct uvc_format_mjpeg uvc_format_mjpg = { |
|
.bLength = UVC_DT_FORMAT_MJPEG_SIZE, |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_FORMAT_MJPEG, |
|
.bFormatIndex = 2, |
|
.bNumFrameDescriptors = 2, |
|
.bmFlags = 0, |
|
.bDefaultFrameIndex = 1, |
|
.bAspectRatioX = 0, |
|
.bAspectRatioY = 0, |
|
.bmInterfaceFlags = 0, |
|
.bCopyProtect = 0, |
|
}; |
|
|
|
DECLARE_UVC_FRAME_MJPEG(1); |
|
DECLARE_UVC_FRAME_MJPEG(3); |
|
|
|
static const struct UVC_FRAME_MJPEG(3) uvc_frame_mjpg_360p = { |
|
.bLength = UVC_DT_FRAME_MJPEG_SIZE(3), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_FRAME_MJPEG, |
|
.bFrameIndex = 1, |
|
.bmCapabilities = 0, |
|
.wWidth = cpu_to_le16(640), |
|
.wHeight = cpu_to_le16(360), |
|
.dwMinBitRate = cpu_to_le32(18432000), |
|
.dwMaxBitRate = cpu_to_le32(55296000), |
|
.dwMaxVideoFrameBufferSize = cpu_to_le32(460800), |
|
.dwDefaultFrameInterval = cpu_to_le32(666666), |
|
.bFrameIntervalType = 3, |
|
.dwFrameInterval[0] = cpu_to_le32(666666), |
|
.dwFrameInterval[1] = cpu_to_le32(1000000), |
|
.dwFrameInterval[2] = cpu_to_le32(5000000), |
|
}; |
|
|
|
static const struct UVC_FRAME_MJPEG(1) uvc_frame_mjpg_720p = { |
|
.bLength = UVC_DT_FRAME_MJPEG_SIZE(1), |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_FRAME_MJPEG, |
|
.bFrameIndex = 2, |
|
.bmCapabilities = 0, |
|
.wWidth = cpu_to_le16(1280), |
|
.wHeight = cpu_to_le16(720), |
|
.dwMinBitRate = cpu_to_le32(29491200), |
|
.dwMaxBitRate = cpu_to_le32(29491200), |
|
.dwMaxVideoFrameBufferSize = cpu_to_le32(1843200), |
|
.dwDefaultFrameInterval = cpu_to_le32(5000000), |
|
.bFrameIntervalType = 1, |
|
.dwFrameInterval[0] = cpu_to_le32(5000000), |
|
}; |
|
|
|
static const struct uvc_color_matching_descriptor uvc_color_matching = { |
|
.bLength = UVC_DT_COLOR_MATCHING_SIZE, |
|
.bDescriptorType = USB_DT_CS_INTERFACE, |
|
.bDescriptorSubType = UVC_VS_COLORFORMAT, |
|
.bColorPrimaries = 1, |
|
.bTransferCharacteristics = 1, |
|
.bMatrixCoefficients = 4, |
|
}; |
|
|
|
static const struct uvc_descriptor_header * const uvc_fs_control_cls[] = { |
|
(const struct uvc_descriptor_header *) &uvc_control_header, |
|
(const struct uvc_descriptor_header *) &uvc_camera_terminal, |
|
(const struct uvc_descriptor_header *) &uvc_processing, |
|
(const struct uvc_descriptor_header *) &uvc_output_terminal, |
|
NULL, |
|
}; |
|
|
|
static const struct uvc_descriptor_header * const uvc_ss_control_cls[] = { |
|
(const struct uvc_descriptor_header *) &uvc_control_header, |
|
(const struct uvc_descriptor_header *) &uvc_camera_terminal, |
|
(const struct uvc_descriptor_header *) &uvc_processing, |
|
(const struct uvc_descriptor_header *) &uvc_output_terminal, |
|
NULL, |
|
}; |
|
|
|
static const struct uvc_descriptor_header * const uvc_fs_streaming_cls[] = { |
|
(const struct uvc_descriptor_header *) &uvc_input_header, |
|
(const struct uvc_descriptor_header *) &uvc_format_yuv, |
|
(const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, |
|
(const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, |
|
(const struct uvc_descriptor_header *) &uvc_format_mjpg, |
|
(const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, |
|
(const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, |
|
(const struct uvc_descriptor_header *) &uvc_color_matching, |
|
NULL, |
|
}; |
|
|
|
static const struct uvc_descriptor_header * const uvc_hs_streaming_cls[] = { |
|
(const struct uvc_descriptor_header *) &uvc_input_header, |
|
(const struct uvc_descriptor_header *) &uvc_format_yuv, |
|
(const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, |
|
(const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, |
|
(const struct uvc_descriptor_header *) &uvc_format_mjpg, |
|
(const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, |
|
(const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, |
|
(const struct uvc_descriptor_header *) &uvc_color_matching, |
|
NULL, |
|
}; |
|
|
|
static const struct uvc_descriptor_header * const uvc_ss_streaming_cls[] = { |
|
(const struct uvc_descriptor_header *) &uvc_input_header, |
|
(const struct uvc_descriptor_header *) &uvc_format_yuv, |
|
(const struct uvc_descriptor_header *) &uvc_frame_yuv_360p, |
|
(const struct uvc_descriptor_header *) &uvc_frame_yuv_720p, |
|
(const struct uvc_descriptor_header *) &uvc_format_mjpg, |
|
(const struct uvc_descriptor_header *) &uvc_frame_mjpg_360p, |
|
(const struct uvc_descriptor_header *) &uvc_frame_mjpg_720p, |
|
(const struct uvc_descriptor_header *) &uvc_color_matching, |
|
NULL, |
|
}; |
|
|
|
/* -------------------------------------------------------------------------- |
|
* USB configuration |
|
*/ |
|
|
|
static int |
|
webcam_config_bind(struct usb_configuration *c) |
|
{ |
|
int status = 0; |
|
|
|
f_uvc = usb_get_function(fi_uvc); |
|
if (IS_ERR(f_uvc)) |
|
return PTR_ERR(f_uvc); |
|
|
|
status = usb_add_function(c, f_uvc); |
|
if (status < 0) |
|
usb_put_function(f_uvc); |
|
|
|
return status; |
|
} |
|
|
|
static struct usb_configuration webcam_config_driver = { |
|
.label = webcam_config_label, |
|
.bConfigurationValue = 1, |
|
.iConfiguration = 0, /* dynamic */ |
|
.bmAttributes = USB_CONFIG_ATT_SELFPOWER, |
|
.MaxPower = CONFIG_USB_GADGET_VBUS_DRAW, |
|
}; |
|
|
|
static int |
|
webcam_unbind(struct usb_composite_dev *cdev) |
|
{ |
|
if (!IS_ERR_OR_NULL(f_uvc)) |
|
usb_put_function(f_uvc); |
|
if (!IS_ERR_OR_NULL(fi_uvc)) |
|
usb_put_function_instance(fi_uvc); |
|
return 0; |
|
} |
|
|
|
static int |
|
webcam_bind(struct usb_composite_dev *cdev) |
|
{ |
|
struct f_uvc_opts *uvc_opts; |
|
int ret; |
|
|
|
fi_uvc = usb_get_function_instance("uvc"); |
|
if (IS_ERR(fi_uvc)) |
|
return PTR_ERR(fi_uvc); |
|
|
|
uvc_opts = container_of(fi_uvc, struct f_uvc_opts, func_inst); |
|
|
|
uvc_opts->streaming_interval = streaming_interval; |
|
uvc_opts->streaming_maxpacket = streaming_maxpacket; |
|
uvc_opts->streaming_maxburst = streaming_maxburst; |
|
|
|
uvc_opts->fs_control = uvc_fs_control_cls; |
|
uvc_opts->ss_control = uvc_ss_control_cls; |
|
uvc_opts->fs_streaming = uvc_fs_streaming_cls; |
|
uvc_opts->hs_streaming = uvc_hs_streaming_cls; |
|
uvc_opts->ss_streaming = uvc_ss_streaming_cls; |
|
|
|
/* Allocate string descriptor numbers ... note that string contents |
|
* can be overridden by the composite_dev glue. |
|
*/ |
|
ret = usb_string_ids_tab(cdev, webcam_strings); |
|
if (ret < 0) |
|
goto error; |
|
webcam_device_descriptor.iManufacturer = |
|
webcam_strings[USB_GADGET_MANUFACTURER_IDX].id; |
|
webcam_device_descriptor.iProduct = |
|
webcam_strings[USB_GADGET_PRODUCT_IDX].id; |
|
webcam_config_driver.iConfiguration = |
|
webcam_strings[STRING_DESCRIPTION_IDX].id; |
|
|
|
/* Register our configuration. */ |
|
if ((ret = usb_add_config(cdev, &webcam_config_driver, |
|
webcam_config_bind)) < 0) |
|
goto error; |
|
|
|
usb_composite_overwrite_options(cdev, &coverwrite); |
|
INFO(cdev, "Webcam Video Gadget\n"); |
|
return 0; |
|
|
|
error: |
|
usb_put_function_instance(fi_uvc); |
|
return ret; |
|
} |
|
|
|
/* -------------------------------------------------------------------------- |
|
* Driver |
|
*/ |
|
|
|
static struct usb_composite_driver webcam_driver = { |
|
.name = "g_webcam", |
|
.dev = &webcam_device_descriptor, |
|
.strings = webcam_device_strings, |
|
.max_speed = USB_SPEED_SUPER, |
|
.bind = webcam_bind, |
|
.unbind = webcam_unbind, |
|
}; |
|
|
|
module_usb_composite_driver(webcam_driver); |
|
|
|
MODULE_AUTHOR("Laurent Pinchart"); |
|
MODULE_DESCRIPTION("Webcam Video Gadget"); |
|
MODULE_LICENSE("GPL"); |
|
|
|
|