diff --git a/apps/debug_menu.c b/apps/debug_menu.c index b8199ff0d1..7fc1b14d57 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -116,6 +116,9 @@ #ifdef HAVE_USBSTACK #include "usb_core.h" +#ifdef USB_ENABLE_AUDIO +#include "../usbstack/usb_audio.h" +#endif #endif #include "talk.h" @@ -2509,18 +2512,56 @@ static bool dbg_talk(void) } #ifdef HAVE_USBSTACK -#if defined(ROCKBOX_HAS_LOGF) && defined(USB_ENABLE_SERIAL) -static bool toggle_usb_serial(void) +#if (defined(ROCKBOX_HAS_LOGF) && defined(USB_ENABLE_SERIAL)) +static bool toggle_usb_core_driver(int driver, char *msg) { - bool enabled = !usb_core_driver_enabled(USB_DRIVER_SERIAL); + bool enabled = !usb_core_driver_enabled(driver); - usb_core_enable_driver(USB_DRIVER_SERIAL, enabled); - splashf(HZ, "USB Serial %sabled", enabled ? "en" : "dis"); + usb_core_enable_driver(driver,enabled); + splashf(HZ, "%s %s", msg, enabled ? "enabled" : "disabled"); return false; } + +#ifdef USB_ENABLE_SERIAL +static bool toggle_usb_serial(void) +{ + return toggle_usb_core_driver(USB_DRIVER_SERIAL, "USB Serial"); +} +#endif /* USB_ENABLE_SERIAL */ #endif -#endif + +#ifdef USB_ENABLE_AUDIO +static int dbg_usb_audio_cb(int action, struct gui_synclist *lists) +{ + (void)lists; + simplelist_reset_lines(); + simplelist_addline("%sabled", usb_core_driver_enabled(USB_DRIVER_AUDIO)?"En":"Dis"); + simplelist_addline("%sPlaying", usb_audio_get_playing()?"":"Not "); + simplelist_addline("iface: %d alt: %d", usb_audio_get_main_intf(), usb_audio_get_alt_intf()); + simplelist_addline("out ep: 0x%X in ep: 0x%X", usb_audio_get_out_ep(), usb_audio_get_in_ep()); + simplelist_addline("Volume: %d", usb_audio_get_cur_volume()); + simplelist_addline("Playback Frequency: %lu", usb_audio_get_playback_sampling_frequency()); + simplelist_addline("Buffers filled: %d", usb_audio_get_prebuffering()); + simplelist_addline("%s", usb_audio_get_underflow()?"UNDERFLOW!":" "); + simplelist_addline("%s", usb_audio_get_overflow()?"OVERFLOW!":" "); + simplelist_addline("%s", usb_audio_get_alloc_failed()?"ALLOC FAILED!":" "); + if (action == ACTION_NONE) + { + action = ACTION_REDRAW; + } + return action; +} +static bool dbg_usb_audio(void) +{ + struct simplelist_info info; + simplelist_info_init(&info, "USB Audio", 0, NULL); + info.scroll_all = true; + info.action_callback = dbg_usb_audio_cb; + return simplelist_show_list(&info); +} +#endif /* USB_ENABLE_AUDIO */ +#endif /* HAVE_USBSTACK */ #if CONFIG_USBOTG == USBOTG_ISP1583 extern int dbg_usb_num_items(void); @@ -2847,6 +2888,9 @@ static const struct { #if defined(ROCKBOX_HAS_LOGF) && defined(USB_ENABLE_SERIAL) {"USB Serial driver (logf)", toggle_usb_serial }, #endif +#if defined(USB_ENABLE_AUDIO) + {"USB-DAC", dbg_usb_audio}, +#endif #endif /* HAVE_USBSTACK */ #ifdef CPU_BOOST_LOGGING {"Show cpu_boost log",cpu_boost_log}, diff --git a/apps/lang/english.lang b/apps/lang/english.lang index 6a745f6a6d..f0dddb416d 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -10370,6 +10370,62 @@ usb_hid: "Mouse" + + id: LANG_USB_DAC + desc: in settings_menu + user: core + + *: "USB-DAC" + + + *: "USB-DAC" + + + *: "USB-DAC" + + + + id: LANG_WHILE_USB_CHARGE_ONLY + desc: in settings_menu + user: core + + *: "While In USB Charge-Only Mode" + + + *: "While In USB Charge-Only Mode" + + + *: "While In USB Charge-Only Mode" + + + + id: LANG_WHILE_MASS_STORAGE_USB_ONLY + desc: in settings_menu + user: core + + *: "While In USB Mass-Storage Mode" + + + *: "While In USB Mass-Storage Mode" + + + *: "While In USB Mass-Storage Mode" + + + + id: LANG_USB_DAC_ACTIVE + desc: for splash + user: core + + *: "USB-DAC Active" + + + *: "USB-DAC Active" + + + *: "USB-DAC Active" + + id: LANG_SCROLLBAR_WIDTH desc: in Settings -> General -> Display -> Status-/Scrollbar diff --git a/apps/menus/settings_menu.c b/apps/menus/settings_menu.c index b9c83683f2..78c79b2ef8 100644 --- a/apps/menus/settings_menu.c +++ b/apps/menus/settings_menu.c @@ -358,6 +358,9 @@ MENUITEM_SETTING(lineout_onoff, &global_settings.lineout_active, NULL); MENUITEM_SETTING(usb_hid, &global_settings.usb_hid, NULL); MENUITEM_SETTING(usb_keypad_mode, &global_settings.usb_keypad_mode, NULL); #endif +#ifdef USB_ENABLE_AUDIO +MENUITEM_SETTING(usb_audio, &global_settings.usb_audio, NULL); +#endif #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) MENUITEM_SETTING(usb_skip_first_drive, &global_settings.usb_skip_first_drive, NULL); #endif @@ -454,6 +457,9 @@ MAKE_MENU(system_menu, ID2P(LANG_SYSTEM), &usb_hid, &usb_keypad_mode, #endif +#ifdef USB_ENABLE_AUDIO + &usb_audio, +#endif #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) &usb_skip_first_drive, #endif diff --git a/apps/settings.h b/apps/settings.h index 65f43663db..9b8f82de5a 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -823,6 +823,10 @@ struct user_settings int usb_keypad_mode; #endif +#ifdef USB_ENABLE_AUDIO + int usb_audio; +#endif + #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) bool usb_skip_first_drive; #endif diff --git a/apps/settings_list.c b/apps/settings_list.c index b030989362..e74c70c448 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -2315,6 +2315,11 @@ const struct settings_list settings[] = { ), /* CHOICE_SETTING( usb_keypad_mode ) */ #endif +#ifdef USB_ENABLE_AUDIO + CHOICE_SETTING(0, usb_audio, LANG_USB_DAC, 0, "usb-dac", "never,always,while_charge_only,while_mass_storage", usb_set_audio, 4, + ID2P(LANG_NEVER), ID2P(LANG_ALWAYS), ID2P(LANG_WHILE_USB_CHARGE_ONLY), ID2P(LANG_WHILE_MASS_STORAGE_USB_ONLY)), +#endif + #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) OFFON_SETTING(0, usb_skip_first_drive, LANG_USB_SKIP_FIRST_DRIVE, false, "usb skip first drive", usb_set_skip_first_drive), #endif diff --git a/firmware/SOURCES b/firmware/SOURCES index c8d8212927..8ea7abe610 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -935,6 +935,9 @@ usbstack/usb_storage.c #ifdef USB_ENABLE_SERIAL usbstack/usb_serial.c #endif +#ifdef USB_ENABLE_AUDIO +usbstack/usb_audio.c +#endif #ifdef USB_ENABLE_CHARGING_ONLY usbstack/usb_charging_only.c #endif diff --git a/firmware/drivers/usb-designware.c b/firmware/drivers/usb-designware.c index 01552d9c94..00be10a3fe 100644 --- a/firmware/drivers/usb-designware.c +++ b/firmware/drivers/usb-designware.c @@ -231,7 +231,7 @@ static void usb_dw_set_stall(int epnum, enum usb_dw_epdir epdir, int stall) else { DWC_EPCTL(epnum, epdir) &= ~STALL; - DWC_EPCTL(epnum, epdir) |= SD0PID; + DWC_EPCTL(epnum, epdir) |= SETD0PIDEF; } } @@ -668,7 +668,7 @@ static void usb_dw_unconfigure_ep(int epnum, enum usb_dw_epdir epdir) static int usb_dw_configure_ep(int epnum, enum usb_dw_epdir epdir, int type, int maxpktsize) { - uint32_t epctl = SD0PID|EPTYP(type)|USBAEP|maxpktsize; + uint32_t epctl = SETD0PIDEF|EPTYP(type)|USBAEP|maxpktsize; if (epdir == USB_DW_EPDIR_IN) { @@ -1259,7 +1259,7 @@ static void usb_dw_irq(void) * FIFO and raises StatusRecvd | XferCompl. * * We do not need or want this -- we've already handled - * the data phase by this point -- but EP0 is stoppped + * the data phase by this point -- but EP0 is stopped * as a side effect of XferCompl, so we need to restart * it to keep receiving packets. */ usb_dw_ep0_recv(); @@ -1528,7 +1528,7 @@ void usb_drv_cancel_all_transfers() { //usb_dw_flush_endpoint(ep, dir); usb_dw_abort_endpoint(ep, dir); - DWC_EPCTL(ep, dir) |= SD0PID; + DWC_EPCTL(ep, dir) |= SETD0PIDEF; } usb_dw_target_enable_irq(); } @@ -1606,8 +1606,15 @@ int usb_drv_request_endpoint(int type, int dir) struct usb_dw_ep* dw_ep = usb_dw_get_ep(ep, epdir); if (!dw_ep->active) { + int maxpktsize = 64; + if (type == EPTYP_ISOCHRONOUS){ + maxpktsize = 1023; + } else { + maxpktsize = usb_drv_port_speed() ? 512 : 64; + } + if (usb_dw_configure_ep(ep, epdir, type, - usb_drv_port_speed() ? 512 : 64) >= 0) + maxpktsize) >= 0) { dw_ep->active = true; request_ep = ep | dir; @@ -1679,3 +1686,11 @@ void usb_drv_control_response(enum usb_control_response resp, usb_dw_control_response(resp, data, length); usb_dw_target_enable_irq(); } + +int usb_drv_get_frame_number() +{ + // SOFFN is 14 bits, the least significant 3 appear to be some sort of microframe count. + // The USB spec says a frame number is 11 bits. This way we get 1 frame per millisecond, + // just like we're supposed to! + return (DWC_DSTS >> 11) & 0x7FF; +} diff --git a/firmware/export/config.h b/firmware/export/config.h index 9ddc563d9b..6f27e8fceb 100644 --- a/firmware/export/config.h +++ b/firmware/export/config.h @@ -1347,6 +1347,7 @@ Lyre prototype 1 */ #elif (CONFIG_USBOTG == USBOTG_DESIGNWARE) #define USB_HAS_BULK #define USB_HAS_INTERRUPT +#define USB_HAS_ISOCHRONOUS #elif (CONFIG_USBOTG == USBOTG_ARC) || \ (CONFIG_USBOTG == USBOTG_JZ4740) || \ (CONFIG_USBOTG == USBOTG_JZ4760) || \ @@ -1356,6 +1357,9 @@ Lyre prototype 1 */ (CONFIG_USBOTG == USBOTG_TNETV105) #define USB_HAS_BULK #define USB_HAS_INTERRUPT +#if (CONFIG_USBOTG == USBOTG_ARC) +#define USB_HAS_ISOCHRONOUS +#endif #define USB_LEGACY_CONTROL_API #elif defined(CPU_TCC780X) #define USB_HAS_BULK @@ -1366,11 +1370,6 @@ Lyre prototype 1 */ //#define USB_HAS_INTERRUPT -- seems to be broken #endif /* CONFIG_USBOTG */ -#if (CONFIG_USBOTG == USBOTG_ARC) || \ - (CONFIG_USBOTG == USBOTG_AS3525) -#define USB_HAS_ISOCHRONOUS -#endif - /* define the class drivers to enable */ #ifdef BOOTLOADER @@ -1398,6 +1397,10 @@ Lyre prototype 1 */ #endif #endif +#ifdef USB_HAS_ISOCHRONOUS +#define USB_ENABLE_AUDIO +#endif + #endif /* BOOTLOADER */ #endif /* HAVE_USBSTACK */ diff --git a/firmware/export/pcm_mixer.h b/firmware/export/pcm_mixer.h index 39a814de6f..4e164c0d10 100644 --- a/firmware/export/pcm_mixer.h +++ b/firmware/export/pcm_mixer.h @@ -72,6 +72,9 @@ enum pcm_mixer_channel { PCM_MIXER_CHAN_PLAYBACK = 0, +#ifdef USB_ENABLE_AUDIO + PCM_MIXER_CHAN_USBAUDIO, +#endif PCM_MIXER_CHAN_VOICE, #ifndef HAVE_HARDWARE_BEEP PCM_MIXER_CHAN_BEEP, diff --git a/firmware/export/usb-designware.h b/firmware/export/usb-designware.h index 9e5496f0db..ccef6d3d0d 100644 --- a/firmware/export/usb-designware.h +++ b/firmware/export/usb-designware.h @@ -200,7 +200,8 @@ #define DWC_DOEPCTL(x) (*((REG32_PTR_T)(OTGBASE + 0xb00 + 0x20*(x)))) #define EPENA (1<<31) #define EPDIS (1<<30) - #define SD0PID (1<<28) + #define SETD1PIDOF (1<<29) + #define SETD0PIDEF (1<<28) #define SNAK (1<<27) #define CNAK (1<<26) #define DTXFNUM(x) ((x)<<22) diff --git a/firmware/export/usb.h b/firmware/export/usb.h index c075fa83ec..2987d3e41e 100644 --- a/firmware/export/usb.h +++ b/firmware/export/usb.h @@ -167,6 +167,9 @@ enum { #endif #ifdef USB_ENABLE_HID USB_DRIVER_HID, +#endif +#ifdef USB_ENABLE_AUDIO + USB_DRIVER_AUDIO, #endif USB_NUM_DRIVERS }; @@ -256,6 +259,10 @@ void usb_firewire_connect_event(void); void usb_set_hid(bool enable); #endif +#ifdef USB_ENABLE_AUDIO +void usb_set_audio(int value); +#endif + #if defined(USB_ENABLE_STORAGE) && defined(HAVE_MULTIDRIVE) /* when the target has several drives, decide whether mass storage should * skip the first drive. This is useful when the second drive is a SD card diff --git a/firmware/export/usb_ch9.h b/firmware/export/usb_ch9.h index db311c592e..cd1dc4c228 100644 --- a/firmware/export/usb_ch9.h +++ b/firmware/export/usb_ch9.h @@ -339,6 +339,12 @@ struct usb_endpoint_descriptor { #define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ #define USB_ENDPOINT_DIR_MASK 0x80 +#define USB_ENDPOINT_SYNCTYPE_MASK 0x0c /* in bmAttributes */ +#define USB_ENDPOINT_SYNC_NONE (0 << 2) +#define USB_ENDPOINT_SYNC_ASYNC (1 << 2) +#define USB_ENDPOINT_SYNC_ADAPTIVE (2 << 2) +#define USB_ENDPOINT_SYNC_SYNC (3 << 2) + #define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ #define USB_ENDPOINT_XFER_CONTROL 0 #define USB_ENDPOINT_XFER_ISOC 1 diff --git a/firmware/export/usb_drv.h b/firmware/export/usb_drv.h index 3ef4db3c9c..35b31a359d 100644 --- a/firmware/export/usb_drv.h +++ b/firmware/export/usb_drv.h @@ -74,6 +74,7 @@ void usb_drv_stall(int endpoint, bool stall,bool in); bool usb_drv_stalled(int endpoint,bool in); int usb_drv_send(int endpoint, void* ptr, int length); int usb_drv_send_nonblocking(int endpoint, void* ptr, int length); +int usb_drv_recv_blocking(int endpoint, void* ptr, int length); int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length); void usb_drv_control_response(enum usb_control_response resp, void* data, int length); @@ -86,6 +87,14 @@ void usb_drv_set_test_mode(int mode); bool usb_drv_connected(void); int usb_drv_request_endpoint(int type, int dir); void usb_drv_release_endpoint(int ep); +#ifdef USB_HAS_ISOCHRONOUS +/* returns the last received frame number (the 11-bit number contained in the last SOF): + * - full-speed: the host sends one SOF every 1ms (so 1000 SOF/s) + * - high-speed: the hosts sends one SOF every 125us but each consecutive 8 SOF have the same frame + * number + * thus in all mode, the frame number can be interpreted as the current millisecond *in USB time*. */ +int usb_drv_get_frame_number(void); +#endif /* USB_STRING_INITIALIZER(u"Example String") */ #define USB_STRING_INITIALIZER(S) { \ diff --git a/firmware/target/arm/usb-drv-arc.c b/firmware/target/arm/usb-drv-arc.c index 22751f27f0..a732340bf9 100644 --- a/firmware/target/arm/usb-drv-arc.c +++ b/firmware/target/arm/usb-drv-arc.c @@ -599,11 +599,22 @@ int usb_drv_recv_nonblocking(int endpoint, void* ptr, int length) return prime_transfer(EP_NUM(endpoint), ptr, length, false, false); } +int usb_drv_recv_blocking(int endpoint, void* ptr, int length) +{ + return prime_transfer(EP_NUM(endpoint), ptr, length, false, true); +} + int usb_drv_port_speed(void) { return (REG_PORTSC1 & 0x08000000) ? 1 : 0; } +int usb_drv_get_frame_number(void) +{ + /* the lower 3 bits store the microframe (in HS mode), discard them */ + return (REG_FRINDEX & USB_FRINDEX_MASKS) >> 3; +} + bool usb_drv_connected(void) { return (REG_PORTSC1 & @@ -970,10 +981,8 @@ static void init_control_queue_heads(void) /* manual: 32.14.4.1 Queue Head Initialization */ static void init_queue_heads(void) { - /* FIXME the packetsize for isochronous transfers is 1023 : 1024 but - * the current code only support one type of packet size so we restrict - * isochronous packet size for now also */ int packetsize = (usb_drv_port_speed() ? 512 : 64); + int isopacketsize = (usb_drv_port_speed() ? 1024 : 1024); int i; /* TODO: this should take ep_allocation into account */ @@ -982,7 +991,7 @@ static void init_queue_heads(void) /* OUT */ if(endpoints[i].type[DIR_OUT] == USB_ENDPOINT_XFER_ISOC) /* FIXME: we can adjust the number of packets per frame, currently use one */ - qh_array[i*2].max_pkt_length = packetsize << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL | 1 << QH_MULT_POS; + qh_array[i*2].max_pkt_length = isopacketsize << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL | 1 << QH_MULT_POS; else qh_array[i*2].max_pkt_length = packetsize << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL; @@ -991,7 +1000,7 @@ static void init_queue_heads(void) /* IN */ if(endpoints[i].type[DIR_IN] == USB_ENDPOINT_XFER_ISOC) /* FIXME: we can adjust the number of packets per frame, currently use one */ - qh_array[i*2+1].max_pkt_length = packetsize << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL | 1 << QH_MULT_POS; + qh_array[i*2+1].max_pkt_length = isopacketsize << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL | 1 << QH_MULT_POS; else qh_array[i*2+1].max_pkt_length = packetsize << QH_MAX_PKT_LEN_POS | QH_ZLT_SEL; diff --git a/firmware/usb.c b/firmware/usb.c index ed8c08af66..d45600c0f3 100644 --- a/firmware/usb.c +++ b/firmware/usb.c @@ -97,6 +97,9 @@ static bool exclusive_storage_access = false; #ifdef USB_ENABLE_HID static bool usb_hid = true; #endif +#ifdef USB_ENABLE_AUDIO +static int usb_audio = 0; +#endif #ifdef USB_FULL_INIT static bool usb_host_present = false; @@ -194,6 +197,10 @@ static inline void usb_handle_hotswap(long id) static inline bool usb_configure_drivers(int for_state) { +#ifdef USB_ENABLE_AUDIO + // FIXME: doesn't seem to get set when loaded at boot... + usb_audio = global_settings.usb_audio; +#endif switch(for_state) { case USB_POWERED: @@ -207,6 +214,9 @@ static inline bool usb_configure_drivers(int for_state) usb_core_enable_driver(USB_DRIVER_HID, true); #endif /* USB_ENABLE_CHARGING_ONLY */ #endif /* USB_ENABLE_HID */ +#ifdef USB_ENABLE_AUDIO + usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 2)); // while "always" or "only in charge-only mode" +#endif /* USB_ENABLE_AUDIO */ #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, true); @@ -224,6 +234,9 @@ static inline bool usb_configure_drivers(int for_state) #ifdef USB_ENABLE_HID usb_core_enable_driver(USB_DRIVER_HID, usb_hid); #endif +#ifdef USB_ENABLE_AUDIO + usb_core_enable_driver(USB_DRIVER_AUDIO, (usb_audio == 1) || (usb_audio == 3)); // while "always" or "only in mass-storage mode" +#endif /* USB_ENABLE_AUDIO */ #ifdef USB_ENABLE_CHARGING_ONLY usb_core_enable_driver(USB_DRIVER_CHARGING_ONLY, false); #endif @@ -845,6 +858,13 @@ void usb_set_hid(bool enable) } #endif /* USB_ENABLE_HID */ +#ifdef USB_ENABLE_AUDIO +void usb_set_audio(int value) +{ + usb_audio = value; +} +#endif /* USB_ENABLE_AUDIO */ + #ifdef HAVE_USB_POWER bool usb_powered_only(void) { diff --git a/firmware/usbstack/usb_audio.c b/firmware/usbstack/usb_audio.c new file mode 100644 index 0000000000..6907f4c8b2 --- /dev/null +++ b/firmware/usbstack/usb_audio.c @@ -0,0 +1,1105 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: $ + * + * Copyright (C) 2010 by Amaury Pouly + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#include "string.h" +#include "system.h" +#include "usb_core.h" +#include "usb_drv.h" +#include "kernel.h" +#include "sound.h" +#include "usb_class_driver.h" +#include "usb_audio_def.h" +#include "pcm_sampr.h" +#include "audio.h" +#include "sound.h" +#include "stdlib.h" +#include "fixedpoint.h" +#include "misc.h" +#include "settings.h" +#include "core_alloc.h" +#include "pcm_mixer.h" + +#define LOGF_ENABLE +#include "logf.h" + +/* Audio Control Interface */ +static struct usb_interface_descriptor + ac_interface = +{ + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL, + .bInterfaceProtocol = 0, + .iInterface = 0 +}; + +/* Audio Control Terminals/Units*/ +static struct usb_ac_header ac_header = +{ + .bLength = USB_AC_SIZEOF_HEADER(1), /* one interface */ + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AC_HEADER, + .bcdADC = 0x0100, + .wTotalLength = 0, /* fill later */ + .bInCollection = 1, /* one interface */ + .baInterfaceNr = {0}, /* fill later */ +}; + +enum +{ + AC_PLAYBACK_INPUT_TERMINAL_ID = 1, + AC_PLAYBACK_FEATURE_ID, + AC_PLAYBACK_OUTPUT_TERMINAL_ID, +}; + +static struct usb_ac_input_terminal ac_playback_input = +{ + .bLength = sizeof(struct usb_ac_input_terminal), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AC_INPUT_TERMINAL, + .bTerminalId = AC_PLAYBACK_INPUT_TERMINAL_ID, + .wTerminalType = USB_AC_TERMINAL_STREAMING, + .bAssocTerminal = 0, + .bNrChannels = 2, + .wChannelConfig = USB_AC_CHANNELS_LEFT_RIGHT_FRONT, + .iChannelNames = 0, + .iTerminal = 0, +}; + +static struct usb_ac_output_terminal ac_playback_output = +{ + .bLength = sizeof(struct usb_ac_output_terminal), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AC_OUTPUT_TERMINAL, + .bTerminalId = AC_PLAYBACK_OUTPUT_TERMINAL_ID, + .wTerminalType = USB_AC_OUTPUT_TERMINAL_HEADPHONES, + .bAssocTerminal = 0, + .bSourceId = AC_PLAYBACK_FEATURE_ID, + .iTerminal = 0, +}; + +/* Feature Unit with 2 logical channels and 1 byte(8 bits) per control */ +DEFINE_USB_AC_FEATURE_UNIT(8, 2) + +static struct usb_ac_feature_unit_8_2 ac_playback_feature = +{ + .bLength = sizeof(struct usb_ac_feature_unit_8_2), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AC_FEATURE_UNIT, + .bUnitId = AC_PLAYBACK_FEATURE_ID, + .bSourceId = AC_PLAYBACK_INPUT_TERMINAL_ID, + .bControlSize = 1, /* by definition */ + .bmaControls = { + [0] = USB_AC_FU_MUTE | USB_AC_FU_VOLUME, + [1] = 0, + [2] = 0 + }, + .iFeature = 0 +}; + +/* Audio Streaming Interface */ +/* Alternative: no streaming */ +static struct usb_interface_descriptor + as_interface_alt_idle_playback = +{ + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, + .bInterfaceProtocol = 0, + .iInterface = 0 +}; + +/* Alternative: output streaming */ +static struct usb_interface_descriptor + as_interface_alt_playback = +{ + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 1, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_AUDIO, + .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, + .bInterfaceProtocol = 0, + .iInterface = 0 +}; + +/* Class Specific Audio Streaming Interface */ +static struct usb_as_interface + as_playback_cs_interface = +{ + .bLength = sizeof(struct usb_as_interface), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AS_GENERAL, + .bTerminalLink = AC_PLAYBACK_INPUT_TERMINAL_ID, + .bDelay = 1, + .wFormatTag = USB_AS_FORMAT_TYPE_I_PCM +}; + +static struct usb_as_format_type_i_discrete + as_playback_format_type_i = +{ + .bLength = USB_AS_SIZEOF_FORMAT_TYPE_I_DISCRETE((HW_FREQ_44+1)), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_AS_FORMAT_TYPE, + .bFormatType = USB_AS_FORMAT_TYPE_I, + .bNrChannels = 2, /* Stereo */ + .bSubframeSize = 2, /* 2 bytes per sample */ + .bBitResolution = 16, /* all 16-bits are used */ + .bSamFreqType = (HW_FREQ_44+1), + .tSamFreq = { + // only values 44.1k and higher (array is in descending order) + [0 ... HW_FREQ_44 ] = {0}, /* filled later */ + } +}; + +/* + * TODO: + * It appears that "Adaptive" sync mode means it it the device's duty + * to adapt its consumption rate of data to whatever the host sends, which + * has the possibility to cause trouble in underflows/overflows, etc. + * In practice, this is probably not a large concern, as we have a fairly large + * amount of buffering in the PCM system. + * An improvement may be to use "Asynchronous", but this is more complicated + * due to the need to inform the host about how fast the device will consume + * the data. + * So implementation of "Asynchronous" mode will be left for a later improvement. + */ +static struct usb_iso_audio_endpoint_descriptor + out_iso_ep = +{ + .bLength = sizeof(struct usb_iso_audio_endpoint_descriptor), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, /* filled later */ + .bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ADAPTIVE, + .wMaxPacketSize = 0, /* filled later */ + .bInterval = 0, /* filled later */ + .bRefresh = 0, + .bSynchAddress = 0 /* filled later */ +}; + +static struct usb_as_iso_endpoint + as_out_iso_ep = +{ + .bLength = sizeof(struct usb_as_iso_endpoint), + .bDescriptorType = USB_DT_CS_ENDPOINT, + .bDescriptorSubType = USB_AS_EP_GENERAL, + .bmAttributes = USB_AS_EP_CS_SAMPLING_FREQ_CTL, + .bLockDelayUnits = 0, /* undefined */ + .wLockDelay = 0 /* undefined */ +}; + +static const struct usb_descriptor_header* const ac_cs_descriptors_list[] = +{ + (struct usb_descriptor_header *) &ac_header, + (struct usb_descriptor_header *) &ac_playback_input, + (struct usb_descriptor_header *) &ac_playback_output, + (struct usb_descriptor_header *) &ac_playback_feature +}; + +#define AC_CS_DESCRIPTORS_LIST_SIZE (sizeof(ac_cs_descriptors_list)/sizeof(ac_cs_descriptors_list[0])) + +static const struct usb_descriptor_header* const usb_descriptors_list[] = +{ + /* Audio Control */ + (struct usb_descriptor_header *) &ac_interface, + (struct usb_descriptor_header *) &ac_header, + (struct usb_descriptor_header *) &ac_playback_input, + (struct usb_descriptor_header *) &ac_playback_feature, + (struct usb_descriptor_header *) &ac_playback_output, + /* Audio Streaming */ + /* Idle Playback */ + (struct usb_descriptor_header *) &as_interface_alt_idle_playback, + /* Playback */ + (struct usb_descriptor_header *) &as_interface_alt_playback, + (struct usb_descriptor_header *) &as_playback_cs_interface, + (struct usb_descriptor_header *) &as_playback_format_type_i, + (struct usb_descriptor_header *) &out_iso_ep, + (struct usb_descriptor_header *) &as_out_iso_ep, +}; + +#define USB_DESCRIPTORS_LIST_SIZE (sizeof(usb_descriptors_list)/sizeof(usb_descriptors_list[0])) + +static int usb_interface; /* first interface */ +static int usb_as_playback_intf_alt; /* playback streaming interface alternate setting */ + +static int as_playback_freq_idx; /* audio playback streaming frequency index (in hw_freq_sampr) */ + +static int out_iso_ep_adr; /* output isochronous endpoint */ +static int in_iso_ep_adr; /* input isochronous endpoint */ + +/* small buffer used for control transfers */ +static unsigned char usb_buffer[128] USB_DEVBSS_ATTR; + +/* number of buffers: 2 is double-buffering (one for usb, one for playback), + * 3 is triple-buffering (one for usb, one for playback, one for queuing), ... */ + +/* Samples come in (maximum) 1023 byte chunks. Samples are also 16 bits per channel per sample. + * + * One buffer holds (1023 / (2Bx2ch)) = 255 (rounded down) samples + * So the _maximum_ play time per buffer is (255 / sps). + * For 44100 Hz: 5.7 mS + * For 48000 Hz: 5.3 mS + * For 192000 Hz: 1.3 mS + * + * From testing on MacOS (likely to be the toughest customer...) on Designware driver + * we get data every Frame (so, every millisecond). + * + * If we get data every millisecond, we need 1mS to transfer 1.3mS of playback + * in order to sustain 192 kHz playback! + * At 44.1 kHz, the requirements are much less - 1mS of data transfer for 5.7mS of playback + * At 48 kHz, 1mS can transfer 5.3mS of playback. + * + * It appears that this is "maximum", but we more likely get "enough for 1mS" every millisecond. + * + * Working backwards: + * 44100 Hz: 45 samples transferred every frame (*2ch * 2bytes) = 180 bytes every frame + * 48000 Hz: 48 samples transferred every frame (*2ch * 2bytes) = 192 bytes every frame + * 192000 Hz: *2ch *2bytes = 768 bytes every frame + * + * We appear to be more limited by our PCM system's need to gobble up data at startup. + * This may actually, contrary to intuition, make us need a higher number of buffers + * for _lower_ sample rates, as we will need more buffers' worth of data up-front due to + * lower amounts of data in each USB frame (assuming the mixer wants the same amount of data upfront + * regardless of sample rate). + * + * Making the executive decision to only export frequencies 44.1k+. + */ +#define NR_BUFFERS 32 +#define MINIMUM_BUFFERS_QUEUED 16 +/* size of each buffer: must be smaller than 1023 (max isochronous packet size) */ +#define BUFFER_SIZE 1023 +/* make sure each buffer size is actually a multiple of 32 bytes to avoid any + * issue with strange alignements */ +#define REAL_BUF_SIZE ALIGN_UP(BUFFER_SIZE, 32) + +bool alloc_failed = false; +bool usb_audio_playing = false; +int tmp_saved_vol; + +/* buffers used for usb, queuing and playback */ +static unsigned char *rx_buffer; +int rx_buffer_handle; +/* buffer size */ +static int rx_buf_size[NR_BUFFERS]; +/* index of the next buffer to play */ +static int rx_play_idx; +/* index of the next buffer to fill */ +static int rx_usb_idx; +/* playback underflowed ? */ +bool playback_audio_underflow; +/* usb overflow ? */ +bool usb_rx_overflow; + +/* Schematic view of the RX situation: + * (in case NR_BUFFERS = 4) + * + * +--------+ +--------+ +--------+ +--------+ + * | | | | | | | | + * | buf[0] | ---> | buf[1] | ---> | buf[2] | ---> | buf[3] | ---> (back to buf[0]) + * | | | | | | | | + * +--------+ +--------+ +--------+ +--------+ + * ^ ^ ^ ^ + * | | | | + * rx_play_idx (buffer rx_usb_idx (empty buffer) + * (buffer being filled) (buffer being + * played) filled) + * + * Error handling: + * in the RX situation, there are two possible errors + * - playback underflow: playback wants more data but we don't have any to + * provide, so we have to stop audio and wait for some prebuffering before + * starting again + * - usb overflow: usb wants to send more data but don't have any more free buffers, + * so we have to pause usb reception and wait for some playback buffer to become + * free again + */ + +/* USB Audio encodes frequencies with 3 bytes... */ +static void encode3(uint8_t arr[3], unsigned long freq) +{ + /* ugly */ + arr[0] = freq & 0xff; + arr[1] = (freq >> 8) & 0xff; + arr[2] = (freq >> 16) & 0xff; +} + +static unsigned long decode3(uint8_t arr[3]) +{ + return arr[0] | (arr[1] << 8) | (arr[2] << 16); +} + +static void set_playback_sampling_frequency(unsigned long f) +{ + // only values 44.1k and higher (array is in descending order) + for(int i = 0; i <= HW_FREQ_44; i++) + { + /* compare errors */ + int err = abs((long)hw_freq_sampr[i] - (long)f); + int best_err = abs((long)hw_freq_sampr[as_playback_freq_idx] - (long)f); + if(err < best_err) + as_playback_freq_idx = i; + } + + logf("usbaudio: set playback sampling frequency to %lu Hz for a requested %lu Hz", + hw_freq_sampr[as_playback_freq_idx], f); + + mixer_set_frequency(hw_freq_sampr[as_playback_freq_idx]); + pcm_apply_settings(); +} + +unsigned long usb_audio_get_playback_sampling_frequency(void) +{ + logf("usbaudio: get playback sampl freq %lu Hz", + hw_freq_sampr[as_playback_freq_idx]); + return hw_freq_sampr[as_playback_freq_idx]; +} + +void usb_audio_init(void) +{ + unsigned int i; + /* initialized tSamFreq array */ + logf("usbaudio: supported frequencies"); + // only values 44.1k and higher (array is in descending order) + for(i = 0; i <= HW_FREQ_44; i++) + { + logf("usbaudio: %lu Hz", hw_freq_sampr[i]); + encode3(as_playback_format_type_i.tSamFreq[i], hw_freq_sampr[i]); + } +} + +int usb_audio_request_buf(void) +{ + + // stop playback first thing + audio_stop(); + + // attempt to allocate the receive buffers + rx_buffer_handle = core_alloc(NR_BUFFERS * REAL_BUF_SIZE); + if (rx_buffer_handle < 0) + { + alloc_failed = true; + return -1; + } + else + { + alloc_failed = false; + + // "pin" the allocation so that the core does not move it in memory + core_pin(rx_buffer_handle); + + // get the pointer to the actual buffer location + rx_buffer = core_get_data(rx_buffer_handle); + } + // logf("usbaudio: got buffer"); + return 0; +} + +void usb_audio_free_buf(void) +{ + // logf("usbaudio: free buffer"); + rx_buffer_handle = core_free(rx_buffer_handle); + rx_buffer = NULL; +} + +int usb_audio_request_endpoints(struct usb_class_driver *drv) +{ + // make sure we can get the buffers first... + // return -1 if the allocation _failed_ + if (usb_audio_request_buf()) + return -1; + + out_iso_ep_adr = usb_core_request_endpoint(USB_ENDPOINT_XFER_ISOC, USB_DIR_OUT, drv); + if(out_iso_ep_adr < 0) + { + logf("usbaudio: cannot get an out iso endpoint"); + return -1; + } + + in_iso_ep_adr = usb_core_request_endpoint(USB_ENDPOINT_XFER_ISOC, USB_DIR_IN, drv); + if(in_iso_ep_adr < 0) + { + usb_core_release_endpoint(out_iso_ep_adr); + logf("usbaudio: cannot get an out iso endpoint"); + return -1; + } + + logf("usbaudio: iso out ep is 0x%x, in ep is 0x%x", out_iso_ep_adr, in_iso_ep_adr); + + out_iso_ep.bEndpointAddress = out_iso_ep_adr; + out_iso_ep.bSynchAddress = 0; + + return 0; +} + +unsigned int usb_audio_get_out_ep(void) +{ + return out_iso_ep_adr; +} + +unsigned int usb_audio_get_in_ep(void) +{ + return in_iso_ep_adr; +} + +int usb_audio_set_first_interface(int interface) +{ + usb_interface = interface; + logf("usbaudio: usb_interface=%d", usb_interface); + return interface + 2; /* Audio Control and Audio Streaming */ +} + +int usb_audio_get_config_descriptor(unsigned char *dest, int max_packet_size) +{ + (void)max_packet_size; + unsigned int i; + unsigned char *orig_dest = dest; + + // logf("get config descriptors"); + + /** Configuration */ + + /* header */ + ac_header.baInterfaceNr[0] = usb_interface + 1; + + /* audio control interface */ + ac_interface.bInterfaceNumber = usb_interface; + + /* compute total size of AC headers*/ + ac_header.wTotalLength = 0; + for(i = 0; i < AC_CS_DESCRIPTORS_LIST_SIZE; i++) + ac_header.wTotalLength += ac_cs_descriptors_list[i]->bLength; + + /* audio streaming */ + as_interface_alt_idle_playback.bInterfaceNumber = usb_interface + 1; + as_interface_alt_playback.bInterfaceNumber = usb_interface + 1; + + /* endpoints */ + out_iso_ep.wMaxPacketSize = 1023; /* one micro-frame per transaction */ + /** Endpoint Interval calculation: + * typically sampling frequency is 44100 Hz and top is 192000 Hz, which + * account for typical 44100*2(stereo)*2(16-bit) ~= 180 kB/s + * and top 770 kB/s. Since there are 1000 frames per seconds and maximum + * packet size is set to 1023, one transaction per frame is good enough + * for over 1 MB/s. At high-speed, add 3 to this value because there are + * 8 = 2^3 micro-frames per frame. + * Recall that actual is 2^(bInterval - 1) */ + out_iso_ep.bInterval = usb_drv_port_speed() ? 4 : 1; + + /** Packing */ + for(i = 0; i < USB_DESCRIPTORS_LIST_SIZE; i++) + { + memcpy(dest, usb_descriptors_list[i], usb_descriptors_list[i]->bLength); + dest += usb_descriptors_list[i]->bLength; + } + + return dest - orig_dest; +} + +static void playback_audio_get_more(const void **start, size_t *size) +{ + /* if there are no more filled buffers, playback has just underflowed */ + if(rx_play_idx == rx_usb_idx) + { + logf("usbaudio: playback underflow"); + playback_audio_underflow = true; + *start = NULL; + *size = 0; + return; + } + /* give buffer and advance */ + logf("usbaudio: buf adv"); + *start = rx_buffer + (rx_play_idx * REAL_BUF_SIZE); + *size = rx_buf_size[rx_play_idx]; + rx_play_idx = (rx_play_idx + 1) % NR_BUFFERS; + /* if usb RX buffers had overflowed, we can start to receive again + * guard against IRQ to avoid race with completion usb completion (although + * this function is probably running in IRQ context anyway) */ + int oldlevel = disable_irq_save(); + if(usb_rx_overflow) + { + logf("usbaudio: recover usb rx overflow"); + usb_rx_overflow = false; + usb_drv_recv_nonblocking(out_iso_ep_adr, rx_buffer + (rx_usb_idx * REAL_BUF_SIZE), BUFFER_SIZE); + } + restore_irq(oldlevel); +} + +static void usb_audio_start_playback(void) +{ + usb_audio_playing = true; + usb_rx_overflow = false; + playback_audio_underflow = true; + rx_play_idx = 0; + rx_usb_idx = 0; + + // TODO: implement recording from the USB stream +#if (INPUT_SRC_CAPS != 0) + audio_set_input_source(AUDIO_SRC_PLAYBACK, SRCF_PLAYBACK); + audio_set_output_source(AUDIO_SRC_PLAYBACK); +#endif + logf("usbaudio: start playback at %lu Hz", hw_freq_sampr[as_playback_freq_idx]); + mixer_set_frequency(hw_freq_sampr[as_playback_freq_idx]); + pcm_apply_settings(); + mixer_channel_set_amplitude(PCM_MIXER_CHAN_USBAUDIO, MIX_AMP_UNITY); + usb_drv_recv_nonblocking(out_iso_ep_adr, rx_buffer + (rx_usb_idx * REAL_BUF_SIZE), BUFFER_SIZE); +} + +static void usb_audio_stop_playback(void) +{ + // logf("usbaudio: stop playback"); + if(usb_audio_playing) + { + mixer_channel_stop(PCM_MIXER_CHAN_USBAUDIO); + usb_audio_playing = false; + } +} + +int usb_audio_set_interface(int intf, int alt) +{ + if(intf == usb_interface) + { + if(alt != 0) + { + logf("usbaudio: control interface has no alternate %d", alt); + return -1; + } + + return 0; + } + if(intf == (usb_interface + 1)) + { + if(alt < 0 || alt > 1) + { + logf("usbaudio: playback interface has no alternate %d", alt); + return -1; + } + usb_as_playback_intf_alt = alt; + + if(usb_as_playback_intf_alt == 1) + usb_audio_start_playback(); + else + usb_audio_stop_playback(); + logf("usbaudio: use playback alternate %d", alt); + + return 0; + } + else + { + logf("usbaudio: interface %d has no alternate", intf); + return -1; + } +} + +int usb_audio_get_interface(int intf) +{ + if(intf == usb_interface) + { + logf("usbaudio: control interface alternate is 0"); + return 0; + } + else if(intf == (usb_interface + 1)) + { + logf("usbaudio: playback interface alternate is %d", usb_as_playback_intf_alt); + return usb_as_playback_intf_alt; + } + else + { + logf("usbaudio: unknown interface %d", intf); + return -1; + } +} + +int usb_audio_get_main_intf(void) +{ + return usb_interface; +} + +int usb_audio_get_alt_intf(void) +{ + return usb_as_playback_intf_alt; +} + +static bool usb_audio_as_playback_endpoint_request(struct usb_ctrlrequest* req, void *reqdata) +{ + /* only support sampling frequency */ + if(req->wValue != (USB_AS_EP_CS_SAMPLING_FREQ_CTL << 8)) + { + logf("usbaudio: endpoint only handles sampling frequency control"); + return false; + } + + switch(req->bRequest) + { + case USB_AC_SET_CUR: + if(req->wLength != 3) + { + logf("usbaudio: bad length for SET_CUR"); + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + return true; + } + logf("usbaudio: SET_CUR sampling freq"); + + if (reqdata) { /* control write, second pass */ + set_playback_sampling_frequency(decode3(reqdata)); + usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); + return true; + } else { /* control write, first pass */ + bool error = false; + + if (req->wLength != 3) + error = true; + /* ... other validation? */ + + if (error) + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + else + usb_drv_control_response(USB_CONTROL_RECEIVE, usb_buffer, 3); + + return true; + } + + case USB_AC_GET_CUR: + if(req->wLength != 3) + { + logf("usbaudio: bad length for GET_CUR"); + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + return true; + } + logf("usbaudio: GET_CUR sampling freq"); + encode3(usb_buffer, usb_audio_get_playback_sampling_frequency()); + usb_drv_control_response(USB_CONTROL_ACK, usb_buffer, req->wLength); + + return true; + + default: + logf("usbaudio: unhandled ep req 0x%x", req->bRequest); + } + + return true; +} + +static bool usb_audio_endpoint_request(struct usb_ctrlrequest* req, void *reqdata) +{ + int ep = req->wIndex & 0xff; + + if(ep == out_iso_ep_adr) + return usb_audio_as_playback_endpoint_request(req, reqdata); + else + { + logf("usbaudio: unhandled ep req (ep=%d)", ep); + return false; + } +} + +static bool feature_unit_set_mute(int value, uint8_t cmd) +{ + if(cmd != USB_AC_CUR_REQ) + { + logf("usbaudio: feature unit MUTE control only has a CUR setting"); + return false; + } + + if(value == 1) + { + logf("usbaudio: mute !"); + tmp_saved_vol = sound_current(SOUND_VOLUME); + // sound_set_volume(sound_min(SOUND_VOLUME)); + // setvol does range checking for us! + global_status.volume = sound_min(SOUND_VOLUME); + setvol(); + return true; + } + else if(value == 0) + { + logf("usbaudio: not muted !"); + // sound_set_volume(tmp_saved_vol); + // setvol does range checking for us! + global_status.volume = tmp_saved_vol; + setvol(); + return true; + } + else + { + logf("usbaudio: invalid value for CUR setting of feature unit (%d)", value); + return false; + } +} + +static bool feature_unit_get_mute(int *value, uint8_t cmd) +{ + if(cmd != USB_AC_CUR_REQ) + { + logf("usbaudio: feature unit MUTE control only has a CUR setting"); + return false; + } + + *value = (sound_current(SOUND_VOLUME) == sound_min(SOUND_VOLUME)); + return true; +} + +/* +* USB volume is a signed 16-bit value, -127.9961 dB (0x8001) to +127.9961 dB (0x7FFF) +* in steps of 1/256 dB (0.00390625 dB) +* +* We need to account for different devices having different numbers of decimals +*/ +// TODO: do we need to explicitly round these? Will we have a "walking" round conversion issue? +// Step values of 1 dB (and multiples), and 0.5 dB should be able to be met exactly, +// presuming that it starts on an even number. +static int usb_audio_volume_to_db(int vol, int numdecimals) +{ + int tmp = (signed long)((signed short)vol * ipow(10, numdecimals)) / 256; + // logf("vol=0x%04X, numdecimals=%d, tmp=%d", vol, numdecimals, tmp); + return tmp; +} +static int db_to_usb_audio_volume(int db, int numdecimals) +{ + int tmp = (signed long)(db * 256) / ipow(10, numdecimals); + // logf("db=%d, numdecimals=%d, tmpTodB=%d", db, numdecimals, usb_audio_volume_to_db(tmp, numdecimals)); + return tmp; +} + +#if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF) +static const char *usb_audio_ac_ctl_req_str(uint8_t cmd) +{ + switch(cmd) + { + case USB_AC_CUR_REQ: return "CUR"; + case USB_AC_MIN_REQ: return "MIN"; + case USB_AC_MAX_REQ: return "MAX"; + case USB_AC_RES_REQ: return "RES"; + case USB_AC_MEM_REQ: return "MEM"; + default: return ""; + } +} +#endif + +static bool feature_unit_set_volume(int value, uint8_t cmd) +{ + if(cmd != USB_AC_CUR_REQ) + { + logf("usbaudio: feature unit VOLUME doesn't support %s setting", usb_audio_ac_ctl_req_str(cmd)); + return false; + } + + logf("usbaudio: set volume=%d dB", usb_audio_volume_to_db(value, sound_numdecimals(SOUND_VOLUME))); + + // sound_set_volume(usb_audio_volume_to_db(value, sound_numdecimals(SOUND_VOLUME))); + // setvol does range checking for us! + // we cannot guarantee the host will send us a volume within our range + global_status.volume = usb_audio_volume_to_db(value, sound_numdecimals(SOUND_VOLUME)); + setvol(); + return true; +} + +static bool feature_unit_get_volume(int *value, uint8_t cmd) +{ + switch(cmd) + { + case USB_AC_CUR_REQ: *value = db_to_usb_audio_volume(sound_current(SOUND_VOLUME), sound_numdecimals(SOUND_VOLUME)); break; + case USB_AC_MIN_REQ: *value = db_to_usb_audio_volume(sound_min(SOUND_VOLUME), sound_numdecimals(SOUND_VOLUME)); break; + case USB_AC_MAX_REQ: *value = db_to_usb_audio_volume(sound_max(SOUND_VOLUME), sound_numdecimals(SOUND_VOLUME)); break; + case USB_AC_RES_REQ: *value = db_to_usb_audio_volume(sound_steps(SOUND_VOLUME), sound_numdecimals(SOUND_VOLUME)); break; + default: + logf("usbaudio: feature unit VOLUME doesn't support %s setting", usb_audio_ac_ctl_req_str(cmd)); + return false; + } + + logf("usbaudio: get %s volume=%d dB", usb_audio_ac_ctl_req_str(cmd), usb_audio_volume_to_db(*value, sound_numdecimals(SOUND_VOLUME))); + return true; +} + +int usb_audio_get_cur_volume(void) +{ + int vol; + feature_unit_get_volume(&vol, USB_AC_CUR_REQ); + return usb_audio_volume_to_db(vol, sound_numdecimals(SOUND_VOLUME)); +} + +static bool usb_audio_set_get_feature_unit(struct usb_ctrlrequest* req, void *reqdata) +{ + int channel = req->wValue & 0xff; + int selector = req->wValue >> 8; + uint8_t cmd = (req->bRequest & ~USB_AC_GET_REQ); + int value = 0; + int i; + bool handled; + + /* master channel only */ + if(channel != 0) + { + logf("usbaudio: set/get on feature unit only apply to master channel (%d)", channel); + return false; + } + /* selectors */ + /* all send/received values are integers so already read data if necessary and store in it in an integer */ + if(req->bRequest & USB_AC_GET_REQ) + { + /* get */ + switch(selector) + { + case USB_AC_FU_MUTE: + handled = (req->wLength == 1) && feature_unit_get_mute(&value, cmd); + break; + case USB_AC_VOLUME_CONTROL: + handled = (req->wLength == 2) && feature_unit_get_volume(&value, cmd); + break; + default: + handled = false; + logf("usbaudio: unhandled control selector of feature unit (0x%x)", selector); + break; + } + + if(!handled) + { + logf("usbaudio: unhandled get control 0x%x selector 0x%x of feature unit", cmd, selector); + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + return true; + } + + if(req->wLength == 0 || req->wLength > 4) + { + logf("usbaudio: get data payload size is invalid (%d)", req->wLength); + return false; + } + + for(i = 0; i < req->wLength; i++) + usb_buffer[i] = (value >> (8 * i)) & 0xff; + + usb_drv_control_response(USB_CONTROL_ACK, usb_buffer, req->wLength); + return true; + } + else + { + /* set */ + if(req->wLength == 0 || req->wLength > 4) + { + logf("usbaudio: set data payload size is invalid (%d)", req->wLength); + return false; + } + + if (reqdata) { + + for(i = 0; i < req->wLength; i++) + value = value | (usb_buffer[i] << (i * 8)); + + switch(selector) + { + case USB_AC_FU_MUTE: + handled = (req->wLength == 1) && feature_unit_set_mute(value, cmd); + break; + case USB_AC_VOLUME_CONTROL: + handled = (req->wLength == 2) && feature_unit_set_volume(value, cmd); + break; + default: + handled = false; + logf("usbaudio: unhandled control selector of feature unit (0x%x)", selector); + break; + } + + if(!handled) + { + logf("usbaudio: unhandled set control 0x%x selector 0x%x of feature unit", cmd, selector); + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + return true; + } + + usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); + return true; + } else { + /* + * should handle the following (req->wValue >> 8): + * USB_AC_FU_MUTE + * USB_AC_VOLUME_CONTROL + */ + + bool error = false; + + if (error) + usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); + else + usb_drv_control_response(USB_CONTROL_RECEIVE, usb_buffer, 3); + + return true; + } + + return true; + } +} + +static bool usb_audio_ac_set_get_request(struct usb_ctrlrequest* req, void *reqdata) +{ + switch(req->wIndex >> 8) + { + case AC_PLAYBACK_FEATURE_ID: + return usb_audio_set_get_feature_unit(req, reqdata); + default: + logf("usbaudio: unhandled set/get on entity %d", req->wIndex >> 8); + return false; + } +} + +static bool usb_audio_interface_request(struct usb_ctrlrequest* req, void *reqdata) +{ + int intf = req->wIndex & 0xff; + + if(intf == usb_interface) + { + switch(req->bRequest) + { + case USB_AC_SET_CUR: case USB_AC_SET_MIN: case USB_AC_SET_MAX: case USB_AC_SET_RES: + case USB_AC_SET_MEM: case USB_AC_GET_CUR: case USB_AC_GET_MIN: case USB_AC_GET_MAX: + case USB_AC_GET_RES: case USB_AC_GET_MEM: + return usb_audio_ac_set_get_request(req, reqdata); + default: + logf("usbaudio: unhandled ac intf req 0x%x", req->bRequest); + return false; + } + } + else + { + logf("usbaudio: unhandled intf req (intf=%d)", intf); + return false; + } +} + +bool usb_audio_control_request(struct usb_ctrlrequest* req, void *reqdata) +{ + (void) reqdata; + + switch(req->bRequestType & USB_RECIP_MASK) + { + case USB_RECIP_ENDPOINT: + return usb_audio_endpoint_request(req, reqdata); + case USB_RECIP_INTERFACE: + return usb_audio_interface_request(req, reqdata); + default: + logf("usbaudio: unhandled req type 0x%x", req->bRequestType); + return false; + } +} + +void usb_audio_init_connection(void) +{ + logf("usbaudio: init connection"); + + usb_as_playback_intf_alt = 0; + set_playback_sampling_frequency(HW_SAMPR_DEFAULT); + tmp_saved_vol = sound_current(SOUND_VOLUME); + usb_audio_playing = false; +} + +void usb_audio_disconnect(void) +{ + logf("usbaudio: disconnect"); + + usb_audio_stop_playback(); + usb_audio_free_buf(); +} + +bool usb_audio_get_alloc_failed(void) +{ + return alloc_failed; +} + +bool usb_audio_get_playing(void) +{ + return usb_audio_playing; +} + +/* determine if enough prebuffering has been done to restart audio */ +bool prebuffering_done(void) +{ + /* restart audio if at least two buffers are filled */ + int diff = (rx_usb_idx - rx_play_idx + NR_BUFFERS) % NR_BUFFERS; + return diff >= MINIMUM_BUFFERS_QUEUED; +} + +int usb_audio_get_prebuffering(void) +{ + return (rx_usb_idx - rx_play_idx + NR_BUFFERS) % NR_BUFFERS; +} + +bool usb_audio_get_underflow(void) +{ + return playback_audio_underflow; +} + +bool usb_audio_get_overflow(void) +{ + return usb_rx_overflow; +} + +void usb_audio_transfer_complete(int ep, int dir, int status, int length) +{ + /* normal handler is too slow to handle the completion rate, because + * of the low thread schedule rate */ + (void) ep; + (void) dir; + (void) status; + (void) length; +} + +bool usb_audio_fast_transfer_complete(int ep, int dir, int status, int length) +{ + (void) dir; + + if(ep == out_iso_ep_adr && usb_as_playback_intf_alt == 1) + { + // logf("usbaudio: frame: %d", usb_drv_get_frame_number()); + if(status != 0) + return true; /* FIXME how to handle error here ? */ + /* store length, queue buffer */ + rx_buf_size[rx_usb_idx] = length; + rx_usb_idx = (rx_usb_idx + 1) % NR_BUFFERS; + /* guard against IRQ to avoid race with completion audio completion */ + int oldlevel = disable_irq_save(); + /* setup a new transaction except if we ran out of buffers */ + if(rx_usb_idx != rx_play_idx) + { + logf("usbaudio: new transaction"); + usb_drv_recv_nonblocking(out_iso_ep_adr, rx_buffer + (rx_usb_idx*REAL_BUF_SIZE), BUFFER_SIZE); + } + else + { + logf("usbaudio: rx overflow"); + usb_rx_overflow = true; + } + /* if audio underflowed and prebuffering is done, restart audio */ + if(playback_audio_underflow && prebuffering_done()) + { + logf("usbaudio: prebuffering done"); + playback_audio_underflow = false; + usb_rx_overflow = false; + mixer_channel_play_data(PCM_MIXER_CHAN_USBAUDIO, playback_audio_get_more, NULL, 0); + } + restore_irq(oldlevel); + return true; + } + else + return false; +} diff --git a/firmware/usbstack/usb_audio.h b/firmware/usbstack/usb_audio.h new file mode 100644 index 0000000000..e713f8f92b --- /dev/null +++ b/firmware/usbstack/usb_audio.h @@ -0,0 +1,223 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: $ + * + * Copyright (C) 2014 by Amaury Pouly + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +#ifndef USB_AUDIO_H +#define USB_AUDIO_H + +#include "usb_ch9.h" + +/* + * usb_audio_request_endpoints(): + * + * Calls usb_core_request_endpoint() to request one IN and one OUT + * isochronous endpoint. + * + * Called by allocate_interfaces_and_endpoints(). + * + * Returns -1 if either request fails, returns 0 if success. + * + * Also requests buffer allocations. If allocation fails, + * returns -1 so that the driver will be disabled by the USB core. + */ +int usb_audio_request_endpoints(struct usb_class_driver *); + +/* + * usb_audio_set_first_interface(): + * + * Required function for the class driver. + * + * Called by allocate_interfaces_and_endpoints() to + * tell the class driver what its first interface number is. + * Returns the number of the interface available for the next + * class driver to use. + * + * We need 2 interfaces, AudioControl and AudioStreaming. + * Return interface+2. + */ +int usb_audio_set_first_interface(int interface); + +/* + * usb_audio_get_config_descriptor(): + * + * Required function for the class driver. + * + * Called by request_handler_device_get_descriptor(), which expects + * this function to fill *dest with the configuration descriptor for this + * class driver. + * + * Return the size of this descriptor in bytes. + */ +int usb_audio_get_config_descriptor(unsigned char *dest,int max_packet_size); + +/* + * usb_audio_init_connection(): + * + * Called by usb_core_do_set_config() when the + * connection is ready to be used. Currently just sets + * the audio sample rate to default. + */ +void usb_audio_init_connection(void); + +/* + * usb_audio_init(): + * + * Initialize the driver. Called by usb_core_init(). + * Currently initializes the sampling frequency values available + * to the AudioStreaming interface. + */ +void usb_audio_init(void); + +/* + * usb_audio_disconnect(): + * + * Called by usb_core_exit() AND usb_core_do_set_config(). + * + * Indicates to the Class driver that the connection is no + * longer active. Currently just calls usb_audio_stop_playback(). + */ +void usb_audio_disconnect(void); + +/* + * usb_audio_get_playing(): + * + * Returns playing/not playing status of usbaudio. + */ +bool usb_audio_get_playing(void); + +/* + * usb_audio_get_alloc_failed(): + * + * Return whether the buffer allocation succeeded (0) + * or failed (1). + */ +bool usb_audio_get_alloc_failed(void); + +/* + * usb_audio_transfer_complete(): + * + * Dummy function. + * + * The fast_transfer_complete() function needs to be used instead. + */ +void usb_audio_transfer_complete(int ep,int dir, int status, int length); + +/* + * usb_audio_fast_transfer_complete(): + * + * Called by usb_core_transfer_complete(). + * The normal transfer complete handler system is too slow to deal with + * ISO data at the rate required, so this is required. + * + * Return true if the transfer is handled, false otherwise. + */ +bool usb_audio_fast_transfer_complete(int ep,int dir, int status, int length); + +/* + * usb_audio_control_request(): + * + * Called by control_request_handler_drivers(). + * Pass control requests down to the appropriate functions. + * + * Return true if this driver handles the request, false otherwise. + */ +bool usb_audio_control_request(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); + +/* + * usb_audio_set_interface(): + * + * Called by control_request_handler_drivers(). + * Deal with changing the interface between control and streaming. + * + * Return 0 for success, -1 otherwise. + */ +int usb_audio_set_interface(int intf, int alt); + +/* + * usb_audio_get_interface(): + * + * Called by control_request_handler_drivers(). + * Get the alternate of the given interface. + * + * Return the alternate of the given interface, -1 if unknown. + */ +int usb_audio_get_interface(int intf); + +/* + * usb_audio_get_playback_sampling_frequency(): + * + * Return the sample rate currently set. + */ +unsigned long usb_audio_get_playback_sampling_frequency(void); + +/* + * usb_audio_get_main_intf(): + * + * Return the main usb interface + */ +int usb_audio_get_main_intf(void); + +/* + * usb_audio_get_alt_intf(): + * + * Return the alternate usb interface + */ +int usb_audio_get_alt_intf(void); + +/* + * usb_audio_get_out_ep(): + * + * Return the out (to device) endpoint + */ +unsigned int usb_audio_get_out_ep(void); + +/* + * usb_audio_get_in_ep(): + * + * Return the in (to host) endpoint + */ +unsigned int usb_audio_get_in_ep(void); + +/* + * usb_audio_get_prebuffering(): + * + * Return number of buffers filled ahead of playback + */ +int usb_audio_get_prebuffering(void); + +/* + * usb_audio_get_underflow(): + * + * Return whether playback is in "underflow" state + */ +bool usb_audio_get_underflow(void); + +/* + * usb_audio_get_overflow(): + * + * Return whether usb is in "overflow" state + */ +bool usb_audio_get_overflow(void); + +/* + * usb_audio_get_cur_volume(): + * + * Return current audio volume in db + */ +int usb_audio_get_cur_volume(void); + +#endif diff --git a/firmware/usbstack/usb_audio_def.h b/firmware/usbstack/usb_audio_def.h new file mode 100644 index 0000000000..87bfe7ece9 --- /dev/null +++ b/firmware/usbstack/usb_audio_def.h @@ -0,0 +1,233 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: $ + * + * Copyright (C) 2010 by Amaury Pouly + * + * All files in this archive are subject to the GNU General Public License. + * See the file COPYING in the source tree root for full license agreement. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ****************************************************************************/ +/* Parts of this file are based on Frank Gevaerts work and on audio.h from linux */ + +#ifndef USB_AUDIO_DEF_H +#define USB_AUDIO_DEF_H + +#include "usb_ch9.h" + +#define USB_SUBCLASS_AUDIO_CONTROL 1 +#define USB_SUBCLASS_AUDIO_STREAMING 2 + +#define USB_AC_HEADER 1 +#define USB_AC_INPUT_TERMINAL 2 +#define USB_AC_OUTPUT_TERMINAL 3 +#define USB_AC_MIXER_UNIT 4 +#define USB_AC_SELECTOR_UNIT 5 +#define USB_AC_FEATURE_UNIT 6 +#define USB_AC_PROCESSING_UNIT 8 +#define USB_AC_EXTENSION_UNIT 9 + +#define USB_AS_GENERAL 1 +#define USB_AS_FORMAT_TYPE 2 + +#define USB_AC_CHANNEL_LEFT_FRONT 0x1 +#define USB_AC_CHANNEL_RIGHT_FRONT 0x2 + +#define USB_AC_CHANNELS_LEFT_RIGHT_FRONT (USB_AC_CHANNEL_LEFT_FRONT | USB_AC_CHANNEL_RIGHT_FRONT) + +/* usb audio data structures */ +struct usb_ac_header { + uint8_t bLength; + uint8_t bDescriptorType; /* USB_DT_CS_INTERFACE */ + uint8_t bDescriptorSubType; /* USB_AC_HEADER */ + uint16_t bcdADC; + uint16_t wTotalLength; + uint8_t bInCollection; + uint8_t baInterfaceNr[]; +} __attribute__ ((packed)); + +#define USB_AC_SIZEOF_HEADER(n) (8 + (n)) + +#define USB_AC_TERMINAL_UNDEFINED 0x100 +#define USB_AC_TERMINAL_STREAMING 0x101 +#define USB_AC_TERMINAL_VENDOR_SPEC 0x1ff + +#define USB_AC_EXT_TERMINAL_UNDEFINED 0x600 +#define USB_AC_EXT_TERMINAL_ANALOG 0x601 +#define USB_AC_EXT_TERMINAL_DIGITAL 0x602 +#define USB_AC_EXT_TERMINAL_LINE 0x603 +#define USB_AC_EXT_TERMINAL_LEGACY 0x604 +#define USB_AC_EXT_TERMINAL_SPDIF 0x605 +#define USB_AC_EXT_TERMINAL_1394_DA 0x606 +#define USB_AC_EXT_TERMINAL_1394_DV 0x607 + +#define USB_AC_EMB_MINIDISK 0x706 +#define USB_AC_EMB_RADIO_RECV 0x710 + +#define USB_AC_INPUT_TERMINAL_UNDEFINED 0x200 +#define USB_AC_INPUT_TERMINAL_MICROPHONE 0x201 +#define USB_AC_INPUT_TERMINAL_DESKTOP_MICROPHONE 0x202 +#define USB_AC_INPUT_TERMINAL_PERSONAL_MICROPHONE 0x203 +#define USB_AC_INPUT_TERMINAL_OMNI_DIR_MICROPHONE 0x204 +#define USB_AC_INPUT_TERMINAL_MICROPHONE_ARRAY 0x205 +#define USB_AC_INPUT_TERMINAL_PROC_MICROPHONE_ARRAY 0x206 + +struct usb_ac_input_terminal { + uint8_t bLength; + uint8_t bDescriptorType; /* USB_DT_CS_INTERFACE */ + uint8_t bDescriptorSubType; /* USB_AC_INPUT_TERMINAL */ + uint8_t bTerminalId; + uint16_t wTerminalType; /* USB_AC_INPUT_TERMINAL_* */ + uint8_t bAssocTerminal; + uint8_t bNrChannels; + uint16_t wChannelConfig; + uint8_t iChannelNames; + uint8_t iTerminal; +} __attribute__ ((packed)); + +#define USB_AC_OUTPUT_TERMINAL_UNDEFINED 0x300 +#define USB_AC_OUTPUT_TERMINAL_SPEAKER 0x301 +#define USB_AC_OUTPUT_TERMINAL_HEADPHONES 0x302 +#define USB_AC_OUTPUT_TERMINAL_HEAD_MOUNTED_DISPLAY_AUDIO 0x303 +#define USB_AC_OUTPUT_TERMINAL_DESKTOP_SPEAKER 0x304 +#define USB_AC_OUTPUT_TERMINAL_ROOM_SPEAKER 0x305 +#define USB_AC_OUTPUT_TERMINAL_COMMUNICATION_SPEAKER 0x306 +#define USB_AC_OUTPUT_TERMINAL_LOW_FREQ_EFFECTS_SPEAKER 0x307 + +struct usb_ac_output_terminal { + uint8_t bLength; + uint8_t bDescriptorType; /* USB_DT_CS_INTERFACE */ + uint8_t bDescriptorSubType; /* USB_AC_OUTPUT_TERMINAL */ + uint8_t bTerminalId; + uint16_t wTerminalType; /* USB_AC_OUTPUT_TERMINAL_* */ + uint8_t bAssocTerminal; + uint8_t bSourceId; + uint8_t iTerminal; +} __attribute__ ((packed)); + +#define USB_AC_FU_CONTROL_UNDEFINED 0x00 +#define USB_AC_MUTE_CONTROL 0x01 +#define USB_AC_VOLUME_CONTROL 0x02 +#define USB_AC_BASS_CONTROL 0x03 +#define USB_AC_MID_CONTROL 0x04 +#define USB_AC_TREBLE_CONTROL 0x05 +#define USB_AC_EQUALIZER_CONTROL 0x06 +#define USB_AC_AUTO_GAIN_CONTROL 0x07 +#define USB_AC_DELAY_CONTROL 0x08 +#define USB_AC_BASS_BOOST_CONTROL 0x09 +#define USB_AC_LOUDNESS_CONTROL 0x0a + +#define USB_AC_FU_MUTE (1 << (USB_AC_MUTE_CONTROL - 1)) +#define USB_AC_FU_VOLUME (1 << (USB_AC_VOLUME_CONTROL - 1)) +#define USB_AC_FU_BASS (1 << (USB_AC_BASS_CONTROL - 1)) +#define USB_AC_FU_MID (1 << (USB_AC_MID_CONTROL - 1)) +#define USB_AC_FU_TREBLE (1 << (USB_AC_TREBLE_CONTROL - 1)) +#define USB_AC_FU_EQUILIZER (1 << (USB_AC_EQUALIZER_CONTROL - 1)) +#define USB_AC_FU_AUTO_GAIN (1 << (USB_AC_AUTO_GAIN_CONTROL - 1)) +#define USB_AC_FU_DELAY (1 << (USB_AC_DELAY_CONTROL - 1)) +#define USB_AC_FU_BASS_BOOST (1 << (USB_AC_BASS_BOOST_CONTROL - 1)) +#define USB_AC_FU_LOUDNESS (1 << (USB_AC_LOUDNESS_CONTROL - 1)) + +#define DEFINE_USB_AC_FEATURE_UNIT(n,ch) \ +struct usb_ac_feature_unit_##n##_##ch { \ + uint8_t bLength; \ + uint8_t bDescriptorType; /* USB_DT_CS_INTERFACE */ \ + uint8_t bDescriptorSubType; /* USB_AC_FEATURE_UNIT */ \ + uint8_t bUnitId; \ + uint8_t bSourceId; \ + uint8_t bControlSize; \ + uint##n##_t bmaControls[ch + 1]; /* FU_* ORed*/ \ + uint8_t iFeature; \ +} __attribute__ ((packed)); + +#define USB_AC_SET_REQ 0x00 +#define USB_AC_GET_REQ 0x80 + +#define USB_AC_CUR_REQ 0x1 +#define USB_AC_MIN_REQ 0x2 +#define USB_AC_MAX_REQ 0x3 +#define USB_AC_RES_REQ 0x4 +#define USB_AC_MEM_REQ 0x5 + +#define USB_AC_SET_CUR (USB_AC_SET_REQ | USB_AC_CUR_REQ) +#define USB_AC_GET_CUR (USB_AC_GET_REQ | USB_AC_CUR_REQ) +#define USB_AC_SET_MIN (USB_AC_SET_REQ | USB_AC_MIN_REQ) +#define USB_AC_GET_MIN (USB_AC_GET_REQ | USB_AC_MIN_REQ) +#define USB_AC_SET_MAX (USB_AC_SET_REQ | USB_AC_MAX_REQ) +#define USB_AC_GET_MAX (USB_AC_GET_REQ | USB_AC_MAX_REQ) +#define USB_AC_SET_RES (USB_AC_SET_REQ | USB_AC_RES_REQ) +#define USB_AC_GET_RES (USB_AC_GET_REQ | USB_AC_RES_REQ) +#define USB_AC_SET_MEM (USB_AC_SET_REQ | USB_AC_MEM_REQ) +#define USB_AC_GET_MEM (USB_AC_GET_REQ | USB_AC_MEM_REQ) +#define USB_AC_GET_STAT 0xff + +#define USB_AS_FORMAT_TYPE_UNDEFINED 0x0 +#define USB_AS_FORMAT_TYPE_I 0x1 +#define USB_AS_FORMAT_TYPE_II 0x2 +#define USB_AS_FORMAT_TYPE_III 0x3 + +struct usb_as_interface { + uint8_t bLength; + uint8_t bDescriptorType; /* USB_DT_CS_INTERFACE */ + uint8_t bDescriptorSubType; /* USB_AS_GENERAL */ + uint8_t bTerminalLink; + uint8_t bDelay; + uint16_t wFormatTag; +} __attribute__ ((packed)); + +#define USB_AS_EP_GENERAL 0x01 + +#define USB_AS_EP_CS_SAMPLING_FREQ_CTL 0x01 +#define USB_AS_EP_CS_PITCH_CTL 0x02 + +struct usb_iso_audio_endpoint_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + uint8_t bRefresh; + uint8_t bSynchAddress; +} __attribute__ ((packed)); + +struct usb_as_iso_endpoint { + uint8_t bLength; + uint8_t bDescriptorType; /* USB_DT_CS_ENDPOINT */ + uint8_t bDescriptorSubType; /* USB_AS_EP_GENERAL */ + uint8_t bmAttributes; + uint8_t bLockDelayUnits; + uint16_t wLockDelay; +} __attribute__ ((packed)); + +#define USB_AS_FORMAT_TYPE_I_UNDEFINED 0x0 +#define USB_AS_FORMAT_TYPE_I_PCM 0x1 +#define USB_AS_FORMAT_TYPE_I_PCM8 0x2 +#define USB_AS_FORMAT_TYPE_I_IEEE_FLOAT 0x3 +#define USB_AS_FORMAT_TYPE_I_ALAW 0x4 +#define USB_AS_FORMAT_TYPE_I_MULAW 0x5 + +struct usb_as_format_type_i_discrete { + uint8_t bLength; + uint8_t bDescriptorType; /* USB_DT_CS_INTERFACE */ + uint8_t bDescriptorSubType; /* USB_AS_FORMAT_TYPE */ + uint8_t bFormatType; /* USB_AS_FORMAT_TYPE_I */ + uint8_t bNrChannels; + uint8_t bSubframeSize; + uint8_t bBitResolution; + uint8_t bSamFreqType; /* Number of discrete frequencies */ + uint8_t tSamFreq[][3]; +} __attribute__ ((packed)); + +#define USB_AS_SIZEOF_FORMAT_TYPE_I_DISCRETE(n) (8 + (n * 3)) + +#endif /* USB_AUDIO_DEF_H */ diff --git a/firmware/usbstack/usb_class_driver.h b/firmware/usbstack/usb_class_driver.h index bffc994d9e..8c4d28409c 100644 --- a/firmware/usbstack/usb_class_driver.h +++ b/firmware/usbstack/usb_class_driver.h @@ -74,7 +74,16 @@ struct usb_class_driver { /* Tells the driver that a usb transfer has been completed. Note that "dir" is relative to the host Optional function */ - void (*transfer_complete)(int ep,int dir, int status, int length); + void (*transfer_complete)(int ep, int dir, int status, int length); + + /* Similar to transfer_complete but called directly instead of going through + * the usb queue. Since it might be called in an interrupt context, + * processing should be kept to a minimum. This is mainly intended for + * isochronous transfers. + * The function must return true if it handled the completion, and false + * otherwise so that it is dispatched to the normal handler + * Optional function */ + bool (*fast_transfer_complete)(int ep, int dir, int status, int length); /* Tells the driver that a control request has come in. If the driver is able to handle it, it should ack the request, and return true. Otherwise @@ -88,6 +97,16 @@ struct usb_class_driver { Optional function */ void (*notify_hotswap)(int volume, bool inserted); #endif + + /* Tells the driver to select an alternate setting for a specific interface. + * Returns 0 on success and -1 on error. + * Mandatory function if alternate interface support is needed */ + int (*set_interface)(int interface, int alt_setting); + + /* Asks the driver what is the current alternate setting for a specific interface. + * Returns value on success and -1 on error. + * Mandatory function if alternate interface support is needed */ + int (*get_interface)(int interface); }; #define PACK_DATA(dest, data) pack_data(dest, &(data), sizeof(data)) diff --git a/firmware/usbstack/usb_core.c b/firmware/usbstack/usb_core.c index 82592b0c53..0a670d43ce 100644 --- a/firmware/usbstack/usb_core.c +++ b/firmware/usbstack/usb_core.c @@ -48,6 +48,11 @@ #include "usb_hid.h" #endif +#ifdef USB_ENABLE_AUDIO +#include "usb_audio.h" +#include "usb_audio_def.h" // DEBUG +#endif + /* TODO: Move target-specific stuff somewhere else (serial number reading) */ #if defined(IPOD_ARCH) && defined(CPU_PP) @@ -166,12 +171,14 @@ static int usb_no_host_callback(struct timeout *tmo) static int usb_core_num_interfaces; typedef void (*completion_handler_t)(int ep, int dir, int status, int length); +typedef bool (*fast_completion_handler_t)(int ep, int dir, int status, int length); typedef bool (*control_handler_t)(struct usb_ctrlrequest* req, void* reqdata, unsigned char* dest); static struct { completion_handler_t completion_handler[2]; + fast_completion_handler_t fast_completion_handler[2]; control_handler_t control_handler[2]; struct usb_transfer_completion_event_data completion_event[2]; } ep_data[USB_NUM_ENDPOINTS]; @@ -254,6 +261,28 @@ static struct usb_class_driver drivers[USB_NUM_DRIVERS] = #endif }, #endif +#ifdef USB_ENABLE_AUDIO + [USB_DRIVER_AUDIO] = { + .enabled = false, + .needs_exclusive_storage = false, + .first_interface = 0, + .last_interface = 0, + .request_endpoints = usb_audio_request_endpoints, + .set_first_interface = usb_audio_set_first_interface, + .get_config_descriptor = usb_audio_get_config_descriptor, + .init_connection = usb_audio_init_connection, + .init = usb_audio_init, + .disconnect = usb_audio_disconnect, + .transfer_complete = usb_audio_transfer_complete, + .fast_transfer_complete = usb_audio_fast_transfer_complete, + .control_request = usb_audio_control_request, +#ifdef HAVE_HOTSWAP + .notify_hotswap = NULL, +#endif + .set_interface = usb_audio_set_interface, + .get_interface = usb_audio_get_interface, + }, +#endif }; #ifdef USB_LEGACY_CONTROL_API @@ -542,6 +571,7 @@ int usb_core_request_endpoint(int type, int dir, struct usb_class_driver* drv) ep = EP_NUM(ret); ep_data[ep].completion_handler[dir] = drv->transfer_complete; + ep_data[ep].fast_completion_handler[dir] = drv->fast_transfer_complete; ep_data[ep].control_handler[dir] = drv->control_request; return ret; @@ -598,16 +628,46 @@ static void control_request_handler_drivers(struct usb_ctrlrequest* req, void* r if(drivers[i].enabled && drivers[i].control_request && drivers[i].first_interface <= interface && - drivers[i].last_interface > interface) - { + drivers[i].last_interface > interface) { + /* Check for SET_INTERFACE and GET_INTERFACE */ + if((req->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE && + (req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + + if(req->bRequest == USB_REQ_SET_INTERFACE) { + logf("usb_core: SET INTERFACE 0x%x 0x%x", req->wValue, req->wIndex); + if(drivers[i].set_interface && + drivers[i].set_interface(req->wIndex, req->wValue) >= 0) { + + usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); + handled = true; + } + break; + } + else if(req->bRequest == USB_REQ_GET_INTERFACE) { + int alt = -1; + logf("usb_core: GET INTERFACE 0x%x", req->wIndex); + + if(drivers[i].get_interface) + alt = drivers[i].get_interface(req->wIndex); + + if(alt >= 0 && alt < 255) { + response_data[0] = alt; + usb_drv_control_response(USB_CONTROL_ACK, response_data, 1); + handled = true; + } + break; + } + /* fallback */ + } + handled = drivers[i].control_request(req, reqdata, response_data); - if(handled) - break; + break; /* no other driver can handle it because it's interface specific */ } } if(!handled) { /* nope. flag error */ - logf("bad req:desc %d:%d", req->bRequest, req->wValue >> 8); + logf("bad req 0x%x:0x%x:0x%x:0x%x:0x%x", req->bRequestType,req->bRequest, + req->wValue, req->wIndex, req->wLength); usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); } } @@ -804,13 +864,9 @@ static void request_handler_interface_standard(struct usb_ctrlrequest* req, void { case USB_REQ_SET_INTERFACE: logf("usb_core: SET_INTERFACE"); - usb_drv_control_response(USB_CONTROL_ACK, NULL, 0); - break; - case USB_REQ_GET_INTERFACE: - logf("usb_core: GET_INTERFACE"); - response_data[0] = 0; - usb_drv_control_response(USB_CONTROL_ACK, response_data, 1); + control_request_handler_drivers(req, reqdata); + break; break; case USB_REQ_GET_STATUS: response_data[0] = 0; @@ -860,7 +916,8 @@ static void request_handler_endpoint_drivers(struct usb_ctrlrequest* req, void* if(!handled) { /* nope. flag error */ - logf("usb bad req %d", req->bRequest); + logf("bad req 0x%x:0x%x:0x%x:0x%x:0x%x", req->bRequestType,req->bRequest, + req->wValue, req->wIndex, req->wLength); usb_drv_control_response(USB_CONTROL_STALL, NULL, 0); } } @@ -970,6 +1027,10 @@ void usb_core_transfer_complete(int endpoint, int dir, int status, int length) { struct usb_transfer_completion_event_data* completion_event = &ep_data[endpoint].completion_event[EP_DIR(dir)]; + /* Fast notification */ + fast_completion_handler_t handler = ep_data[endpoint].fast_completion_handler[EP_DIR(dir)]; + if(handler != NULL && handler(endpoint, dir, status, length)) + return; /* do not dispatch to the queue if handled */ void* data0 = NULL; void* data1 = NULL;