/* * SPDX-FileCopyrightText: Copyright (c) 2020 Ha Thach (tinyusb.org) * SPDX-FileCopyrightText: Copyright (c) 2020 Reinhard Panhuber * SPDX-FileCopyrightText: Copyright (c) 2023 HiFiPhile (Zixun LI) * SPDX-License-Identifier: MIT * * This file is part of the TinyUSB stack. */ #ifndef TUSB_AUDIO_DEVICE_H_ #define TUSB_AUDIO_DEVICE_H_ #include "audio.h" //--------------------------------------------------------------------+ // Class Driver Configuration //--------------------------------------------------------------------+ // All sizes are in bytes! // Size of control buffer used to receive and send control messages via EP0 - has to be big enough to hold your // biggest request structure e.g. range requests with multiple intervals defined or cluster descriptors #ifndef CFG_TUD_AUDIO_CTRL_BUF_SZ #define CFG_TUD_AUDIO_CTRL_BUF_SZ 64 #endif // End point sizes IN BYTES - Limits: Full Speed <= 1023, High Speed <= 1024 #ifndef CFG_TUD_AUDIO_ENABLE_EP_IN #define CFG_TUD_AUDIO_ENABLE_EP_IN 0 // TX #endif #ifndef CFG_TUD_AUDIO_ENABLE_EP_OUT #define CFG_TUD_AUDIO_ENABLE_EP_OUT 0 // RX #endif // Maximum EP sizes for all alternate AS interface settings - used for checks and buffer allocation #if CFG_TUD_AUDIO_ENABLE_EP_IN #ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX #error You must tell the driver the biggest EP IN size! #endif #if CFG_TUD_AUDIO > 1 #ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX #error You must tell the driver the biggest EP IN size! #endif #endif #if CFG_TUD_AUDIO > 2 #ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX #error You must tell the driver the biggest EP IN size! #endif #endif #endif // CFG_TUD_AUDIO_ENABLE_EP_IN #if CFG_TUD_AUDIO_ENABLE_EP_OUT #ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX #error You must tell the driver the biggest EP OUT size! #endif #if CFG_TUD_AUDIO > 1 #ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX #error You must tell the driver the biggest EP OUT size! #endif #endif #if CFG_TUD_AUDIO > 2 #ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX #error You must tell the driver the biggest EP OUT size! #endif #endif #endif // CFG_TUD_AUDIO_ENABLE_EP_OUT // Software EP FIFO buffer sizes - must be >= max EP SIZEs! #ifndef CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ #define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ 0 #endif #ifndef CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ #define CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ 0 #endif #ifndef CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ #define CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ 0 #endif #ifndef CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ #define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ 0 #endif #ifndef CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ #define CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ 0 #endif #ifndef CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ #define CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ 0 #endif #if CFG_TUD_AUDIO_ENABLE_EP_IN #if CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX #error EP software buffer size MUST BE at least as big as maximum EP size #endif #if CFG_TUD_AUDIO > 1 #if CFG_TUD_AUDIO_FUNC_2_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_IN_SZ_MAX #error EP software buffer size MUST BE at least as big as maximum EP size #endif #endif #if CFG_TUD_AUDIO > 2 #if CFG_TUD_AUDIO_FUNC_3_EP_IN_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_IN_SZ_MAX #error EP software buffer size MUST BE at least as big as maximum EP size #endif #endif #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT #if CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX #error EP software buffer size MUST BE at least as big as maximum EP size #endif #if CFG_TUD_AUDIO > 1 #if CFG_TUD_AUDIO_FUNC_2_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_2_EP_OUT_SZ_MAX #error EP software buffer size MUST BE at least as big as maximum EP size #endif #endif #if CFG_TUD_AUDIO > 2 #if CFG_TUD_AUDIO_FUNC_3_EP_OUT_SW_BUF_SZ < CFG_TUD_AUDIO_FUNC_3_EP_OUT_SZ_MAX #error EP software buffer size MUST BE at least as big as maximum EP size #endif #endif #endif // (For TYPE-I format only) Flow control is necessary to allow IN ep send correct amount of data, unless it's a // virtual device where data is perfectly synchronized to USB clock. #ifndef CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL #define CFG_TUD_AUDIO_EP_IN_FLOW_CONTROL 1 #endif // Enable/disable feedback EP (required for asynchronous RX applications) #ifndef CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP #define CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP 0 // Feedback - 0 or 1 #endif // Enable/disable interrupt EP (required for notifying host of control changes) #ifndef CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP #define CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP 0 // Feedback - 0 or 1 #endif // Audio control interrupt EP - 6 Bytes according to UAC 2 specification (p. 74) #define CFG_TUD_AUDIO_INTERRUPT_EP_SZ 6 #ifdef __cplusplus extern "C" { #endif /** \addtogroup AUDIO_Serial Serial * @{ * \defgroup AUDIO_Serial_Device Device * @{ */ //--------------------------------------------------------------------+ // Application API (Multiple Interfaces) // CFG_TUD_AUDIO > 1 //--------------------------------------------------------------------+ bool tud_audio_n_mounted(uint8_t func_id); uint8_t tud_audio_n_version(uint8_t func_id); #if CFG_TUD_AUDIO_ENABLE_EP_OUT uint16_t tud_audio_n_available (uint8_t func_id); uint16_t tud_audio_n_read (uint8_t func_id, void* buffer, uint16_t bufsize); bool tud_audio_n_clear_ep_out_ff (uint8_t func_id); tu_fifo_t* tud_audio_n_get_ep_out_ff (uint8_t func_id); #endif #if CFG_TUD_AUDIO_ENABLE_EP_IN uint16_t tud_audio_n_write (uint8_t func_id, const void * data, uint16_t len); bool tud_audio_n_clear_ep_in_ff (uint8_t func_id); tu_fifo_t* tud_audio_n_get_ep_in_ff (uint8_t func_id); uint16_t tud_audio_n_get_ep_in_fifo_threshold(uint8_t func_id); void tud_audio_n_set_ep_in_fifo_threshold(uint8_t func_id, uint16_t threshold); #endif #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP bool tud_audio_int_n_write (uint8_t func_id, const audio_interrupt_data_t * data); #endif //--------------------------------------------------------------------+ // Application API (Interface0) //--------------------------------------------------------------------+ static inline bool tud_audio_mounted (void); static inline uint8_t tud_audio_version (void); #if CFG_TUD_AUDIO_ENABLE_EP_OUT static inline uint16_t tud_audio_available (void); static inline bool tud_audio_clear_ep_out_ff (void); static inline uint16_t tud_audio_read (void* buffer, uint16_t bufsize); static inline tu_fifo_t* tud_audio_get_ep_out_ff (void); #endif #if CFG_TUD_AUDIO_ENABLE_EP_IN static inline uint16_t tud_audio_write (const void * data, uint16_t len); static inline bool tud_audio_clear_ep_in_ff (void); static inline tu_fifo_t* tud_audio_get_ep_in_ff (void); #endif // INT CTR API #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP static inline bool tud_audio_int_write (const audio_interrupt_data_t * data); #endif // Buffer control EP data and schedule a transmit // This function is intended to be used if you do not have a persistent buffer or memory location available // (e.g. non-local variables) and need to answer onto a get request. This function buffers your answer request // frame into the control buffer of the corresponding audio driver and schedules a transmit for sending it. // Since transmission is triggered via interrupts, a persistent memory location is required onto which the buffer // pointer in pointing. If you already have such available you may directly use 'tud_control_xfer(...)'. In this // case data does not need to be copied into an additional buffer and you save some time. // If the request's wLength is zero, a status packet is sent instead. bool tud_audio_buffer_and_schedule_control_xfer(uint8_t rhport, tusb_control_request_t const * p_request, void* data, uint16_t len); //--------------------------------------------------------------------+ // Application Callback API //--------------------------------------------------------------------+ #if CFG_TUD_AUDIO_ENABLE_EP_IN // Invoked in ISR context once an audio packet was sent successfully. // Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), // call tud_audio_write() in I2S receive callback. bool tud_audio_tx_done_isr(uint8_t rhport, uint16_t n_bytes_sent, uint8_t func_id, uint8_t ep_in, uint8_t cur_alt_setting); #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT // Invoked in ISR context once an audio packet was received successfully. // Normally this function is not needed, since the data transfer should be driven by audio clock (i.e. I2S clock), // call tud_audio_read() in I2S transmit callback. bool tud_audio_rx_done_isr(uint8_t rhport, uint16_t n_bytes_received, uint8_t func_id, uint8_t ep_out, uint8_t cur_alt_setting); #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP // Note about feedback calculation // // Option 1 - AUDIO_FEEDBACK_METHOD_FIFO_COUNT // Feedback value is calculated within the audio driver by regulating the FIFO level to half fill. // Advantage: No SOF interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus // less CPU load, well tested (Windows, Linux, OSX) with a reliable result so far. // Disadvantage: A FIFO of minimal 4 frames is needed to compensate for jitter, an average delay of 2 frames is // introduced. // // Option 2 - AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED / AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT // Feedback value is calculated within the audio driver by use of SOF interrupt. The driver needs information // about the master clock f_m from which the audio sample frequency f_s is derived, f_s itself, and the cycle // count of f_m at time of the SOF interrupt (e.g. by use of a hardware counter). // See tud_audio_set_fb_params() and tud_audio_feedback_update() // Advantage: Reduced jitter in the feedback value computation, hence, the receive FIFO can be smaller and thus a // smaller delay is possible. // Disadvantage: higher CPU load due to SOF ISR handling every frame i.e. 1ms or 125us. (The most critical point // is the reading of the cycle counter value of f_m. It is read from within the SOF ISR - see: audiod_sof() -, // hence, the ISR must has a high priority such that no software dependent "random" delay i.e. jitter is // introduced). Long-term drift will cause the FIFO under/overflow, you still needs to correct it somehow. // // Option 3 - manual // Determined by the user itself and set by use of tud_audio_n_fb_set(). The feedback value may be determined // e.g. from some fill status of some FIFO buffer. // Advantage: No ISR interrupt is enabled, hence the CPU need not to handle an ISR every 1ms or 125us and thus // less CPU load. // Disadvantage: typically a larger FIFO is needed to compensate for jitter (e.g. 6 frames), i.e. a larger delay // is introduced. // This function is used to provide data rate feedback from an asynchronous sink. Feedback value will be sent at // FB endpoint interval till it's changed. // // The feedback format is specified to be 16.16 for HS and 10.14 for FS devices (see Universal Serial Bus // Specification Revision 2.0 5.12.4.2). For simplicity, this function always uses 16.16 format. For FS devices, // the driver will automatically convert the value to 10.14 format. // // 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 it seems the driver can work with either 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. // // 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_m 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_m / f_s)), K) // feedback = n_cycles / n_frames * f_s / f_m in 16.16 format, where n_cycles are the number of main clock cycles // within fb_n_frames bool tud_audio_n_fb_set(uint8_t func_id, uint32_t feedback); // Update feedback value with passed MCLK cycles since last time this update function is called. // Typically called within tud_audio_sof_isr(). Required tud_audio_feedback_params_cb() is implemented // This function will also call tud_audio_feedback_set() // return feedback value in 16.16 for reference (0 for error) // Example : // binterval=3 (4ms); FS = 48kHz; MCLK = 12.288MHz // In 4 SOF MCLK counted 49152 cycles uint32_t tud_audio_feedback_update(uint8_t func_id, uint32_t cycles); enum { AUDIO_FEEDBACK_METHOD_DISABLED, AUDIO_FEEDBACK_METHOD_FREQUENCY_FIXED, AUDIO_FEEDBACK_METHOD_FREQUENCY_FLOAT, AUDIO_FEEDBACK_METHOD_FREQUENCY_POWER_OF_2, // For driver internal use only AUDIO_FEEDBACK_METHOD_FIFO_COUNT }; typedef struct { uint8_t method; uint32_t sample_freq; // sample frequency in Hz union { struct { uint32_t mclk_freq; // Main clock frequency in Hz i.e. master clock to which sample clock is based on } frequency; struct { uint16_t fifo_threshold; // Target FIFO threshold level, default to half FIFO if not set } fifo_count; }; } audio_feedback_params_t; // Invoked when needed to set feedback parameters void tud_audio_feedback_params_cb(uint8_t func_id, uint8_t alt_itf, audio_feedback_params_t* feedback_param); // Callback in ISR context, invoked periodically according to feedback endpoint bInterval. // Could be used to compute and update feedback value, should be placed in RAM if possible // frame_number : current SOF count // interval_shift: number of bit shift i.e log2(interval) from Feedback endpoint descriptor TU_ATTR_FAST_FUNC void tud_audio_feedback_interval_isr(uint8_t func_id, uint32_t frame_number, uint8_t interval_shift); #endif // CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP // Invoked when an interrupt notification transfer is complete void tud_audio_int_done_cb(uint8_t rhport); #endif // Invoked when audio set interface request received bool tud_audio_set_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); // Invoked when audio set interface request received which closes an EP bool tud_audio_set_itf_close_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); // backward compatible for typo #define tud_audio_set_itf_close_EP_cb tud_audio_set_itf_close_ep_cb // Invoked when audio class specific set request received for an EP bool tud_audio_set_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); // Invoked when audio class specific set request received for an interface bool tud_audio_set_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); // Invoked when audio class specific set request received for an entity bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff); // Invoked when audio class specific get request received for an EP bool tud_audio_get_req_ep_cb(uint8_t rhport, tusb_control_request_t const * p_request); // Invoked when audio class specific get request received for an interface bool tud_audio_get_req_itf_cb(uint8_t rhport, tusb_control_request_t const * p_request); // Invoked when audio class specific get request received for an entity bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request); //--------------------------------------------------------------------+ // Inline Functions //--------------------------------------------------------------------+ TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_mounted(void) { return tud_audio_n_mounted(0); } TU_ATTR_ALWAYS_INLINE static inline uint8_t tud_audio_version(void) { return tud_audio_n_version(0); } #if CFG_TUD_AUDIO_ENABLE_EP_OUT TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_available(void) { return tud_audio_n_available(0); } TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_read(void* buffer, uint16_t bufsize) { return tud_audio_n_read(0, buffer, bufsize); } TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_out_ff(void) { return tud_audio_n_clear_ep_out_ff(0); } TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_out_ff(void) { return tud_audio_n_get_ep_out_ff(0); } #endif #if CFG_TUD_AUDIO_ENABLE_EP_IN TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_write(const void * data, uint16_t len) { return tud_audio_n_write(0, data, len); } TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_clear_ep_in_ff(void) { return tud_audio_n_clear_ep_in_ff(0); } TU_ATTR_ALWAYS_INLINE static inline tu_fifo_t* tud_audio_get_ep_in_ff(void) { return tud_audio_n_get_ep_in_ff(0); } TU_ATTR_ALWAYS_INLINE static inline uint16_t tud_audio_get_ep_in_fifo_threshold(void) { return tud_audio_n_get_ep_in_fifo_threshold(0); } TU_ATTR_ALWAYS_INLINE static inline void tud_audio_set_ep_in_fifo_threshold(uint16_t threshold) { tud_audio_n_set_ep_in_fifo_threshold(0, threshold); } #endif #if CFG_TUD_AUDIO_ENABLE_INTERRUPT_EP TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_int_write(const audio_interrupt_data_t * data) { return tud_audio_int_n_write(0, data); } #endif #if CFG_TUD_AUDIO_ENABLE_EP_OUT && CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP TU_ATTR_ALWAYS_INLINE static inline bool tud_audio_fb_set(uint32_t feedback) { return tud_audio_n_fb_set(0, feedback); } #endif //--------------------------------------------------------------------+ // Internal Class Driver API //--------------------------------------------------------------------+ void audiod_init (void); bool audiod_deinit (void); 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); bool audiod_xfer_isr (uint8_t rhport, uint8_t edpt_addr, xfer_result_t result, uint32_t xferred_bytes); void audiod_sof_isr (uint8_t rhport, uint32_t frame_count); #ifdef __cplusplus } #endif #endif /* TUSB_AUDIO_DEVICE_H_ */ /** @} */ /** @} */