Add SOF callback function for feedback value determination in uac - wip!

This commit is contained in:
Reinhard Panhuber 2022-03-14 20:40:33 +01:00
parent 606f932d92
commit f212899b54
5 changed files with 156 additions and 9 deletions

View File

@ -306,6 +306,13 @@ typedef struct
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
uint32_t fb_val; // Feedback value for asynchronous mode (in 16.16 format).
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
uint8_t n_frames; // Number of (micro)frames used to estimate feedback value
uint8_t n_frames_current; // Current (micro)frame number
uint32_t feeback_param_factor; // TODO: Set this value within some new tud_audio_set_feedback_params_fm_fs function as feeback_param_factor = f_s / (f_cpu * n_frames)!
#endif
#endif
#endif
@ -421,6 +428,10 @@ static inline uint8_t tu_desc_subtype(void const* desc)
}
#endif
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
static bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback);
#endif
bool tud_audio_n_mounted(uint8_t func_id)
{
TU_VERIFY(func_id < CFG_TUD_AUDIO);
@ -1658,6 +1669,11 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
{
audio->ep_fb = ep_addr;
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
usbd_sof_enable(rhport, true); // Enable SOF interrupt
audio->n_frames_current = 0;
#endif
// Invoke callback after ep_out is set
if (audio->ep_out != 0)
{
@ -1682,6 +1698,23 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const *
p_desc = tu_desc_next(p_desc);
}
// Disable SOF interrupt if no driver has any enabled feedback EP
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP && CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
bool disable = true;
for(uint8_t i=0; i < CFG_TUD_AUDIO; i++)
{
if (_audiod_fct[i].ep_fb != 0)
{
disable = false;
}
}
if (disable) usbd_sof_enable(rhport, false);
#endif
tud_control_status(rhport, p_request);
return true;
@ -1970,6 +2003,52 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3
return false;
}
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP && CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
bool tud_audio_set_feedback_params_fm_fs(uint8_t func_id, uint32_t f_m, uint32_t f_s)
{
audiod_function_t* audio = &_audiod_fct[func_id];
uint8_t n_frame = 1; // TODO: finalize that
audio->n_frames = n_frame;
audio->feeback_param_factor = f_s / f_m / n_frame; // TODO: Check the 16.16 precision!
return true;
}
#endif
void audiod_sof (uint8_t rhport, uint32_t frame_count)
{
(void) rhport;
(void) frame_count;
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP && CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
// Determine feedback value - The feedback method is described in 5.12.4.2 of the USB 2.0 spec
// Boiled down, the feedback value Ff = n_samples / (micro)frame.
// Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_cpu) frames need to be measured, where K = 10 for full speed and K = 13 for high speed, f_s is the sampling frequency e.g. 48 kHz and f_cpu is the cpu clock frequency e.g. 100 MHz (or any other master clock whose clock count is available and locked to f_s)
// The update interval in the (4.10.2.1) Feedback Endpoint Descriptor must be less or equal to 2^(K - P), where P = min( ceil(log2(f_cpu / f_s)), K)
// Ff = n_cycles / n_frames * f_s / f_cpu in 16.16 format, where n_cycles are the number of CPU cycles within n_frames
// Iterate over audio functions and set feedback value
for(uint8_t i=0; i < CFG_TUD_AUDIO; i++)
{
audiod_function_t* audio = &_audiod_fct[i];
if (audio->ep_fb != 0)
{
audio->n_frames_current++;
if (audio->n_frames_current == audio->n_frames)
{
uint32_t n_cylces = tud_audio_n_get_fm_n_cycles_cb(rhport, audio->ep_fb);
uint32_t feedback = n_cylces * audio->feeback_param_factor;
tud_audio_n_fb_set(i, feedback);
audio->n_frames_current = 0;
}
}
}
#endif
}
bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len)
{
// Handles only sending of data not receiving
@ -2247,7 +2326,11 @@ static void audiod_parse_for_AS_params(audiod_function_t* audio, uint8_t const *
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
static bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback)
#else
bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback)
#endif
{
TU_VERIFY(func_id < CFG_TUD_AUDIO && _audiod_fct[func_id].p_desc != NULL);

View File

@ -191,6 +191,11 @@
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_FORMAT_CORRECTION 0 // 0 or 1
#endif
// Determine feedback value within SOF ISR within audio driver - if disabled the user has to call tud_audio_n_fb_set() with a suitable feedback value on its own. If done within audio driver SOF ISR, tud_audio_n_fb_set() is disabled for user
#ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
#define CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR 1 // 0 or 1
#endif
// Audio interrupt control EP size - disabled if 0
#ifndef CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN
#define CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN 0 // Audio interrupt control - if required - 6 Bytes according to UAC 2 specification (p. 74)
@ -468,8 +473,23 @@ TU_ATTR_WEAK bool tud_audio_fb_done_cb(uint8_t rhport);
//
// Note that due to a bug in its USB Audio 2.0 driver, Windows currently requires 16.16 format for _all_ USB 2.0 devices. On Linux and macOS it seems the
// driver can work with either format. So a good compromise is to keep format correction disabled and stick to 16.16 format.
// Feedback value can be determined from within the SOF ISR of the audio driver. This should reduce jitter. If the feature is used, the user can not set the feedback value.
# if !CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback);
static inline bool tud_audio_fb_set(uint32_t feedback);
# else
// This callback function is called once the feedback value needs to be updated within the SOF ISR in the audio class. To determine the feedback value, some
// parameters need to be given. The user must implement this callback function and provide the current cycle count of the master clock.
// The feedback endpoint number can be used to identify the correct audio function in case multiple audio functions were defined.
TU_ATTR_WEAK uint32_t tud_audio_n_get_fm_n_cycles_cb(uint8_t rhport, uint8_t ep_fb);
// f_m : Main clock frequency in Hz i.e. master clock to which sample clock is locked
// f_s : Current sample rate in Hz
bool tud_audio_set_feedback_params_fm_fs(uint8_t func_id, uint32_t f_m, uint32_t f_s);
#endif
#endif
#if CFG_TUD_AUDIO_INT_CTR_EPSIZE_IN
@ -611,7 +631,7 @@ static inline uint16_t tud_audio_int_ctr_write(uint8_t const* buffer, uint16_t l
}
#endif
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
#if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP && !CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR
static inline bool tud_audio_fb_set(uint32_t feedback)
{
return tud_audio_n_fb_set(0, feedback);
@ -626,6 +646,7 @@ void audiod_reset (uint8_t rhport);
uint16_t audiod_open (uint8_t rhport, tusb_desc_interface_t const * itf_desc, uint16_t max_len);
bool audiod_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
bool audiod_xfer_cb (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes);
void audiod_sof (uint8_t rhport, uint32_t frame_count);
#ifdef __cplusplus
}

View File

@ -139,7 +139,7 @@ static usbd_class_driver_t const _usbd_driver[] =
.open = audiod_open,
.control_xfer_cb = audiod_control_xfer_cb,
.xfer_cb = audiod_xfer_cb,
.sof = NULL
.sof = audiod_sof
},
#endif
@ -612,7 +612,7 @@ void tud_task (void)
for ( uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++ )
{
usbd_class_driver_t const * driver = get_driver(i);
if ( driver->sof ) driver->sof(event.rhport);
if ( driver->sof ) driver->sof(event.rhport, event.sof.frame_count);
}
break;
@ -1131,7 +1131,18 @@ void dcd_event_handler(dcd_event_t const * event, bool in_isr)
break;
case DCD_EVENT_SOF:
// SOF Handler
// SOF driver handler in ISR context
for (uint8_t i = 0; i < TOTAL_DRIVER_COUNT; i++)
{
usbd_class_driver_t const * driver = get_driver(i);
if (driver->sof)
{
driver->sof(event->rhport, event->sof.frame_count);
// TU_LOG2("%s sof\r\n", driver->name); // too demanding
}
}
// SOF user handler in ISR context
if (_sof_isr) _sof_isr(event->sof.frame_count);
// Some MCUs after running dcd_remote_wakeup() does not have way to detect the end of remote wakeup
@ -1441,4 +1452,9 @@ void usbd_edpt_close(uint8_t rhport, uint8_t ep_addr)
return;
}
void usbd_sof_enable(uint8_t rhport, bool en)
{
dcd_sof_enable(rhport, en);
}
#endif

View File

@ -48,7 +48,7 @@ typedef struct
uint16_t (* open ) (uint8_t rhport, tusb_desc_interface_t const * desc_intf, uint16_t max_len);
bool (* control_xfer_cb ) (uint8_t rhport, uint8_t stage, tusb_control_request_t const * request);
bool (* xfer_cb ) (uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes);
void (* sof ) (uint8_t rhport); /* optional */
void (* sof ) (uint8_t rhport, uint32_t frame_count); /* optional */
} usbd_class_driver_t;
// Invoked when initializing device stack to get additional class drivers.
@ -102,6 +102,9 @@ bool usbd_edpt_ready(uint8_t rhport, uint8_t ep_addr)
return !usbd_edpt_busy(rhport, ep_addr) && !usbd_edpt_stalled(rhport, ep_addr);
}
// Enable SOF interrupt
void usbd_sof_enable(uint8_t rhport, bool en);
/*------------------------------------------------------------------*/
/* Helper
*------------------------------------------------------------------*/

View File

@ -93,6 +93,9 @@ static uint16_t ep0_pending[2]; // Index determines direction
static uint16_t _allocated_fifo_words_tx; // TX FIFO size in words (IN EPs)
static bool _out_ep_closed; // Flag to check if RX FIFO size needs an update (reduce its size)
// SOF enabling flag - required for SOF to not get disabled in ISR when SOF was enabled by
static bool _sof_en;
// Calculate the RX FIFO size according to recommendations from reference manual
static inline uint16_t calc_rx_ff_size(uint16_t ep_size)
{
@ -126,6 +129,8 @@ static void bus_reset(uint8_t rhport)
tu_memclr(xfer_status, sizeof(xfer_status));
_out_ep_closed = false;
_sof_en = false;
// clear device address
dwc2->dcfg &= ~DCFG_DAD_Msk;
@ -588,12 +593,23 @@ void dcd_disconnect(uint8_t rhport)
dwc2->dctl |= DCTL_SDIS;
}
// Be advised: audio, video and possibly other iso-ep classes use dcd_sof_enable() to enable/disable its corresponding ISR on purpose!
void dcd_sof_enable(uint8_t rhport, bool en)
{
(void) rhport;
(void) en;
dwc2_regs_t * dwc2 = DWC2_REG(rhport);
// TODO implement later
_sof_en = en;
if (en)
{
dwc2->gintsts = GINTSTS_SOF;
dwc2->gintmsk |= GINTMSK_SOFM;
}
else
{
dwc2->gintmsk &= ~GINTMSK_SOFM;
}
}
/*------------------------------------------------------------------*/
@ -1258,8 +1274,16 @@ void dcd_int_handler(uint8_t rhport)
{
dwc2->gotgint = GINTSTS_SOF;
// Disable SOF interrupt since currently only used for remote wakeup detection
dwc2->gintmsk &= ~GINTMSK_SOFM;
if (_sof_en)
{
uint32_t frame = (dwc2->dsts & (USB_OTG_DSTS_FNSOF)) >> 8;
dcd_event_sof(rhport, frame, true);
}
else
{
// Disable SOF interrupt if SOF was not explicitly enabled. SOF was used for remote wakeup detection
dwc2->gintmsk &= ~GINTMSK_SOFM;
}
dcd_event_bus_signal(rhport, DCD_EVENT_SOF, true);
}