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;