mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-12-06 13:15:25 -05:00
Original commit credit to Amaury Pouly, Moshe Piekarski Pushed across the finish line by Dana Conrad To enable, see setting under General Settings --> System --> USB-DAC. On devices with few endpoints, this may not work while HID and/or mass storage is enabled. Adds new dedicated mixer channel. setting usb-dac can have values: - never (0) - always (1) - while_charge_only (2) - while_mass_storage (3) Relevant devices are DWC2 and ARC usb controller devices. That being: x1000 Native targets (m3k, erosqnative, q1, others...?), sansac200, creativezenxfi2, vibe500, ipodmini2g, ipod4g, creativezenxfi, creativezenxfi3, sansaview, ipodcolor, creativezenxfistyle, samsungypz5, sansafuzeplus, iriverh10_5gb, tatungtpj1022, gigabeats, faketarget, samsungyh820, gogearhdd1630, samsungyh925, ipodmini1g, ipodvideo, creativezenmozaic, sonynwze370, creativezen, gogearsa9200, gogearhdd6330, sonynwze360, sansae200, mrobe100, iriverh10, creativezenv, ipodnano1g, samsungyh920 USB Driver-wise, it should be noted that this patch requires some slight changes: - proper blocking on control OUT transfers, to make sure the data is received *before* using it, the usb_core should probably use that too - drivers can now support interface alternate settings - drivers can be notified of completion by a new fast handler, which is called directly from the driver; this is is necessary for isochronous transfers because going through the usb queue is way too slow Designware changes: - enable for USBOTG_DESIGNWARE - set maxpacketsize to 1023 for ISO endpoints Change-Id: I570871884a4e4820b4312b203b07701f06ecacc6
1105 lines
35 KiB
C
1105 lines
35 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 "<unknown>";
|
|
}
|
|
}
|
|
#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;
|
|
}
|