Files
nuttx/drivers/usbdev/uvc.c
T
Piyush Patle 0dccc8ba21 include/debug.h: Move to include/nuttx/debug.h
debug.h is a NuttX-specific, non-POSIX header. Placing it in the
top-level include/ directory creates naming conflicts with external
projects that define their own debug.h.
This commit moves the canonical header to include/nuttx/debug.h,
following the NuttX convention for non-POSIX/non-standard headers,
and updates all in-tree references.

A backward-compatibility shim is left at include/debug.h that
emits a deprecation #warning and re-includes <nuttx/debug.h>,
allowing out-of-tree code to continue building while migrating.

Signed-off-by: Piyush Patle <piyushpatle228@gmail.com>
2026-04-07 07:50:06 -03:00

1666 lines
48 KiB
C

/****************************************************************************
* drivers/usbdev/uvc.c
*
* SPDX-License-Identifier: Apache-2.0
*
* USB Video Class (UVC) 1.1 Gadget Driver for NuttX
* Bulk transport, uncompressed YUY2, QVGA (320x240)
*
* Architecture: modeled after drivers/usbdev/adb.c (usbdev_fs pattern)
* but with custom class-specific setup handling for UVC PROBE/COMMIT.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <errno.h>
#include <nuttx/debug.h>
#include <string.h>
#include <nuttx/nuttx.h>
#include <nuttx/kmalloc.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <nuttx/usb/uvc.h>
#ifdef CONFIG_USBDEV_COMPOSITE
# include <nuttx/usb/composite.h>
#endif
#include <nuttx/fs/fs.h>
#include <nuttx/wqueue.h>
#include <poll.h>
#include <nuttx/mutex.h>
#include <nuttx/lib/lib.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define USBUVC_CHARDEV_PATH "/dev/uvc0"
#ifdef CONFIG_USBDEV_SELFPOWERED
# define USBUVC_SELFPOWERED USB_CONFIG_ATTR_SELFPOWER
#else
# define USBUVC_SELFPOWERED (0)
#endif
#ifdef CONFIG_USBDEV_REMOTEWAKEUP
# define USBUVC_REMOTEWAKEUP USB_CONFIG_ATTR_WAKEUP
#else
# define USBUVC_REMOTEWAKEUP (0)
#endif
#define USBUVC_MXDESCLEN (64)
#define USBUVC_MAXSTRLEN (USBUVC_MXDESCLEN - 2)
#define USBUVC_VERSIONNO (0x0100)
#define USBUVC_STR_LANGUAGE (0x0409)
/* String descriptor IDs — standalone mode uses absolute IDs;
* composite mode uses relative IDs offset by devinfo.strbase.
*/
#ifndef CONFIG_USBUVC_COMPOSITE
# define USBUVC_MANUFACTURERSTRID (1)
# define USBUVC_PRODUCTSTRID (2)
# define USBUVC_SERIALSTRID (3)
# define USBUVC_CONFIGSTRID (4)
# define USBUVC_VCIFSTRID (5)
# define USBUVC_VSIFSTRID (6)
# define USBUVC_NSTDSTRIDS (6) /* Total standalone string IDs */
#endif
/* In composite mode, we only contribute 2 interface strings.
* The composite framework assigns strbase; our strings are
* strbase+1 (VC) and strbase+2 (VS).
*/
#define USBUVC_VCIFSTRID_REL (1) /* Relative: VC interface string */
#define USBUVC_VSIFSTRID_REL (2) /* Relative: VS interface string */
/* Video parameters — defaults when no runtime params supplied */
#define USBUVC_DEF_WIDTH 320
#define USBUVC_DEF_HEIGHT 240
#define USBUVC_DEF_FPS 5
#define USBUVC_BPP 2 /* YUY2 = 2 bytes/pixel */
/* Number of poll waiters */
#ifndef CONFIG_USBUVC_NPOLLWAITERS
# define CONFIG_USBUVC_NPOLLWAITERS 2
#endif
/* Number of write requests */
#ifndef CONFIG_USBUVC_NWRREQS
# define CONFIG_USBUVC_NWRREQS 4
#endif
/* Bulk IN EP max packet size (Full-Speed) */
#ifndef CONFIG_USBUVC_EPBULKIN_FSSIZE
# define CONFIG_USBUVC_EPBULKIN_FSSIZE 64
#endif
#ifndef CONFIG_USBUVC_EP0MAXPACKET
# define CONFIG_USBUVC_EP0MAXPACKET 64
#endif
/* UVC descriptor unit/terminal IDs */
#define USBUVC_IT_ID 1 /* Input Terminal (Camera) */
#define USBUVC_OT_ID 2 /* Output Terminal (USB Streaming) */
/****************************************************************************
* Private Types
****************************************************************************/
/* Container for write requests — flink must be first for sq_entry_t cast */
struct uvc_wrreq_s
{
FAR struct uvc_wrreq_s *flink; /* Singly linked list link */
FAR struct usbdev_req_s *req; /* The contained USB request */
};
struct uvc_dev_s
{
FAR struct usbdev_s *usbdev;
FAR struct usbdev_ep_s *epbulkin;
FAR struct usbdev_req_s *ctrlreq;
FAR struct usbdev_ep_s *ep0;
struct uvc_streaming_control_s probe;
struct uvc_streaming_control_s commit;
struct usbdev_devinfo_s devinfo;
/* Runtime video parameters from application */
uint16_t width;
uint16_t height;
uint8_t fps;
uint32_t frame_size;
uint32_t frame_interval; /* in 100ns units */
uint32_t bitrate;
mutex_t lock;
sem_t wrsem; /* Signaled when wrreq available */
bool streaming;
FAR struct pollfd *fds[CONFIG_USBUVC_NPOLLWAITERS];
uint8_t fid; /* Frame ID toggle bit */
uint8_t config;
/* Write request pool */
struct sq_queue_s wrfree; /* Available write request containers */
struct uvc_wrreq_s wrreqs[CONFIG_USBUVC_NWRREQS];
int nwralloc;
};
/* Forward declarations */
/* EP0 submit helper — in composite mode, route through composite layer */
#ifdef CONFIG_USBUVC_COMPOSITE
# define UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl) \
composite_ep0submit(driver, dev, ctrlreq, ctrl)
#else
# define UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl) \
EP_SUBMIT(dev->ep0, ctrlreq)
#endif
static int uvc_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void uvc_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static int uvc_setup(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl,
FAR uint8_t *dataout, size_t outlen);
static void uvc_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void uvc_streaming_stop(FAR struct uvc_dev_s *priv);
static const struct usbdevclass_driverops_s g_uvc_driverops =
{
.bind = uvc_bind,
.unbind = uvc_unbind,
.setup = uvc_setup,
.disconnect = uvc_disconnect,
};
/* File operations for /dev/uvc0 */
static ssize_t uvc_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static int uvc_open(FAR struct file *filep);
static int uvc_close(FAR struct file *filep);
static int uvc_poll(FAR struct file *filep,
FAR struct pollfd *fds, bool setup);
static const struct file_operations g_uvc_fops =
{
.open = uvc_open,
.close = uvc_close,
.write = uvc_write,
.poll = uvc_poll,
};
/****************************************************************************
* Private Data — USB Descriptors
****************************************************************************/
#ifndef CONFIG_USBUVC_COMPOSITE
/* Device Descriptor — standalone mode only */
static const struct usb_devdesc_s g_uvc_devdesc =
{
.len = USB_SIZEOF_DEVDESC,
.type = USB_DESC_TYPE_DEVICE,
.usb =
{
LSBYTE(0x0200), MSBYTE(0x0200)
},
.classid = USB_CLASS_MISC, /* 0xEF - IAD device */
.subclass = 0x02, /* Common Class */
.protocol = 0x01, /* IAD */
.mxpacketsize = CONFIG_USBUVC_EP0MAXPACKET,
.vendor =
{
LSBYTE(0x1d6b), MSBYTE(0x1d6b) /* Linux Foundation */
},
.product =
{
LSBYTE(0x0102), MSBYTE(0x0102) /* Webcam gadget */
},
.device =
{
LSBYTE(USBUVC_VERSIONNO),
MSBYTE(USBUVC_VERSIONNO)
},
.imfgr = USBUVC_MANUFACTURERSTRID,
.iproduct = USBUVC_PRODUCTSTRID,
.serno = USBUVC_SERIALSTRID,
.nconfigs = USBUVC_NCONFIGS,
};
static const struct usb_cfgdesc_s g_uvc_cfgdesc =
{
.len = USB_SIZEOF_CFGDESC,
.type = USB_DESC_TYPE_CONFIG,
.ninterfaces = USBUVC_NINTERFACES,
.cfgvalue = 1,
.icfg = USBUVC_CONFIGSTRID,
.attr = USB_CONFIG_ATTR_ONE | USBUVC_SELFPOWERED |
USBUVC_REMOTEWAKEUP,
.mxpower = (CONFIG_USBDEV_MAXPOWER + 1) / 2,
};
#endif /* !CONFIG_USBUVC_COMPOSITE */
/* Interface Association Descriptor (IAD) for UVC
* Note: bFirstInterface and iFunction are patched at runtime.
*/
static const uint8_t g_uvc_iad[] =
{
0x08, /* bLength */
USB_DESC_TYPE_INTERFACEASSOCIATION, /* bDescriptorType */
0x00, /* bFirstInterface: patched */
0x02, /* bInterfaceCount (VC + VS) */
USB_CLASS_VIDEO, /* bFunctionClass */
UVC_SC_VIDEO_INTERFACE_COLLECTION, /* bFunctionSubClass */
UVC_PC_PROTOCOL_UNDEFINED, /* bFunctionProtocol */
0x00, /* iFunction: patched */
};
/* VideoControl Interface Descriptor
* Note: ifno and iif are patched at runtime.
*/
static const struct usb_ifdesc_s g_uvc_vc_ifdesc =
{
.len = USB_SIZEOF_IFDESC,
.type = USB_DESC_TYPE_INTERFACE,
.ifno = 0, /* patched: ifnobase */
.alt = 0,
.neps = 0, /* No interrupt EP for now */
.classid = USB_CLASS_VIDEO,
.subclass = UVC_SC_VIDEOCONTROL,
.protocol = UVC_PC_PROTOCOL_UNDEFINED,
.iif = 0, /* patched: strbase + VCIFSTRID_REL */
};
/* VC Header Descriptor (12 + 1*n bytes, n=1 VS interface)
* Note: baInterfaceNr is patched at runtime to ifnobase + 1.
*/
static const uint8_t g_uvc_vc_header[] =
{
0x0d, /* bLength (13) */
UVC_CS_INTERFACE, /* bDescriptorType */
UVC_VC_HEADER, /* bDescriptorSubtype */
LSBYTE(0x0110), /* bcdUVC: 1.10 */
MSBYTE(0x0110),
LSBYTE(0x0042), /* wTotalLength: will be patched */
MSBYTE(0x0042),
(0x005b8d80 >> 0) & 0xff, /* dwClockFrequency: 6MHz */
(0x005b8d80 >> 8) & 0xff,
(0x005b8d80 >> 16) & 0xff,
(0x005b8d80 >> 24) & 0xff,
0x01, /* bInCollection: 1 VS interface */
0x00, /* baInterfaceNr(1): patched */
};
/* Input Terminal Descriptor (Camera) — 17 bytes */
static const uint8_t g_uvc_input_terminal[] =
{
0x11, /* bLength (17) */
UVC_CS_INTERFACE, /* bDescriptorType */
UVC_VC_INPUT_TERMINAL, /* bDescriptorSubtype */
USBUVC_IT_ID, /* bTerminalID */
LSBYTE(UVC_ITT_CAMERA), /* wTerminalType */
MSBYTE(UVC_ITT_CAMERA),
0x00, /* bAssocTerminal */
0x00, /* iTerminal */
0x00, 0x00, /* wObjectiveFocalLengthMin */
0x00, 0x00, /* wObjectiveFocalLengthMax */
0x00, 0x00, /* wOcularFocalLength */
0x02, /* bControlSize */
0x00, 0x00, /* bmControls (none) */
};
/* Output Terminal Descriptor — 9 bytes */
static const uint8_t g_uvc_output_terminal[] =
{
0x09, /* bLength */
UVC_CS_INTERFACE, /* bDescriptorType */
UVC_VC_OUTPUT_TERMINAL, /* bDescriptorSubtype */
USBUVC_OT_ID, /* bTerminalID */
LSBYTE(UVC_TT_STREAMING), /* wTerminalType */
MSBYTE(UVC_TT_STREAMING),
0x00, /* bAssocTerminal */
USBUVC_IT_ID, /* bSourceID */
0x00, /* iTerminal */
};
/* VideoStreaming Interface Descriptor
* Note: ifno and iif are patched at runtime.
*/
static const struct usb_ifdesc_s g_uvc_vs_ifdesc =
{
.len = USB_SIZEOF_IFDESC,
.type = USB_DESC_TYPE_INTERFACE,
.ifno = 0, /* patched: ifnobase + 1 */
.alt = 0,
.neps = 1, /* 1 Bulk IN EP */
.classid = USB_CLASS_VIDEO,
.subclass = UVC_SC_VIDEOSTREAMING,
.protocol = UVC_PC_PROTOCOL_UNDEFINED,
.iif = 0, /* patched: strbase + VSIFSTRID_REL */
};
/* VS Input Header — 14 bytes (1 format, bControlSize=1) */
static const uint8_t g_uvc_vs_input_header[] =
{
0x0e, /* bLength (14) */
UVC_CS_INTERFACE, /* bDescriptorType */
UVC_VS_INPUT_HEADER, /* bDescriptorSubtype */
0x01, /* bNumFormats: 1 */
0x00, 0x00, /* wTotalLength: patched later */
0x00, /* bEndpointAddress: patched later */
0x00, /* bmInfo: no dynamic format change */
USBUVC_OT_ID, /* bTerminalLink */
0x00, /* bStillCaptureMethod: none */
0x00, /* bTriggerSupport */
0x00, /* bTriggerUsage */
0x01, /* bControlSize: 1 byte */
0x00, /* bmaControls(1): no controls */
};
/* VS Uncompressed Format Descriptor — 27 bytes */
static const uint8_t g_uvc_vs_format_uncomp[] =
{
0x1b, /* bLength (27) */
UVC_CS_INTERFACE, /* bDescriptorType */
UVC_VS_FORMAT_UNCOMPRESSED, /* bDescriptorSubtype */
0x01, /* bFormatIndex: 1 */
0x01, /* bNumFrameDescriptors: 1 */
/* guidFormat: YUY2 */
'Y', 'U', 'Y', '2', 0x00, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71,
0x10, /* bBitsPerPixel: 16 */
0x01, /* bDefaultFrameIndex: 1 */
0x00, /* bAspectRatioX */
0x00, /* bAspectRatioY */
0x00, /* bmInterlaceFlags */
0x00, /* bCopyProtect */
};
/* VS Uncompressed Frame Descriptor length - 30 bytes (1 discrete interval) */
#define USBUVC_VS_FRAME_DESC_LEN 30
/* VS Color Matching Descriptor — 6 bytes */
static const uint8_t g_uvc_vs_color[] =
{
0x06, /* bLength */
UVC_CS_INTERFACE, /* bDescriptorType */
UVC_VS_COLOR_FORMAT, /* bDescriptorSubtype */
0x01, /* bColorPrimaries: BT.709 */
0x01, /* bTransferCharacteristics: BT.709 */
0x04, /* bMatrixCoefficients: SMPTE 170M */
};
/* Bulk IN Endpoint info */
static const struct usbdev_epinfo_s g_uvc_epbulkin =
{
.desc =
{
.len = USB_SIZEOF_EPDESC,
.type = USB_DESC_TYPE_ENDPOINT,
.addr = USB_DIR_IN,
.attr = USB_EP_ATTR_XFER_BULK |
USB_EP_ATTR_NO_SYNC |
USB_EP_ATTR_USAGE_DATA,
.interval = 0,
},
.reqnum = CONFIG_USBUVC_NWRREQS,
.fssize = CONFIG_USBUVC_EPBULKIN_FSSIZE,
};
/* Endpoint info array for composite framework */
#ifdef CONFIG_USBDEV_COMPOSITE
static FAR const struct usbdev_epinfo_s *g_uvc_epinfos[USBUVC_NUM_EPS] =
{
&g_uvc_epbulkin,
};
#endif
/* Global device instance (singleton) */
static struct uvc_dev_s *g_uvc_dev;
/****************************************************************************
* Private Functions — Helpers
****************************************************************************/
static void uvc_default_streaming_ctrl(
FAR struct uvc_dev_s *priv,
FAR struct uvc_streaming_control_s *ctrl)
{
memset(ctrl, 0, sizeof(*ctrl));
ctrl->bmhint = 1;
ctrl->bformatindex = 1;
ctrl->bframeindex = 1;
ctrl->dwframeinterval = priv->frame_interval;
ctrl->dwmaxvideoframesize = priv->frame_size;
ctrl->dwmaxpayloadtransfersize = priv->frame_size +
UVC_PAYLOAD_HEADER_LEN;
ctrl->dwclockfrequency = 6000000; /* 6 MHz */
ctrl->bmframinginfo = 0x03; /* FID + EOF required */
ctrl->bpreferedversion = 1;
ctrl->bminversion = 1;
ctrl->bmaxversion = 1;
}
static void uvc_ep0incomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
if (req->result || req->xfrd != req->len)
{
uerr("EP0 complete: result=%d xfrd=%d len=%d\n",
req->result, req->xfrd, req->len);
}
}
static void uvc_wrreq_callback(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
if (priv)
{
FAR struct uvc_wrreq_s *wrcontainer;
irqstate_t flags;
if (req->result != OK)
{
uerr("UVC bulk IN xfer failed: %d\n", req->result);
}
wrcontainer = (FAR struct uvc_wrreq_s *)req->priv;
flags = enter_critical_section();
sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->wrfree);
leave_critical_section(flags);
nxsem_post(&priv->wrsem);
}
}
/****************************************************************************
* Private Functions — Configuration Descriptor Builder
****************************************************************************/
static int16_t uvc_mkcfgdesc(FAR uint8_t *buf,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed, uint8_t type)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
FAR uint8_t *p = buf;
uint16_t vs_total;
uint16_t vc_total;
int16_t totallen;
uint8_t vc_ifno;
uint8_t vs_ifno;
uint8_t vc_strid;
uint8_t vs_strid;
UNUSED(speed);
UNUSED(type);
/* Compute interface numbers and string IDs from devinfo */
vc_ifno = devinfo->ifnobase;
vs_ifno = devinfo->ifnobase + 1;
vc_strid = devinfo->strbase + USBUVC_VCIFSTRID_REL;
vs_strid = devinfo->strbase + USBUVC_VSIFSTRID_REL;
/* Calculate total descriptor size (without config header for composite) */
totallen = (int16_t)(sizeof(g_uvc_iad) +
sizeof(g_uvc_vc_ifdesc) +
sizeof(g_uvc_vc_header) +
sizeof(g_uvc_input_terminal) +
sizeof(g_uvc_output_terminal) +
sizeof(g_uvc_vs_ifdesc) +
sizeof(g_uvc_vs_input_header) +
sizeof(g_uvc_vs_format_uncomp) +
USBUVC_VS_FRAME_DESC_LEN +
sizeof(g_uvc_vs_color) +
USB_SIZEOF_EPDESC);
#ifndef CONFIG_USBUVC_COMPOSITE
totallen += sizeof(g_uvc_cfgdesc);
#endif
if (!buf)
{
return totallen;
}
#ifndef CONFIG_USBUVC_COMPOSITE
/* Configuration descriptor header — standalone only */
{
FAR struct usb_cfgdesc_s *dest = (FAR struct usb_cfgdesc_s *)p;
memcpy(p, &g_uvc_cfgdesc, sizeof(g_uvc_cfgdesc));
dest->type = type;
dest->totallen[0] = LSBYTE(totallen);
dest->totallen[1] = MSBYTE(totallen);
}
p += sizeof(g_uvc_cfgdesc);
#endif
/* IAD — patch bFirstInterface and iFunction */
memcpy(p, g_uvc_iad, sizeof(g_uvc_iad));
p[2] = vc_ifno; /* bFirstInterface */
p[7] = vc_strid; /* iFunction */
p += sizeof(g_uvc_iad);
/* VC Interface — patch ifno and iif */
memcpy(p, &g_uvc_vc_ifdesc, sizeof(g_uvc_vc_ifdesc));
((FAR struct usb_ifdesc_s *)p)->ifno = vc_ifno;
((FAR struct usb_ifdesc_s *)p)->iif = vc_strid;
p += sizeof(g_uvc_vc_ifdesc);
/* VC Header — patch wTotalLength and baInterfaceNr */
vc_total = sizeof(g_uvc_vc_header) +
sizeof(g_uvc_input_terminal) +
sizeof(g_uvc_output_terminal);
memcpy(p, g_uvc_vc_header, sizeof(g_uvc_vc_header));
p[5] = LSBYTE(vc_total);
p[6] = MSBYTE(vc_total);
p[12] = vs_ifno; /* baInterfaceNr(1) */
p += sizeof(g_uvc_vc_header);
/* Input Terminal */
memcpy(p, g_uvc_input_terminal, sizeof(g_uvc_input_terminal));
p += sizeof(g_uvc_input_terminal);
/* Output Terminal */
memcpy(p, g_uvc_output_terminal, sizeof(g_uvc_output_terminal));
p += sizeof(g_uvc_output_terminal);
/* VS Interface — patch ifno and iif */
memcpy(p, &g_uvc_vs_ifdesc, sizeof(g_uvc_vs_ifdesc));
((FAR struct usb_ifdesc_s *)p)->ifno = vs_ifno;
((FAR struct usb_ifdesc_s *)p)->iif = vs_strid;
p += sizeof(g_uvc_vs_ifdesc);
/* VS Input Header — patch wTotalLength and bEndpointAddress */
vs_total = sizeof(g_uvc_vs_input_header) +
sizeof(g_uvc_vs_format_uncomp) +
USBUVC_VS_FRAME_DESC_LEN +
sizeof(g_uvc_vs_color);
memcpy(p, g_uvc_vs_input_header, sizeof(g_uvc_vs_input_header));
p[4] = LSBYTE(vs_total);
p[5] = MSBYTE(vs_total);
p[6] = USB_EPIN(devinfo->epno[USBUVC_EP_BULKIN_IDX]);
p += sizeof(g_uvc_vs_input_header);
/* VS Format */
memcpy(p, g_uvc_vs_format_uncomp, sizeof(g_uvc_vs_format_uncomp));
p += sizeof(g_uvc_vs_format_uncomp);
/* VS Frame - built at runtime from video params */
/* bLength (30) */
p[0] = USBUVC_VS_FRAME_DESC_LEN;
/* bDescriptorType */
p[1] = UVC_CS_INTERFACE;
/* bDescriptorSubtype */
p[2] = UVC_VS_FRAME_UNCOMPRESSED;
/* bFrameIndex: 1 */
p[3] = 0x01;
/* bmCapabilities */
p[4] = 0x00;
/* wWidth */
p[5] = LSBYTE(priv->width);
p[6] = MSBYTE(priv->width);
/* Height (2 bytes LE) */
p[7] = LSBYTE(priv->height);
p[8] = MSBYTE(priv->height);
/* dwMinBitRate */
p[9] = (priv->bitrate >> 0) & 0xff;
p[10] = (priv->bitrate >> 8) & 0xff;
p[11] = (priv->bitrate >> 16) & 0xff;
p[12] = (priv->bitrate >> 24) & 0xff;
/* dwMaxBitRate */
p[13] = (priv->bitrate >> 0) & 0xff;
p[14] = (priv->bitrate >> 8) & 0xff;
p[15] = (priv->bitrate >> 16) & 0xff;
p[16] = (priv->bitrate >> 24) & 0xff;
/* dwMaxVideoFrameBufferSize */
p[17] = (priv->frame_size >> 0) & 0xff;
p[18] = (priv->frame_size >> 8) & 0xff;
p[19] = (priv->frame_size >> 16) & 0xff;
p[20] = (priv->frame_size >> 24) & 0xff;
/* dwDefaultFrameInterval */
p[21] = (priv->frame_interval >> 0) & 0xff;
p[22] = (priv->frame_interval >> 8) & 0xff;
p[23] = (priv->frame_interval >> 16) & 0xff;
p[24] = (priv->frame_interval >> 24) & 0xff;
/* bFrameIntervalType: 1 discrete */
p[25] = 0x01;
/* dwFrameInterval(1) */
p[26] = (priv->frame_interval >> 0) & 0xff;
p[27] = (priv->frame_interval >> 8) & 0xff;
p[28] = (priv->frame_interval >> 16) & 0xff;
p[29] = (priv->frame_interval >> 24) & 0xff;
p += USBUVC_VS_FRAME_DESC_LEN;
/* VS Color Matching */
memcpy(p, g_uvc_vs_color, sizeof(g_uvc_vs_color));
p += sizeof(g_uvc_vs_color);
/* Bulk IN EP descriptor */
usbdev_copy_epdesc((FAR struct usb_epdesc_s *)p,
devinfo->epno[USBUVC_EP_BULKIN_IDX],
speed, &g_uvc_epbulkin);
p += USB_SIZEOF_EPDESC;
return (int16_t)(p - buf);
}
/****************************************************************************
* Private Functions — String Descriptor
****************************************************************************/
static int uvc_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc)
{
FAR uint8_t *data = (FAR uint8_t *)(strdesc + 1);
FAR const char *str;
int len;
int ndata;
int i;
switch (id)
{
#ifndef CONFIG_USBUVC_COMPOSITE
case 0:
{
/* Descriptor 0 is the language id */
strdesc->len = 4;
strdesc->type = USB_DESC_TYPE_STRING;
data[0] = LSBYTE(USBUVC_STR_LANGUAGE);
data[1] = MSBYTE(USBUVC_STR_LANGUAGE);
return 4;
}
case USBUVC_MANUFACTURERSTRID:
str = "NuttX";
break;
case USBUVC_PRODUCTSTRID:
str = "NuttX UVC Camera";
break;
case USBUVC_SERIALSTRID:
str = "0001";
break;
case USBUVC_CONFIGSTRID:
str = "UVC Config";
break;
#endif /* !CONFIG_USBUVC_COMPOSITE */
/* Interface strings — used in both standalone and composite.
* In standalone: absolute IDs (USBUVC_VCIFSTRID/VSIFSTRID).
* In composite: relative IDs (USBUVC_VCIFSTRID_REL/VSIFSTRID_REL),
* the composite framework adds strbase.
*/
#ifdef CONFIG_USBUVC_COMPOSITE
case USBUVC_VCIFSTRID_REL:
#else
case USBUVC_VCIFSTRID:
#endif
str = "Video Control";
break;
#ifdef CONFIG_USBUVC_COMPOSITE
case USBUVC_VSIFSTRID_REL:
#else
case USBUVC_VSIFSTRID:
#endif
str = "Video Streaming";
break;
default:
return -EINVAL;
}
len = strlen(str);
if (len > (USBUVC_MAXSTRLEN / 2))
{
len = (USBUVC_MAXSTRLEN / 2);
}
for (i = 0, ndata = 0; i < len; i++, ndata += 2)
{
data[ndata] = str[i];
data[ndata + 1] = 0;
}
strdesc->len = ndata + 2;
strdesc->type = USB_DESC_TYPE_STRING;
return strdesc->len;
}
/****************************************************************************
* Private Functions — UVC Class-Specific Setup (EP0)
****************************************************************************/
static int uvc_class_setup(FAR struct uvc_dev_s *priv,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl,
FAR struct usbdev_req_s *ctrlreq)
{
uint8_t req = ctrl->req;
uint8_t cs = ctrl->value[1]; /* Control selector */
uint16_t len = GETUINT16(ctrl->len);
int ret = -EOPNOTSUPP;
uinfo("UVC class setup: req=0x%02x cs=0x%02x len=%d\n", req, cs, len);
if (cs == UVC_VS_PROBE_CONTROL || cs == UVC_VS_COMMIT_CONTROL)
{
FAR struct uvc_streaming_control_s *target;
target = (cs == UVC_VS_PROBE_CONTROL) ?
&priv->probe : &priv->commit;
switch (req)
{
case UVC_SET_CUR:
/* Host sends probe/commit data — we accept it.
* The data arrives in dataout phase, handled below.
* For now just return 0 to ACK the setup.
*/
if (len >= UVC_PROBE_COMMIT_SIZE)
{
len = UVC_PROBE_COMMIT_SIZE;
}
ret = 0;
break;
case UVC_GET_CUR:
case UVC_GET_MIN:
case UVC_GET_MAX:
case UVC_GET_DEF:
if (len > UVC_PROBE_COMMIT_SIZE)
{
len = UVC_PROBE_COMMIT_SIZE;
}
memcpy(ctrlreq->buf, target, len);
ctrlreq->len = len;
ret = len;
break;
default:
break;
}
}
return ret;
}
/****************************************************************************
* Private Functions — USB Class Driver Operations
****************************************************************************/
static int uvc_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
FAR struct usbdev_req_s *req;
int i;
uinfo("UVC bind\n");
priv->usbdev = dev;
priv->ep0 = dev->ep0;
/* Allocate control request */
priv->ctrlreq = usbdev_allocreq(dev->ep0, 256);
if (!priv->ctrlreq)
{
return -ENOMEM;
}
priv->ctrlreq->callback = uvc_ep0incomplete;
/* Allocate Bulk IN endpoint — use devinfo.epno[] set by
* standalone init or composite framework.
*/
priv->epbulkin = DEV_ALLOCEP(dev,
USB_EPIN(priv->devinfo.epno[
USBUVC_EP_BULKIN_IDX]),
true,
USB_EP_ATTR_XFER_BULK);
if (!priv->epbulkin)
{
uerr("Failed to allocate bulk IN EP\n");
return -ENODEV;
}
priv->epbulkin->priv = priv;
priv->devinfo.epno[USBUVC_EP_BULKIN_IDX] =
USB_EPNO(priv->epbulkin->eplog);
/* Allocate write requests */
sq_init(&priv->wrfree);
for (i = 0; i < CONFIG_USBUVC_NWRREQS; i++)
{
req = usbdev_allocreq(priv->epbulkin,
CONFIG_USBUVC_EPBULKIN_FSSIZE);
if (!req)
{
break;
}
req->callback = uvc_wrreq_callback;
req->priv = &priv->wrreqs[i];
priv->wrreqs[i].req = req;
sq_addlast((FAR sq_entry_t *)&priv->wrreqs[i], &priv->wrfree);
priv->nwralloc++;
}
uvc_default_streaming_ctrl(priv, &priv->probe);
uvc_default_streaming_ctrl(priv, &priv->commit);
DEV_CONNECT(dev);
return OK;
}
static void uvc_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
FAR struct uvc_wrreq_s *wrcontainer;
uinfo("UVC unbind\n");
/* Free write requests */
while ((wrcontainer = (FAR struct uvc_wrreq_s *)
sq_remfirst(&priv->wrfree)))
{
usbdev_freereq(priv->epbulkin, wrcontainer->req);
wrcontainer->req = NULL;
}
if (priv->epbulkin)
{
DEV_FREEEP(dev, priv->epbulkin);
priv->epbulkin = NULL;
}
if (priv->ctrlreq)
{
usbdev_freereq(dev->ep0, priv->ctrlreq);
priv->ctrlreq = NULL;
}
DEV_DISCONNECT(dev);
}
static int uvc_setup(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl,
FAR uint8_t *dataout, size_t outlen)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
FAR struct usbdev_req_s *ctrlreq = priv->ctrlreq;
uint16_t value = GETUINT16(ctrl->value);
uint16_t index = GETUINT16(ctrl->index);
uint16_t len = GETUINT16(ctrl->len);
int ret = -EOPNOTSUPP;
UNUSED(index);
uinfo("UVC setup: type=0x%02x req=0x%02x value=0x%04x len=%d\n",
ctrl->type, ctrl->req, value, len);
/* Handle class-specific requests */
if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_CLASS)
{
/* SET_CUR with data phase — copy incoming data */
if (ctrl->req == UVC_SET_CUR && dataout && outlen > 0)
{
uint8_t cs = ctrl->value[1];
if (cs == UVC_VS_PROBE_CONTROL)
{
memcpy(&priv->probe, dataout,
outlen < UVC_PROBE_COMMIT_SIZE ?
outlen : UVC_PROBE_COMMIT_SIZE);
/* Clamp to our only supported format */
priv->probe.bformatindex = 1;
priv->probe.bframeindex = 1;
priv->probe.dwframeinterval = priv->frame_interval;
priv->probe.dwmaxvideoframesize = priv->frame_size;
priv->probe.dwmaxpayloadtransfersize =
priv->frame_size + UVC_PAYLOAD_HEADER_LEN;
/* Send ZLP status stage for OUT control transfer */
ctrlreq->len = 0;
ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;
UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl);
return 0;
}
else if (cs == UVC_VS_COMMIT_CONTROL)
{
memcpy(&priv->commit, dataout,
outlen < UVC_PROBE_COMMIT_SIZE ?
outlen : UVC_PROBE_COMMIT_SIZE);
/* Reset wrsem to 0 before starting new stream.
* Stale counts from previous EP_CANCEL callbacks
* would cause NULL wrcontainer dereferences.
*/
while (nxsem_trywait(&priv->wrsem) == OK);
priv->streaming = true;
poll_notify(priv->fds, CONFIG_USBUVC_NPOLLWAITERS,
POLLOUT);
uinfo("UVC streaming committed\n");
/* Send ZLP status stage for OUT control transfer */
ctrlreq->len = 0;
ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;
UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl);
return 0;
}
}
ret = uvc_class_setup(priv, dev, ctrl, ctrlreq);
if (ret > 0)
{
ctrlreq->len = ret;
ret = UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl);
if (ret < 0)
{
uerr("EP_SUBMIT failed: %d\n", ret);
}
return ret;
}
/* ret == 0 means SET_CUR accepted, waiting for data phase.
* ret < 0 means unsupported, will fall through.
*/
if (ret == 0)
{
return ret;
}
}
/* Handle standard requests — descriptor requests are standalone only;
* SET_CONFIGURATION / SET_INTERFACE are needed in both modes.
*/
if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD)
{
switch (ctrl->req)
{
#ifndef CONFIG_USBUVC_COMPOSITE
case USB_REQ_GETDESCRIPTOR:
{
switch (ctrl->value[1])
{
case USB_DESC_TYPE_DEVICE:
{
ret = usbdev_copy_devdesc(ctrlreq->buf,
&g_uvc_devdesc,
dev->speed);
}
break;
case USB_DESC_TYPE_CONFIG:
{
ret = uvc_mkcfgdesc(ctrlreq->buf,
&priv->devinfo,
dev->speed,
ctrl->value[1]);
}
break;
case USB_DESC_TYPE_STRING:
{
ret = uvc_mkstrdesc(ctrl->value[0],
(FAR struct usb_strdesc_s *)ctrlreq->buf);
}
break;
default:
break;
}
}
break;
#endif /* !CONFIG_USBUVC_COMPOSITE */
case USB_REQ_SETCONFIGURATION:
if (value == 1)
{
/* Configure the bulk IN endpoint */
struct usb_epdesc_s epdesc;
epdesc.len = USB_SIZEOF_EPDESC;
epdesc.type = USB_DESC_TYPE_ENDPOINT;
epdesc.addr = USB_EPIN(
USB_EPNO(priv->epbulkin->eplog));
epdesc.attr = USB_EP_ATTR_XFER_BULK;
epdesc.mxpacketsize[0] =
LSBYTE(CONFIG_USBUVC_EPBULKIN_FSSIZE);
epdesc.mxpacketsize[1] =
MSBYTE(CONFIG_USBUVC_EPBULKIN_FSSIZE);
epdesc.interval = 0;
EP_CONFIGURE(priv->epbulkin, &epdesc, true);
priv->config = value;
ret = 0;
}
else if (value == 0)
{
uvc_streaming_stop(priv);
EP_DISABLE(priv->epbulkin);
priv->config = 0;
ret = 0;
}
break;
case USB_REQ_SETINTERFACE:
/* alt=0 means host is stopping the stream */
if ((value & 0xff) == 0 && priv->streaming)
{
uvc_streaming_stop(priv);
}
ret = 0;
break;
case USB_REQ_GETINTERFACE:
ctrlreq->buf[0] = 0;
ctrlreq->len = 1;
ret = UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl);
return ret;
default:
break;
}
}
/* Respond to the setup command if data was returned. On an error
* return value (ret < 0), the USB driver will stall EP0.
*/
if (ret >= 0)
{
ctrlreq->len = len < ret ? len : ret;
ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;
ret = UVC_EP0SUBMIT(driver, dev, ctrlreq, ctrl);
if (ret < 0)
{
uerr("EP_SUBMIT failed: %d\n", ret);
}
}
return ret;
}
static void uvc_streaming_stop(FAR struct uvc_dev_s *priv)
{
/* Called from interrupt context (disconnect/setup) or from
* uvc_write timeout — no mutex!
* Cancel all pending bulk IN requests so callbacks fire immediately.
* EP_CANCEL callbacks will post wrsem for each cancelled request,
* which unblocks uvc_write().
*
* Guard against double-cancel: uvc_write timeout may call this,
* then the USB disconnect ISR calls it again. Calling EP_CANCEL
* on an already-cancelled endpoint can corrupt the request list.
*/
if (!priv->streaming)
{
return;
}
priv->streaming = false;
poll_notify(priv->fds, CONFIG_USBUVC_NPOLLWAITERS, POLLHUP);
if (priv->epbulkin)
{
EP_CANCEL(priv->epbulkin, NULL);
}
}
static void uvc_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
uinfo("UVC disconnect\n");
/* Called from USB interrupt context — do NOT use mutex */
priv->config = 0;
uvc_streaming_stop(priv);
}
/****************************************************************************
* Private Functions — File Operations (/dev/uvc0)
****************************************************************************/
static int uvc_open(FAR struct file *filep)
{
return OK;
}
static int uvc_close(FAR struct file *filep)
{
return OK;
}
/****************************************************************************
* Name: uvc_poll
*
* Description:
* Poll for POLLOUT — reports writable when host is streaming.
*
****************************************************************************/
static int uvc_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
irqstate_t flags;
int ret = OK;
int i;
if (!setup)
{
/* Teardown — clear the slot */
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
if (slot)
{
*slot = NULL;
}
fds->priv = NULL;
return OK;
}
/* Setup — find an available slot */
nxmutex_lock(&priv->lock);
flags = enter_critical_section();
for (i = 0; i < CONFIG_USBUVC_NPOLLWAITERS; i++)
{
if (!priv->fds[i])
{
priv->fds[i] = fds;
fds->priv = &priv->fds[i];
break;
}
}
if (i >= CONFIG_USBUVC_NPOLLWAITERS)
{
ret = -EBUSY;
}
else if (priv->config && priv->streaming)
{
poll_notify(&priv->fds[i], 1, POLLOUT);
}
leave_critical_section(flags);
nxmutex_unlock(&priv->lock);
return ret;
}
/****************************************************************************
* Name: uvc_write
*
* Description:
* Write a complete video frame to the UVC bulk endpoint.
* The driver prepends UVC Payload Headers and splits the frame
* into max-packet-sized USB transfers.
*
* Expected input: raw YUY2 frame data
*
****************************************************************************/
static ssize_t uvc_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen)
{
FAR struct uvc_dev_s *priv = g_uvc_dev;
FAR struct uvc_wrreq_s *wrcontainer;
FAR struct usbdev_req_s *req;
irqstate_t flags;
size_t remaining = buflen;
size_t offset = 0;
size_t payload_max;
size_t chunk;
int ret;
nxmutex_lock(&priv->lock);
if (!priv->epbulkin || !priv->config || !priv->streaming)
{
nxmutex_unlock(&priv->lock);
return -EAGAIN;
}
/* Zero-length write is used to probe readiness */
if (buflen == 0)
{
nxmutex_unlock(&priv->lock);
return 0;
}
payload_max = CONFIG_USBUVC_EPBULKIN_FSSIZE - UVC_PAYLOAD_HEADER_LEN;
while (remaining > 0)
{
/* Get a write request from the pool */
flags = enter_critical_section();
wrcontainer = (FAR struct uvc_wrreq_s *)sq_remfirst(&priv->wrfree);
leave_critical_section(flags);
if (!wrcontainer)
{
/* No request available — wait with timeout.
* If host stops reading (e.g. cheese closed), bulk IN
* requests never complete. Timeout and treat as host-gone.
*
* NOTE: When the host truly disconnects or closes the
* video device, the USB disconnect ISR or a CLEAR_FEATURE
* control transfer will call uvc_streaming_stop()
* immediately — the timeout is only a safety net.
*
* It must be long enough to cover the worst case: host
* sends COMMIT, then spends many seconds on REQBUFS /
* MMAP / QBUF / STREAMON before reading bulk data.
* After an unclean close (e.g. v4l2-ctl killed by
* SIGTERM), the host UVC driver reset can add 5-10s.
* 30 seconds is generous enough to never false-trigger.
*/
nxmutex_unlock(&priv->lock);
ret = nxsem_tickwait(&priv->wrsem, 30 * TICK_PER_SEC);
nxmutex_lock(&priv->lock);
if (ret == -ETIMEDOUT)
{
uwarn("UVC write timeout — host not reading\n");
uvc_streaming_stop(priv);
nxmutex_unlock(&priv->lock);
return -EAGAIN;
}
/* Re-check state after re-acquiring lock.
* streaming_stop() cancels EP requests which unblocks us.
*/
if (!priv->streaming || !priv->config || !priv->epbulkin)
{
nxmutex_unlock(&priv->lock);
return -EAGAIN;
}
/* wrcontainer might still be NULL if wrsem had stale count */
continue;
}
req = wrcontainer->req;
/* Build UVC Payload Header */
chunk = remaining > payload_max ? payload_max : remaining;
req->buf[0] = UVC_PAYLOAD_HEADER_LEN; /* bHeaderLength */
req->buf[1] = priv->fid & UVC_STREAM_FID;
if (remaining <= payload_max)
{
/* Last packet of this frame */
req->buf[1] |= UVC_STREAM_EOF;
}
/* Copy video data after header */
memcpy(req->buf + UVC_PAYLOAD_HEADER_LEN, buffer + offset, chunk);
req->len = chunk + UVC_PAYLOAD_HEADER_LEN;
ret = EP_SUBMIT(priv->epbulkin, req);
if (ret < 0)
{
/* Return request to pool */
flags = enter_critical_section();
sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->wrfree);
leave_critical_section(flags);
if (ret == -ESHUTDOWN)
{
uwarn("UVC EP shutdown — stopping stream\n");
priv->streaming = false;
poll_notify(priv->fds, CONFIG_USBUVC_NPOLLWAITERS,
POLLHUP);
nxmutex_unlock(&priv->lock);
return -EAGAIN;
}
uerr("EP_SUBMIT failed: %d\n", ret);
nxmutex_unlock(&priv->lock);
return ret;
}
offset += chunk;
remaining -= chunk;
}
/* Toggle FID for next frame */
priv->fid ^= 1;
nxmutex_unlock(&priv->lock);
return buflen;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: usbdev_uvc_classobject
*
* Description:
* Create a UVC class driver instance. Used by both standalone init
* and composite framework.
*
****************************************************************************/
int usbdev_uvc_classobject(int minor,
FAR struct usbdev_devinfo_s *devinfo,
FAR const struct uvc_params_s *params,
FAR struct usbdevclass_driver_s **classdev)
{
FAR struct uvc_dev_s *priv;
FAR struct usbdevclass_driver_s *drvr;
int ret;
/* Allocate device struct + class driver struct */
priv = kmm_zalloc(sizeof(struct uvc_dev_s) +
sizeof(struct usbdevclass_driver_s));
if (!priv)
{
return -ENOMEM;
}
/* Store runtime video parameters */
if (params)
{
priv->width = params->width;
priv->height = params->height;
priv->fps = params->fps;
}
else
{
priv->width = USBUVC_DEF_WIDTH;
priv->height = USBUVC_DEF_HEIGHT;
priv->fps = USBUVC_DEF_FPS;
}
priv->frame_size = (uint32_t)priv->width * priv->height * USBUVC_BPP;
priv->frame_interval = 10000000 / priv->fps; /* 100ns units */
priv->bitrate = priv->frame_size * 8 * priv->fps;
/* Save devinfo — interface/string/endpoint bases from caller */
memcpy(&priv->devinfo, devinfo, sizeof(struct usbdev_devinfo_s));
g_uvc_dev = priv;
nxmutex_init(&priv->lock);
nxsem_init(&priv->wrsem, 0, 0);
/* Set up the class driver structure right after priv */
drvr = (FAR struct usbdevclass_driver_s *)(priv + 1);
drvr->ops = &g_uvc_driverops;
drvr->speed = USB_SPEED_FULL;
/* Register the character device */
ret = register_driver(USBUVC_CHARDEV_PATH, &g_uvc_fops, 0666, priv);
if (ret < 0)
{
uerr("register_driver failed: %d\n", ret);
kmm_free(priv);
g_uvc_dev = NULL;
return ret;
}
uinfo("UVC gadget initialized: %s\n", USBUVC_CHARDEV_PATH);
*classdev = drvr;
return OK;
}
/****************************************************************************
* Name: usbdev_uvc_classuninitialize
*
* Description:
* Uninitialize a UVC class driver instance.
*
****************************************************************************/
void usbdev_uvc_classuninitialize(
FAR struct usbdevclass_driver_s *classdev)
{
FAR struct uvc_dev_s *priv;
if (!classdev)
{
return;
}
/* priv is right before classdev in the allocation */
priv = (FAR struct uvc_dev_s *)classdev - 1;
unregister_driver(USBUVC_CHARDEV_PATH);
nxmutex_destroy(&priv->lock);
nxsem_destroy(&priv->wrsem);
kmm_free(priv);
if (g_uvc_dev == priv)
{
g_uvc_dev = NULL;
}
}
/****************************************************************************
* Name: usbdev_uvc_initialize
*
* Description:
* Standalone mode initialization — creates the class driver and
* registers it directly with the USB device controller.
*
****************************************************************************/
#ifndef CONFIG_USBUVC_COMPOSITE
FAR void *usbdev_uvc_initialize(FAR const struct uvc_params_s *params)
{
FAR struct usbdevclass_driver_s *classdev = NULL;
struct usbdev_devinfo_s devinfo;
int ret;
memset(&devinfo, 0, sizeof(devinfo));
devinfo.ninterfaces = USBUVC_NINTERFACES;
devinfo.nstrings = USBUVC_NSTDSTRIDS;
devinfo.nendpoints = USBUVC_NUM_EPS;
devinfo.epno[USBUVC_EP_BULKIN_IDX] = CONFIG_USBUVC_EPBULKIN;
ret = usbdev_uvc_classobject(0, &devinfo, params, &classdev);
if (ret < 0)
{
return NULL;
}
ret = usbdev_register(classdev);
if (ret < 0)
{
uerr("usbdev_register failed: %d\n", ret);
usbdev_uvc_classuninitialize(classdev);
return NULL;
}
/* Return priv as handle (priv is right before classdev) */
return (FAR void *)((FAR struct uvc_dev_s *)classdev - 1);
}
/****************************************************************************
* Name: usbdev_uvc_uninitialize
****************************************************************************/
void usbdev_uvc_uninitialize(FAR void *handle)
{
FAR struct uvc_dev_s *priv = handle;
FAR struct usbdevclass_driver_s *drvr;
if (!priv)
{
return;
}
drvr = (FAR struct usbdevclass_driver_s *)(priv + 1);
usbdev_unregister(drvr);
usbdev_uvc_classuninitialize(drvr);
}
#endif /* !CONFIG_USBUVC_COMPOSITE */
/****************************************************************************
* Name: usbdev_uvc_get_composite_devdesc
*
* Description:
* Fill in a composite_devdesc_s for the UVC gadget.
* Board code must set classobject/uninitialize, ifnobase, strbase,
* and epno[] before passing to composite_initialize().
*
****************************************************************************/
#ifdef CONFIG_USBDEV_COMPOSITE
void usbdev_uvc_get_composite_devdesc(
FAR struct composite_devdesc_s *dev)
{
memset(dev, 0, sizeof(struct composite_devdesc_s));
dev->mkconfdesc = uvc_mkcfgdesc;
dev->mkstrdesc = uvc_mkstrdesc;
/* classobject/uninitialize are left NULL — board code must wrap
* usbdev_uvc_classobject() to pass uvc_params_s.
*/
dev->nconfigs = USBUVC_NCONFIGS;
dev->configid = 1;
dev->cfgdescsize = uvc_mkcfgdesc(NULL, NULL,
USB_SPEED_UNKNOWN, 0);
dev->devinfo.ninterfaces = USBUVC_NINTERFACES;
dev->devinfo.nstrings = USBUVC_NSTRIDS;
dev->devinfo.nendpoints = USBUVC_NUM_EPS;
dev->devinfo.epinfos = g_uvc_epinfos;
dev->devinfo.name = USBUVC_CHARDEV_PATH;
}
#endif