From f212899b54740479eff225d89432d88f0a14f17e Mon Sep 17 00:00:00 2001 From: Reinhard Panhuber Date: Mon, 14 Mar 2022 20:40:33 +0100 Subject: [PATCH 1/3] Add SOF callback function for feedback value determination in uac - wip! --- src/class/audio/audio_device.c | 83 +++++++++++++++++++++++++++ src/class/audio/audio_device.h | 23 +++++++- src/device/usbd.c | 22 ++++++- src/device/usbd_pvt.h | 5 +- src/portable/synopsys/dwc2/dcd_dwc2.c | 32 +++++++++-- 5 files changed, 156 insertions(+), 9 deletions(-) diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 06979b09ec..0828f2e6ff 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -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); diff --git a/src/class/audio/audio_device.h b/src/class/audio/audio_device.h index f406cf2811..c9d711a364 100644 --- a/src/class/audio/audio_device.h +++ b/src/class/audio/audio_device.h @@ -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 } diff --git a/src/device/usbd.c b/src/device/usbd.c index 8662d5b84d..170342072e 100644 --- a/src/device/usbd.c +++ b/src/device/usbd.c @@ -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 diff --git a/src/device/usbd_pvt.h b/src/device/usbd_pvt.h index dae95cebbe..f9fb543df2 100644 --- a/src/device/usbd_pvt.h +++ b/src/device/usbd_pvt.h @@ -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 *------------------------------------------------------------------*/ diff --git a/src/portable/synopsys/dwc2/dcd_dwc2.c b/src/portable/synopsys/dwc2/dcd_dwc2.c index 37712d2d90..d92b17703b 100644 --- a/src/portable/synopsys/dwc2/dcd_dwc2.c +++ b/src/portable/synopsys/dwc2/dcd_dwc2.c @@ -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); + + _sof_en = en; - // TODO implement later + 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); } From c9b444e771e138ee50ed8cbff67d895722b46008 Mon Sep 17 00:00:00 2001 From: Reinhard Panhuber Date: Tue, 15 Mar 2022 20:30:31 +0100 Subject: [PATCH 2/3] Implement 16.16 fixed point feedback value calculation --- src/class/audio/audio_device.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 0828f2e6ff..5ca95658e8 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -310,7 +310,8 @@ typedef struct #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)! + uint32_t feeback_param_factor_N; // Numerator of feedback parameter coefficient + uint32_t feeback_param_factor_D; // Denominator of feedback parameter coefficient #endif #endif @@ -2004,12 +2005,16 @@ bool audiod_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint3 } #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP && CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR +// f_s max is 2^19-1 = 524287 Hz +// n_frames_min is ceil(2^10 * f_s / f_m) for full speed and ceil(2^13 * f_s / f_m) for high speed +// f_m max is 2^29/(1 ms * n_frames) for full speed and 2^29/(125 us * n_frames) for high speed 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! + audio->feeback_param_factor_N = f_s << 13; + audio->feeback_param_factor_D = f_m * n_frame; return true; } #endif @@ -2023,7 +2028,7 @@ void audiod_sof (uint8_t rhport, uint32_t frame_count) // 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) + // Since an accuracy of less than 1 Sample / second is desired, at least n_frames = ceil(2^K * f_s / f_m) 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 @@ -2038,7 +2043,7 @@ void audiod_sof (uint8_t rhport, uint32_t frame_count) 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; + uint32_t feedback = (n_cylces << 3) * audio->feeback_param_factor_N / audio->feeback_param_factor_D; // feeback_param_factor_N has scaling factor of 13 bits, n_cycles 3 and feeback_param_factor_D 1, hence 16.16 precision tud_audio_n_fb_set(i, feedback); audio->n_frames_current = 0; From 90502739c34b8cbcf37229031eb4f575c33311fa Mon Sep 17 00:00:00 2001 From: Reinhard Panhuber Date: Tue, 15 Mar 2022 20:45:06 +0100 Subject: [PATCH 3/3] Fix cycle count calculation --- src/class/audio/audio_device.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/class/audio/audio_device.c b/src/class/audio/audio_device.c index 5ca95658e8..1dbeb7643a 100644 --- a/src/class/audio/audio_device.c +++ b/src/class/audio/audio_device.c @@ -310,6 +310,7 @@ typedef struct #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 n_cycles_old; // Old cycle count uint32_t feeback_param_factor_N; // Numerator of feedback parameter coefficient uint32_t feeback_param_factor_D; // Denominator of feedback parameter coefficient #endif @@ -1673,6 +1674,7 @@ static bool audiod_set_interface(uint8_t rhport, tusb_control_request_t const * #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_DETERMINATION_WITHIN_SOF_ISR usbd_sof_enable(rhport, true); // Enable SOF interrupt audio->n_frames_current = 0; + audio->n_cycles_old = 0; #endif // Invoke callback after ep_out is set @@ -2043,10 +2045,11 @@ void audiod_sof (uint8_t rhport, uint32_t frame_count) 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 << 3) * audio->feeback_param_factor_N / audio->feeback_param_factor_D; // feeback_param_factor_N has scaling factor of 13 bits, n_cycles 3 and feeback_param_factor_D 1, hence 16.16 precision + uint32_t feedback = ((n_cylces - audio->n_cycles_old) << 3) * audio->feeback_param_factor_N / audio->feeback_param_factor_D; // feeback_param_factor_N has scaling factor of 13 bits, n_cycles 3 and feeback_param_factor_D 1, hence 16.16 precision tud_audio_n_fb_set(i, feedback); audio->n_frames_current = 0; + audio->n_cycles_old = n_cylces; } } }