diff --git a/examples/device/video_capture/Makefile b/examples/device/video_capture/Makefile new file mode 100644 index 000000000..69b633fea --- /dev/null +++ b/examples/device/video_capture/Makefile @@ -0,0 +1,12 @@ +include ../../../tools/top.mk +include ../../make.mk + +INC += \ + src \ + $(TOP)/hw \ + +# Example source +EXAMPLE_SOURCE += $(wildcard src/*.c) +SRC_C += $(addprefix $(CURRENT_PATH)/, $(EXAMPLE_SOURCE)) + +include ../../rules.mk diff --git a/examples/device/video_capture/src/main.c b/examples/device/video_capture/src/main.c new file mode 100644 index 000000000..098cd2f8d --- /dev/null +++ b/examples/device/video_capture/src/main.c @@ -0,0 +1,123 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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 "bsp/board.h" +#include "tusb.h" + +//--------------------------------------------------------------------+ +// MACRO CONSTANT TYPEDEF PROTYPES +//--------------------------------------------------------------------+ + +/* Blink pattern + * - 250 ms : device not mounted + * - 1000 ms : device mounted + * - 2500 ms : device is suspended + */ +enum { + BLINK_NOT_MOUNTED = 250, + BLINK_MOUNTED = 1000, + BLINK_SUSPENDED = 2500, +}; + +static uint32_t blink_interval_ms = BLINK_NOT_MOUNTED; + +void led_blinking_task(void); +void video_task(void); + +/*------------- MAIN -------------*/ +int main(void) +{ + board_init(); + tusb_init(); + + while (1) + { + tud_task(); // tinyusb device task + led_blinking_task(); + + video_task(); + } + + return 0; +} + +//--------------------------------------------------------------------+ +// Device callbacks +//--------------------------------------------------------------------+ + +// Invoked when device is mounted +void tud_mount_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + +// Invoked when device is unmounted +void tud_umount_cb(void) +{ + blink_interval_ms = BLINK_NOT_MOUNTED; +} + +// Invoked when usb bus is suspended +// remote_wakeup_en : if host allow us to perform remote wakeup +// Within 7ms, device must draw an average of current less than 2.5 mA from bus +void tud_suspend_cb(bool remote_wakeup_en) +{ + (void) remote_wakeup_en; + blink_interval_ms = BLINK_SUSPENDED; +} + +// Invoked when usb bus is resumed +void tud_resume_cb(void) +{ + blink_interval_ms = BLINK_MOUNTED; +} + + +//--------------------------------------------------------------------+ +// USB Video +//--------------------------------------------------------------------+ +void video_task(void) +{ +} + +//--------------------------------------------------------------------+ +// BLINKING TASK +//--------------------------------------------------------------------+ +void led_blinking_task(void) +{ + static uint32_t start_ms = 0; + static bool led_state = false; + + // Blink every interval ms + if ( board_millis() - start_ms < blink_interval_ms) return; // not enough time + start_ms += blink_interval_ms; + + board_led_write(led_state); + led_state = 1 - led_state; // toggle +} diff --git a/examples/device/video_capture/src/tusb_config.h b/examples/device/video_capture/src/tusb_config.h new file mode 100644 index 000000000..ae2387357 --- /dev/null +++ b/examples/device/video_capture/src/tusb_config.h @@ -0,0 +1,106 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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. + * + */ + +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +//-------------------------------------------------------------------- +// COMMON CONFIGURATION +//-------------------------------------------------------------------- + +// RHPort number used for device can be defined by board.mk, default to port 0 +#ifndef BOARD_DEVICE_RHPORT_NUM + #define BOARD_DEVICE_RHPORT_NUM 0 +#endif + +// RHPort max operational speed can defined by board.mk +// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed +#ifndef BOARD_DEVICE_RHPORT_SPEED + #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ + CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56) + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED + #else + #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED + #endif +#endif + +// Device mode with rhport and speed defined by board.mk +#if BOARD_DEVICE_RHPORT_NUM == 0 + #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#elif BOARD_DEVICE_RHPORT_NUM == 1 + #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) +#else + #error "Incorrect RHPort configuration" +#endif + +// This example doesn't use an RTOS +#ifndef CFG_TUSB_OS +#define CFG_TUSB_OS OPT_OS_NONE +#endif + +// CFG_TUSB_DEBUG is defined by compiler in DEBUG build +// #define CFG_TUSB_DEBUG 0 + +/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. + * Tinyusb use follows macros to declare transferring memory so that they can be put + * into those specific section. + * e.g + * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) + * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) + */ +#ifndef CFG_TUSB_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +//-------------------------------------------------------------------- +// DEVICE CONFIGURATION +//-------------------------------------------------------------------- + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +//------------- CLASS -------------// +#define CFG_TUD_CDC 0 +#define CFG_TUD_MSC 0 +#define CFG_TUD_HID 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 0 +#define CFG_TUD_VIDEO 1 +#define CFG_TUD_VENDOR 0 + +#ifdef __cplusplus + } +#endif + +#endif /* _TUSB_CONFIG_H_ */ diff --git a/examples/device/video_capture/src/usb_descriptors.c b/examples/device/video_capture/src/usb_descriptors.c new file mode 100644 index 000000000..df8c41721 --- /dev/null +++ b/examples/device/video_capture/src/usb_descriptors.c @@ -0,0 +1,172 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * 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 "tusb.h" +#include "usb_descriptors.h" + +/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. + * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. + * + * Auto ProductID layout's Bitmap: + * [MSB] VIDEO | AUDIO | MIDI | HID | MSC | CDC [LSB] + */ +#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) +#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(AUDIO, 4) | _PID_MAP(VIDEO, 5) | _PID_MAP(VENDOR, 6) ) + +//--------------------------------------------------------------------+ +// Device Descriptors +//--------------------------------------------------------------------+ +tusb_desc_device_t const desc_device = +{ + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + + // Use Interface Association Descriptor (IAD) for CDC + // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCafe, + .idProduct = USB_PID, + .bcdDevice = 0x0100, + + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + + .bNumConfigurations = 0x01 +}; + +// Invoked when received GET DEVICE DESCRIPTOR +// Application return pointer to descriptor +uint8_t const * tud_descriptor_device_cb(void) +{ + return (uint8_t const *) &desc_device; +} + +//--------------------------------------------------------------------+ +// Configuration Descriptor +//--------------------------------------------------------------------+ + +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_VIDEO_CAPTURE_DESC_LEN) + +#if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX + // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number + // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... + #define EPNUM_VIDEO_IN 0x83 + +#elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_CXD56 + // SAMG doesn't support a same endpoint number with different direction IN and OUT + // e.g EP1 OUT & EP1 IN cannot exist together + #define EPNUM_VIDEO_IN 0x81 + +#elif CFG_TUSB_MCU == OPT_MCU_CXD56 + // CXD56 doesn't support a same endpoint number with different direction IN and OUT + // e.g EP1 OUT & EP1 IN cannot exist together + // CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and direction (IN/OUT) by its number + // 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), 5 Bulk (OUT), 6 In (IN) + #define EPNUM_VIDEO_IN 0x81 + +#else + #define EPNUM_VIDEO_IN 0x81 + +#endif + +uint8_t const desc_fs_configuration[] = +{ + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + // IAD for Video Control + TUD_VIDEO_CAPTURE_DESCRIPTOR(4, EPNUM_VIDEO_IN, 128, 96, 15) +}; + +// Invoked when received GET CONFIGURATION DESCRIPTOR +// Application return pointer to descriptor +// Descriptor contents must exist long enough for transfer to complete +uint8_t const * tud_descriptor_configuration_cb(uint8_t index) +{ + (void) index; // for multiple configurations + + return desc_fs_configuration; +} + +//--------------------------------------------------------------------+ +// String Descriptors +//--------------------------------------------------------------------+ + +// array of pointer to string descriptors +char const* string_desc_arr [] = +{ + (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) + "TinyUSB", // 1: Manufacturer + "TinyUSB Device", // 2: Product + "123456", // 3: Serials, should use chip ID + "TinyUSB UVC", // 4: UVC Interface +}; + +static uint16_t _desc_str[32]; + +// Invoked when received GET STRING DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + (void) langid; + + uint8_t chr_count; + + if ( index == 0) + { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + chr_count = 1; + }else + { + // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. + // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors + + if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; + + const char* str = string_desc_arr[index]; + + // Cap at max char + chr_count = strlen(str); + if ( chr_count > 31 ) chr_count = 31; + + // Convert ASCII string into UTF-16 + for(uint8_t i=0; ibeg + *ofs); +} + + /** Find the first descriptor with the specified descriptor type. * * @param[in] beg The head of descriptor byte array. @@ -188,6 +196,10 @@ static void const* _find_desc_entity(tusb_desc_vc_itf_t const *vc, unsigned enti return end; } +/** Close current video control interface. + * + * @param[in,out] self The context. + * @param[in] altnum The target alternate setting number. */ static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self) { tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); @@ -206,6 +218,10 @@ static bool _close_vc_itf(uint8_t rhport, videod_interface_t *self) return true; } +/** Set the specified alternate setting to own video control interface. + * + * @param[in,out] self The context. + * @param[in] altnum The target alternate setting number. */ static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, unsigned altnum) { void const *beg = self->beg; @@ -238,48 +254,6 @@ static bool _open_vc_itf(uint8_t rhport, videod_interface_t *self, unsigned altn return true; } -/** Set the specified alternate setting to own video control interface. - * - * @param[in,out] self The context. - * @param[in] altnum The target alternate setting number. */ -static bool _set_vc_itf(uint8_t rhport, videod_interface_t *self, unsigned altnum) -{ - void const *beg = self->beg; - void const *end = beg + self->len; - /* The head descriptor is a video control interface descriptor. */ - unsigned itfnum = ((tusb_desc_interface_t const *)beg)->bInterfaceNumber; - void const *cur = _find_desc_itf(beg, end, itfnum, altnum); - TU_VERIFY(cur < end); - - tusb_desc_vc_itf_t const *vc = (tusb_desc_vc_itf_t const *)cur; - /* Support for up to 2 streaming interfaces only. */ - TU_VERIFY(vc->ctl.bInCollection < 3); - - /* Close the previous notification endpoint if it is opened */ - if (self->ep_notif) { - usbd_edpt_close(rhport, self->ep_notif); - self->ep_notif = 0; - } - /* Advance to the next descriptor after the class-specific VC interface header descriptor. */ - cur += vc->std.bLength + vc->ctl.bLength; - /* Update to point the end of the video control interface descriptor. */ - end = cur + vc->ctl.wTotalLength; - /* Open the notification endpoint if it exist. */ - if (vc->std.bNumEndpoints) { - /* Support for 1 endpoint only. */ - TU_VERIFY(1 == vc->std.bNumEndpoints); - /* Find the notification endpoint descriptor. */ - cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); - TU_VERIFY(cur < end); - tusb_desc_endpoint_t const *notif = (tusb_desc_endpoint_t const *)cur; - /* Open the notification endpoint */ - TU_ASSERT(usbd_edpt_open(rhport, notif)); - self->ep_notif = notif->bEndpointAddress; - } - self->ofs[0] = (void const*)vc - beg; - return true; -} - /** Set the specified alternate setting to own video control interface. * * @param[in,out] self The context. @@ -293,7 +267,7 @@ static bool _close_vs_itf(uint8_t rhport, videod_interface_t *self, unsigned itf void const *cur = (void const*)vs + vs->std.bLength + vs->stm.bLength; /* The end of the video streaming interface descriptor. */ void const *end = cur + vs->stm.wTotalLength; - if (unsigned i = 0; i < vs->std.bNumEndpoints; ++i) { + for (unsigned i = 0; i < vs->std.bNumEndpoints; ++i) { cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); TU_ASSERT(cur < end); tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const *)cur; @@ -345,123 +319,28 @@ static bool _open_vs_itf(uint8_t rhport, videod_interface_t *self, unsigned itfn return true; } -/** Set the specified alternate setting to own video control interface. - * - * @param[in,out] self The context. - * @param[in] itfnum The target interface number. - * @param[in] altnum The target alternate setting number. */ -static bool _set_vs_itf(uint8_t rhport, videod_interface_t *self, unsigned itfnum, unsigned altnum) -{ - unsigned i; - tusb_desc_vc_itf_t const *vc = _get_desc_vc(self); - void const *end = self->beg + self->len; - /* Set the end of the video control interface descriptor. */ - void const *cur = (void const*)vc + vc->std.bLength + vc->ctl.bLength + vc->ctl.wTotalLength; - - /* Check itfnum is valid */ - unsigned bInCollection = vc->ctl.bInCollection; - for (i = 0; (i < bInCollection) && (vc->ctl.baInterfaceNr[i] != itfnum); ++i) ; - TU_VERIFY(i < bInCollection); - - cur = _find_desc_itf(cur, end, itfnum, altnum); - TU_VERIFY(cur < end); - tusb_desc_vs_itf_t const *vs = (tusb_desc_vs_itf_t const*)cur; - /* Advance to the next descriptor after the class-specific VS interface header descriptor. */ - cur += vs->std.bLength + vs->stm.bLength; - /* Update to point the end of the video control interface descriptor. */ - end = cur + vs->stm.wTotalLength; - - switch (vs->stm.bDescriptorSubType) { - default: return false; - case VIDEO_CS_VS_INTERFACE_INPUT_HEADER: - /* Support for up to 2 endpoint only. */ - TU_VERIFY(vc->std.bNumEndpoints < 3); - if (self->ep_sti) { - usbd_edpt_close(rhport, self->ep_sti); - self->ep_sti = 0; - } - if (self->ep_in) { - usbd_edpt_close(rhport, self->ep_in); - self->ep_in = 0; - } - if (i = 0; i < vs->std.bNumEndpoints; ++i) { - cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); - TU_VERIFY(cur < end); - tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const *)cur; - if (vs->stm.bEndpointAddress == ep->bEndpointAddress) { - /* video input endpoint */ - TU_ASSERT(!self->ep_in); - TU_ASSERT(usbd_edpt_open(rhport, ep)); - self->ep_in = ep->bEndpointAddress; - } else { - /* still image input endpoint */ - TU_ASSERT(!self->ep_sti); - TU_ASSERT(usbd_edpt_open(rhport, ep)); - self->ep_sti = ep->bEndpointAddress; - } - cur += tu_desc_len(cur); - } - break; - case VIDEO_CS_VS_INTERFACE_OUTPUT_HEADER: - /* Support for up to 1 endpoint only. */ - TU_VERIFY(vc->std.bNumEndpoints < 2); - if (self->ep_out) { - usbd_edpt_close(rhport, self->ep_out); - self->ep_out = 0; - } - if (vs->std.bNumEndpoints) { - cur = _find_desc(cur, end, TUSB_DESC_ENDPOINT); - TU_VERIFY(cur < end); - tusb_desc_endpoint_t const *ep = (tusb_desc_endpoint_t const *)cur; - if (vs->stm.bEndpointAddress == ep->bEndpointAddress) { - /* video output endpoint */ - TU_ASSERT(usbd_edpt_open(rhport, ep)); - self->ep_out = ep->bEndpointAddress; - } - } - break; - } - for (unsigned i = 1; i < sizeof(self->ofs)/sizeof(ofs[0]); ++i) { - if (!self->ofs[i]) { - return true; - } - tusb_desc_interface_t const* itf = (tusb_desc_interface_t const*)(beg + ofs[i]); - if (itfnum == itf->bInterfaceNumber) return itf; - } - return NULL; - for (i = 0; i < sizeof(self->vs)/sizeof(self->vs[0]); ++i) { - if (!self->vs[i] || self->vs[i].stm.bInterfaceNumber == vs->stm.bInterfaceNumber) { - self->ofs[i] = (void const*)vs - self->beg; - return true; - } - } - return false; -} - -static int handle_video_ctl_std_req_get_itf(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, unsigned itf) -{ - if (stage != CONTROL_STAGE_SETUP) - return VIDEO_NO_ERROR; - videod_interface_t *self = &_videod_itf[itf]; - TU_VERIFY(1 == request->wLength, VIDEO_UNKNOWN); - tusb_desc_interface_t const *p = _get_desc_cur_itf(self, itfnum); - if (!p) return VIDEO_UNKNOWN; - if (tud_control_xfer(rhport, request, &p->bAlternateSettings, sizeof(p->bAlternateSettings))) - return VIDEO_NO_ERROR; - return VIDEO_UNKNOWN; -} - /** Handle a standard request to the video control interface. */ static int handle_video_ctl_std_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, unsigned itf) { - switch (p_request->bRequest) { + switch (request->bRequest) { case TUSB_REQ_GET_INTERFACE: - handle_video_ctl_std_req_get_itf(rhport, stage, request, itf); - case TUSB_REQ_SET_INTERFACE: if (stage != CONTROL_STAGE_SETUP) return VIDEO_NO_ERROR; - if (_set_vc_itf(rhport, &_videod_itf[itf], request->wValue)) + TU_VERIFY(1 == request->wLength, VIDEO_UNKNOWN); + tusb_desc_vc_itf_t const *vc = _get_desc_vc(&_videod_itf[itf]); + if (!vc) return VIDEO_UNKNOWN; + if (tud_control_xfer(rhport, request, + (void*)&vc->std.bAlternateSetting, + sizeof(vc->std.bAlternateSetting))) return VIDEO_NO_ERROR; return VIDEO_UNKNOWN; + case TUSB_REQ_SET_INTERFACE: + if (stage != CONTROL_STAGE_SETUP) return VIDEO_NO_ERROR; + TU_VERIFY(0 == request->wLength, VIDEO_UNKNOWN); + if (!_close_vc_itf(rhport, &_videod_itf[itf])) + return VIDEO_UNKNOWN; + if (!_open_vc_itf(rhport, &_videod_itf[itf], request->wValue)) + return VIDEO_UNKNOWN; + return VIDEO_NO_ERROR; default: /* Unknown/Unsupported request */ TU_BREAKPOINT(); return VIDEO_INVALID_REQUEST; @@ -470,11 +349,11 @@ static int handle_video_ctl_std_req(uint8_t rhport, uint8_t stage, tusb_control_ static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, unsigned itf) { - videod_interface_t *self = &_videod_itf[i]; + videod_interface_t *self = &_videod_itf[itf]; /* 4.2.1 Interface Control Request */ switch (TU_U16_HIGH(request->wValue)) { case VIDEO_VC_CTL_VIDEO_POWER_MODE: - switch (p_request->bRequest) { + switch (request->bRequest) { case VIDEO_REQUEST_SET_CUR: if (stage == CONTROL_STAGE_SETUP) { TU_LOG2(" Set Power Mode\r\n"); @@ -482,7 +361,7 @@ static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, tusb_control_r if (!tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode))) return VIDEO_UNKNOWN; } else if (stage == CONTROL_STAGE_ACK) { - if (tud_video_power_mode_cb) return tud_video_power_mode_cb(itf, &self->power_mode); + if (tud_video_power_mode_cb) return tud_video_power_mode_cb(itf, self->power_mode); } return VIDEO_NO_ERROR; case VIDEO_REQUEST_GET_CUR: @@ -497,7 +376,7 @@ static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, tusb_control_r if (stage == CONTROL_STAGE_SETUP) { TU_LOG2(" Get Info Power Mode\r\n"); TU_VERIFY(1 == request->wLength, VIDEO_UNKNOWN); - if (!tud_control_xfer(rhport, request, &_cap_get_set, sizeof(_cap_get_set))) + if (!tud_control_xfer(rhport, request, (uint8_t*)&_cap_get_set, sizeof(_cap_get_set))) return VIDEO_UNKNOWN; } return VIDEO_NO_ERROR; @@ -505,7 +384,7 @@ static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, tusb_control_r } break; case VIDEO_VC_CTL_REQUEST_ERROR_CODE: - switch (p_request->bRequest) { + switch (request->bRequest) { case VIDEO_REQUEST_GET_CUR: if (stage == CONTROL_STAGE_SETUP) { TU_LOG2(" Get Error Code\r\n"); @@ -516,7 +395,7 @@ static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, tusb_control_r case VIDEO_REQUEST_GET_INFO: if (stage == CONTROL_STAGE_SETUP) { TU_LOG2(" Get Info Error Code\r\n"); - if (tud_control_xfer(rhport, request, &_cap_get, sizeof(_cap_get))) + if (tud_control_xfer(rhport, request, (uint8_t*)&_cap_get, sizeof(_cap_get))) return VIDEO_UNKNOWN; } return VIDEO_NO_ERROR; @@ -532,14 +411,17 @@ static int handle_video_ctl_cs_req(uint8_t rhport, uint8_t stage, tusb_control_r static int handle_video_ctl_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, unsigned itf) { - switch (p_request->bmRequestType_bit.type) { + unsigned entity_id; + switch (request->bmRequestType_bit.type) { case TUSB_REQ_TYPE_STANDARD: return handle_video_ctl_std_req(rhport, stage, request, itf); case TUSB_REQ_TYPE_CLASS: - if (!TU_U16_HIGH(request->wIndex)) { + entity_id = TU_U16_HIGH(request->wIndex); + if (!entity_id) { return handle_video_ctl_cs_req(rhport, stage, request, itf); } else { - /* TODO: */ + if (!_find_desc_entity(_get_desc_vc(&_videod_itf[itf]), entity_id)) + return VIDEO_INVALID_REQUEST; return VIDEO_INVALID_REQUEST; } default: @@ -549,16 +431,27 @@ static int handle_video_ctl_req(uint8_t rhport, uint8_t stage, tusb_control_requ static int handle_video_stm_std_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, unsigned itf) { - switch (p_request->bRequest) { + unsigned itfnum; + switch (request->bRequest) { case TUSB_REQ_GET_INTERFACE: - handle_video_ctl_std_req_get_itf(rhport, stage, request, itf); - case TUSB_REQ_SET_INTERFACE: - videod_interface_t *self = &_videod_itf[itf]; - unsigned itfnum = tu_u16_low(p_request->wIndex); if (stage != CONTROL_STAGE_SETUP) return VIDEO_NO_ERROR; - if (_set_vs_itf(rhport, self, itfnum, request->wValue)) + TU_VERIFY(1 == request->wLength, VIDEO_UNKNOWN); + itfnum = tu_u16_low(request->wIndex); + tusb_desc_vs_itf_t const *vs = _get_desc_vs(&_videod_itf[itf], itfnum); + if (!vs) return VIDEO_UNKNOWN; + if (tud_control_xfer(rhport, request, + (void*)&vs->std.bAlternateSetting, + sizeof(vs->std.bAlternateSetting))) return VIDEO_NO_ERROR; return VIDEO_UNKNOWN; + case TUSB_REQ_SET_INTERFACE: + if (stage != CONTROL_STAGE_SETUP) return VIDEO_NO_ERROR; + itfnum = tu_u16_low(request->wIndex); + if (!_close_vs_itf(rhport, &_videod_itf[itf], itfnum)) + return VIDEO_UNKNOWN; + if (!_open_vs_itf(rhport, &_videod_itf[itf], itfnum, request->wValue)) + return VIDEO_UNKNOWN; + return VIDEO_NO_ERROR; default: /* Unknown/Unsupported request */ TU_BREAKPOINT(); return VIDEO_INVALID_REQUEST; @@ -567,56 +460,52 @@ static int handle_video_stm_std_req(uint8_t rhport, uint8_t stage, tusb_control_ static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, unsigned itf) { - videod_interface_t *self = &_videod_itf[i]; + // videod_interface_t *self = &_videod_itf[itf]; + (void)rhport; (void)stage; (void)itf; /* 4.2.1 Interface Control Request */ switch (TU_U16_HIGH(request->wValue)) { case VIDEO_VS_CTL_PROBE: - switch (p_request->bRequest) { + TU_LOG2(" Probe "); + switch (request->bRequest) { case VIDEO_REQUEST_SET_CUR: - if (stage == CONTROL_STAGE_SETUP) { - TU_LOG2(" Set Power Mode\r\n"); - TU_VERIFY(1 == request->wLength, VIDEO_UNKNOWN); - if (!tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode))) - return VIDEO_UNKNOWN; - } else if (stage == CONTROL_STAGE_ACK) { - if (tud_video_power_mode_cb) return tud_video_power_mode_cb(itf, &self->power_mode); - } - return VIDEO_NO_ERROR; + TU_LOG2("Set\r\n"); + return VIDEO_UNKNOWN; case VIDEO_REQUEST_GET_CUR: - if (stage == CONTROL_STAGE_SETUP) { - TU_LOG2(" Get Power Mode\r\n"); - TU_VERIFY(1 == request->wLength, VIDEO_UNKNOWN); - if (!tud_control_xfer(rhport, request, &self->power_mode, sizeof(self->power_mode))) - return VIDEO_UNKNOWN; - } - return VIDEO_NO_ERROR; + TU_LOG2("Get\r\n"); + return VIDEO_UNKNOWN; + case VIDEO_REQUEST_GET_MIN: + TU_LOG2("Get Min\r\n"); + return VIDEO_UNKNOWN; + case VIDEO_REQUEST_GET_MAX: + TU_LOG2("Get Man\r\n"); + return VIDEO_UNKNOWN; + case VIDEO_REQUEST_GET_RES: + TU_LOG2("Get Res\r\n"); + return VIDEO_UNKNOWN; + case VIDEO_REQUEST_GET_DEF: + TU_LOG2("Get Def\r\n"); + return VIDEO_UNKNOWN; + case VIDEO_REQUEST_GET_LEN: + TU_LOG2("Get Len\r\n"); + return VIDEO_UNKNOWN; case VIDEO_REQUEST_GET_INFO: - if (stage == CONTROL_STAGE_SETUP) { - TU_LOG2(" Get Info Power Mode\r\n"); - TU_VERIFY(1 == request->wLength, VIDEO_UNKNOWN); - if (!tud_control_xfer(rhport, request, &_cap_get_set, sizeof(_cap_get_set))) - return VIDEO_UNKNOWN; - } - return VIDEO_NO_ERROR; + TU_LOG2("Get Info\r\n"); + return VIDEO_UNKNOWN; default: break; } break; case VIDEO_VS_CTL_COMMIT: - switch (p_request->bRequest) { + TU_LOG2(" Commit "); + switch (request->bRequest) { + case VIDEO_REQUEST_SET_CUR: + TU_LOG2("Set\r\n"); + return VIDEO_UNKNOWN; case VIDEO_REQUEST_GET_CUR: - if (stage == CONTROL_STAGE_SETUP) { - TU_LOG2(" Get Error Code\r\n"); - if (!tud_control_xfer(rhport, request, &self->error_code, sizeof(self->error_code))) - return VIDEO_UNKNOWN; - } - return VIDEO_NO_ERROR; + TU_LOG2("Get\r\n"); + return VIDEO_UNKNOWN; case VIDEO_REQUEST_GET_INFO: - if (stage == CONTROL_STAGE_SETUP) { - TU_LOG2(" Get Info Error Code\r\n"); - if (tud_control_xfer(rhport, request, &_cap_get, sizeof(_cap_get))) - return VIDEO_UNKNOWN; - } - return VIDEO_NO_ERROR; + TU_LOG2("Get Info\r\n"); + return VIDEO_UNKNOWN; default: break; } break; @@ -629,7 +518,7 @@ static int handle_video_stm_cs_req(uint8_t rhport, uint8_t stage, tusb_control_r static int handle_video_stm_req(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request, unsigned itf) { - switch (p_request->bmRequestType_bit.type) { + switch (request->bmRequestType_bit.type) { case TUSB_REQ_TYPE_STANDARD: return handle_video_stm_std_req(rhport, stage, request, itf); case TUSB_REQ_TYPE_CLASS: @@ -642,40 +531,14 @@ static int handle_video_stm_req(uint8_t rhport, uint8_t stage, tusb_control_requ return VIDEO_UNKNOWN; } -static void _prep_out_transaction(cdcd_interface_t* p_cdc) -{ - uint8_t const rhport = TUD_OPT_RHPORT; - uint16_t available = tu_fifo_remaining(&p_cdc->rx_ff); - - // Prepare for incoming data but only allow what we can store in the ring buffer. - // TODO Actually we can still carry out the transfer, keeping count of received bytes - // and slowly move it to the FIFO when read(). - // This pre-check reduces endpoint claiming - TU_VERIFY(available >= sizeof(p_cdc->epout_buf), ); - - // claim endpoint - TU_VERIFY(usbd_edpt_claim(rhport, p_cdc->ep_out), ); - - // fifo can be changed before endpoint is claimed - available = tu_fifo_remaining(&p_cdc->rx_ff); - - if ( available >= sizeof(p_cdc->epout_buf) ) - { - usbd_edpt_xfer(rhport, p_cdc->ep_out, p_cdc->epout_buf, sizeof(p_cdc->epout_buf)); - }else - { - // Release endpoint since we don't make any transfer - usbd_edpt_release(rhport, p_cdc->ep_out); - } -} - //--------------------------------------------------------------------+ // APPLICATION API //--------------------------------------------------------------------+ bool tud_video_n_connected(uint8_t itf) { + (void)itf; // DTR (bit 0) active is considered as connected - return tud_ready() && tu_bit_test(_cdcd_itf[itf].line_state, 0); + return tud_ready(); } //--------------------------------------------------------------------+ @@ -695,7 +558,7 @@ void videod_init(void) for (unsigned i = 0; i < CFG_TUD_VIDEO; ++i) { - videod_interface_t* p_video = &_videod_itf[i]; + // videod_interface_t* p_video = &_videod_itf[i]; // TODO } @@ -716,14 +579,14 @@ void videod_reset(uint8_t rhport) uint16_t videod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len) { - TU_VERIFY(TUSB_CLASS_VIDEO == itf_desc->bInterfaceClass && - VIDEO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass && - VIDEO_INT_PROTOCOL_CODE_15 == itf_desc->bFunctionProtool, 0); + TU_VERIFY((TUSB_CLASS_VIDEO == itf_desc->bInterfaceClass) && + (VIDEO_SUBCLASS_CONTROL == itf_desc->bInterfaceSubClass) && + (VIDEO_INT_PROTOCOL_CODE_15 == itf_desc->bInterfaceProtocol), 0); /* Find available interface */ videod_interface_t *self = NULL; for (unsigned i = 0; i < CFG_TUD_VIDEO; ++i) { - if (!_videod_itf[i].vc) { + if (!_videod_itf[i].beg) { self = &_videod_itf[i]; break; } @@ -758,24 +621,23 @@ uint16_t videod_open(uint8_t rhport, tusb_desc_interface_t const * itf_desc, uin bool videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request) { int err; - if (p_request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_INTERFACE) { + if (request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_INTERFACE) { return false; } - unsigned itfnum = tu_u16_low(p_request->wIndex); + unsigned itfnum = tu_u16_low(request->wIndex); /* Identify which interface to use */ int itf; tusb_desc_vc_itf_t const *vc = NULL; for (itf = 0; itf < CFG_TUD_VIDEO; ++itf) { - vc = _videod_itf[itf].vc; - if (!vc) continue; - unsigned beg_itfnum = vc->bInterfaceNumber; + vc = _get_desc_vc(&_videod_itf[itf]); + unsigned beg_itfnum = vc->std.bInterfaceNumber; unsigned end_itfnum = vc->ctl.bInCollection; if (beg_itfnum <= itfnum && itfnum < end_itfnum) break; } if (itf == CFG_TUD_VIDEO) return false; - if (itfnum == vc->bInterfaceNumber) { + if (itfnum == vc->std.bInterfaceNumber) { /* To video control interface */ err = handle_video_ctl_req(rhport, stage, request, itf); } else { @@ -789,68 +651,8 @@ bool videod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_ bool videod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { - (void) result; - - uint8_t itf; - videod_interface_t* p_video; - - // Identify which interface to use - for (itf = 0; itf < CFG_TUD_CDC; itf++) - { - p_video = &_videod_itf[itf]; - if ( ( ep_addr == p_video->ep_out ) || ( ep_addr == p_video->ep_in ) ) break; - } - TU_ASSERT(itf < CFG_TUD_CDC); - - // Received new data - if ( ep_addr == p_video->ep_out ) - { - tu_fifo_write_n(&p_video->rx_ff, &p_video->epout_buf, xferred_bytes); - - // Check for wanted char and invoke callback if needed - if ( tud_cdc_rx_wanted_cb && (((signed char) p_video->wanted_char) != -1) ) - { - for ( uint32_t i = 0; i < xferred_bytes; i++ ) - { - if ( (p_video->wanted_char == p_video->epout_buf[i]) && !tu_fifo_empty(&p_video->rx_ff) ) - { - tud_cdc_rx_wanted_cb(itf, p_video->wanted_char); - } - } - } - - // invoke receive callback (if there is still data) - if (tud_cdc_rx_cb && !tu_fifo_empty(&p_video->rx_ff) ) tud_cdc_rx_cb(itf); - - // prepare for OUT transaction - _prep_out_transaction(p_video); - } - - // Data sent to host, we continue to fetch from tx fifo to send. - // Note: This will cause incorrect baudrate set in line coding. - // Though maybe the baudrate is not really important !!! - if ( ep_addr == p_video->ep_in ) - { - // invoke transmit callback to possibly refill tx fifo - if ( tud_cdc_tx_complete_cb ) tud_cdc_tx_complete_cb(itf); - - if ( 0 == tud_cdc_n_write_flush(itf) ) - { - // If there is no data left, a ZLP should be sent if - // xferred_bytes is multiple of EP Packet size and not zero - if ( !tu_fifo_count(&p_video->tx_ff) && xferred_bytes && (0 == (xferred_bytes & (BULK_PACKET_SIZE-1))) ) - { - if ( usbd_edpt_claim(rhport, p_video->ep_in) ) - { - usbd_edpt_xfer(rhport, p_video->ep_in, NULL, 0); - } - } - } - } - - // nothing to do with notif endpoint for now - - return true; + (void)rhport; (void)ep_addr; (void)result; (void)xferred_bytes; + return false; } #endif diff --git a/src/class/video/video_device.h b/src/class/video/video_device.h index cd04b7391..95d352524 100644 --- a/src/class/video/video_device.h +++ b/src/class/video/video_device.h @@ -46,6 +46,8 @@ TU_ATTR_WEAK bool tud_video_set_cur_cb(uint8_t rhport, tusb_control_request_t co // Invoked when GET_CUR request received TU_ATTR_WEAK bool tud_video_get_cur_cb(uint8_t rhport, tusb_control_request_t const *request); +/* @return video_error_code_t */ +TU_ATTR_WEAK int tud_video_power_mode_cb(unsigned itf, uint8_t power_mod); //--------------------------------------------------------------------+ // INTERNAL USBD-CLASS DRIVER API //--------------------------------------------------------------------+