Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sof isr #1402

Merged
merged 4 commits into from Mar 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
91 changes: 91 additions & 0 deletions src/class/audio/audio_device.c
Expand Up @@ -306,6 +306,15 @@ 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 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

#endif
#endif

Expand Down Expand Up @@ -421,6 +430,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);
Expand Down Expand Up @@ -1658,6 +1671,12 @@ 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;
audio->n_cycles_old = 0;
#endif

// Invoke callback after ep_out is set
if (audio->ep_out != 0)
{
Expand All @@ -1682,6 +1701,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;
Expand Down Expand Up @@ -1970,6 +2006,57 @@ 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
// 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_N = f_s << 13;
audio->feeback_param_factor_D = f_m * n_frame;
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_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

// 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->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;
}
}
}

#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
Expand Down Expand Up @@ -2247,7 +2334,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);

Expand Down
23 changes: 22 additions & 1 deletion src/class/audio/audio_device.h
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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
}
Expand Down
22 changes: 19 additions & 3 deletions src/device/usbd.c
Expand Up @@ -134,7 +134,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

Expand Down Expand Up @@ -590,7 +590,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;

Expand Down Expand Up @@ -1109,7 +1109,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
Expand Down Expand Up @@ -1397,4 +1408,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
5 changes: 4 additions & 1 deletion src/device/usbd_pvt.h
Expand Up @@ -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.
Expand Down Expand Up @@ -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
*------------------------------------------------------------------*/
Expand Down
32 changes: 28 additions & 4 deletions src/portable/synopsys/dwc2/dcd_dwc2.c
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
}

/*------------------------------------------------------------------*/
Expand Down Expand Up @@ -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);
}
Expand Down