From 8d2f13c203b689785f5e0a686eb2ae667b15c7c4 Mon Sep 17 00:00:00 2001 From: Emil Obalski Date: Tue, 18 Feb 2020 12:21:50 +0100 Subject: [PATCH] usb: Add USB audio implementation By this commit USB audio class implementation is introduced to Zephyr. The Zephyr USB audio device class follows bellow documentations: - Universal Serial Bus specification rev2.0 (usb20.pdf) - Universal Serial Bus Device Class Definition for Audio Devices (audio10.pdf) - Universal Serial Bus Device Class Definition for Audio Data Formats (frmts10.pdf) - Universal Serial Bus Device Class Definition for Terminal Types (termt10.pdf) Signed-off-by: Emil Obalski --- dts/bindings/usb/usb-audio-hp.yaml | 114 +++ dts/bindings/usb/usb-audio-hs.yaml | 228 +++++ dts/bindings/usb/usb-audio-mic.yaml | 126 +++ dts/bindings/usb/usb-audio.yaml | 13 + include/usb/class/usb_audio.h | 270 +++++- subsys/usb/class/audio/Kconfig | 11 +- subsys/usb/class/audio/audio.c | 967 ++++++++++++++++++++ subsys/usb/class/audio/usb_audio_internal.h | 525 +++++++++++ 8 files changed, 2250 insertions(+), 4 deletions(-) create mode 100644 dts/bindings/usb/usb-audio-hp.yaml create mode 100644 dts/bindings/usb/usb-audio-hs.yaml create mode 100644 dts/bindings/usb/usb-audio-mic.yaml create mode 100644 dts/bindings/usb/usb-audio.yaml create mode 100644 subsys/usb/class/audio/usb_audio_internal.h diff --git a/dts/bindings/usb/usb-audio-hp.yaml b/dts/bindings/usb/usb-audio-hp.yaml new file mode 100644 index 00000000000..f0349a92f18 --- /dev/null +++ b/dts/bindings/usb/usb-audio-hp.yaml @@ -0,0 +1,114 @@ +# Copyright (c) 2020 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Specific fields for USB audio headphones. + +description: USB Audio headphones specific fields. + +compatible: "usb-audio-hp" + +include: usb-audio.yaml + +properties: + resolution: + type: int + default: 16 + required: false + enum: + - 8 + - 16 + - 24 + - 32 +# channel configuration options + channel-l: + type: boolean + required: false + description: Enable (l) channel. + channel-r: + type: boolean + required: false + description: Enable (r) channel. + channel-c: + type: boolean + required: false + description: Enable (c) channel. + channel-lfe: + type: boolean + required: false + description: Enable (lfe) channel. + channel-ls: + type: boolean + required: false + description: Enable (ls) channel. + channel-rs: + type: boolean + required: false + description: Enable (rs) channel. + channel-lc: + type: boolean + required: false + description: Enable (lc) channel. + channel-rc: + type: boolean + required: false + description: Enable (rc) channel. + channel-s: + type: boolean + required: false + description: Enable (s) channel. + channel-sl: + type: boolean + required: false + description: Enable (sl) channel. + channel-sr: + type: boolean + required: false + description: Enable (sr) channel. + channel-t: + type: boolean + required: false + description: Enable (t) channel. + channel-cfg: + type: boolean + required: false + description: Enable (cfg) channel. +# feature unit configuration options + feature-mute: + type: boolean + required: true + description: Enable Mute feature. + feature-volume: + type: boolean + required: false + description: Enable Volume feature. + Currently not supported. + feature-tone-control: + type: boolean + required: false + description: Enable Tone Control (Bass, Mid, Treble) feature. + Currently not supported. + feature-graphic-equalizer: + type: boolean + required: false + description: Enable Graphic Equalizer feature. + Currently not supported. + feature-automatic-gain-control: + type: boolean + required: false + description: Enable Autoamtic Gain Control feature. + Currently not supported. + feature-delay: + type: boolean + required: false + description: Enable Delay feature. + Currently not supported. + feature-bass-boost: + type: boolean + required: false + description: Enable Bass Boost feature. + Currently not supported. + feature-loduness: + type: boolean + required: false + description: Enable Loudness feature. + Currently not supported. diff --git a/dts/bindings/usb/usb-audio-hs.yaml b/dts/bindings/usb/usb-audio-hs.yaml new file mode 100644 index 00000000000..e837e7ec970 --- /dev/null +++ b/dts/bindings/usb/usb-audio-hs.yaml @@ -0,0 +1,228 @@ +# Copyright (c) 2020 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Specific fields for USB audio headset. + +description: USB Audio headset specific fields. + +compatible: "usb-audio-hs" + +include: usb-audio.yaml + +properties: + mic-resolution: + type: int + default: 16 + required: false + enum: + - 8 + - 16 + - 24 + - 32 + mic-sync-type: + required: false + default: "Synchronous" + type: string + description: Type of endpoint synchronization for IN devices. + Default value is Sychronous. + Adaptive is not supported. + enum: + - "No Synchronization" + - "Asynchronous" + - "Adaptive" + - "Synchronous" + hp-resolution: + type: int + default: 16 + required: false + enum: + - 8 + - 16 + - 24 + - 32 +# microphone channel configuration options + mic-channel-l: + type: boolean + required: false + description: Enable (l) channel. + mic-channel-r: + type: boolean + required: false + description: Enable (r) channel. + mic-channel-c: + type: boolean + required: false + description: Enable (c) channel. + mic-channel-lfe: + type: boolean + required: false + description: Enable (lfe) channel. + mic-channel-ls: + type: boolean + required: false + description: Enable (ls) channel. + mic-channel-rs: + type: boolean + required: false + description: Enable (rs) channel. + mic-channel-lc: + type: boolean + required: false + description: Enable (lc) channel. + mic-channel-rc: + type: boolean + required: false + description: Enable (rc) channel. + mic-channel-s: + type: boolean + required: false + description: Enable (s) channel. + mic-channel-sl: + type: boolean + required: false + description: Enable (sl) channel. + mic-channel-sr: + type: boolean + required: false + description: Enable (sr) channel. + mic-channel-t: + type: boolean + required: false + description: Enable (t) channel. + mic-channel-cfg: + type: boolean + required: false + description: Enable (cfg) channel. +# microphone feature unit configuration options + mic-feature-mute: + type: boolean + required: true + description: Enable Mute feature. + mic-feature-volume: + type: boolean + required: false + description: Enable Volume feature. + Currently not supported. + mic-feature-tone-control: + type: boolean + required: false + description: Enable Tone Control (Bass, Mid, Treble) feature. + Currently not supported. + mic-feature-graphic-equalizer: + type: boolean + required: false + description: Enable Graphic Equalizer feature. + Currently not supported. + mic-feature-automatic-gain-control: + type: boolean + required: false + description: Enable Autoamtic Gain Control feature. + Currently not supported. + mic-feature-delay: + type: boolean + required: false + description: Enable Delay feature. + Currently not supported. + mic-feature-bass-boost: + type: boolean + required: false + description: Enable Bass Boost feature. + Currently not supported. + mic-mic-feature-loduness: + type: boolean + required: false + description: Enable Loudness feature. + Currently not supported. +# headphones channel configuration options + hp-channel-l: + type: boolean + required: false + description: Enable (l) channel. + hp-channel-r: + type: boolean + required: false + description: Enable (r) channel. + hp-channel-c: + type: boolean + required: false + description: Enable (c) channel. + hp-channel-lfe: + type: boolean + required: false + description: Enable (lfe) channel. + hp-channel-ls: + type: boolean + required: false + description: Enable (ls) channel. + hp-channel-rs: + type: boolean + required: false + description: Enable (rs) channel. + hp-channel-lc: + type: boolean + required: false + description: Enable (lc) channel. + hp-channel-rc: + type: boolean + required: false + description: Enable (rc) channel. + hp-channel-s: + type: boolean + required: false + description: Enable (s) channel. + hp-channel-sl: + type: boolean + required: false + description: Enable (sl) channel. + hp-channel-sr: + type: boolean + required: false + description: Enable (sr) channel. + hp-channel-t: + type: boolean + required: false + description: Enable (t) channel. + hp-channel-cfg: + type: boolean + required: false + description: Enable (cfg) channel. +# headphones feature unit configuration options + hp-feature-mute: + type: boolean + required: true + description: Enable Mute feature. + hp-feature-volume: + type: boolean + required: false + description: Enable Volume feature. + Currently not supported. + hp-feature-tone-control: + type: boolean + required: false + description: Enable Tone Control (Bass, Mid, Treble) feature. + Currently not supported. + hp-feature-graphic-equalizer: + type: boolean + required: false + description: Enable Graphic Equalizer feature. + Currently not supported. + hp-feature-automatic-gain-control: + type: boolean + required: false + description: Enable Autoamtic Gain Control feature. + Currently not supported. + hp-feature-delay: + type: boolean + required: false + description: Enable Delay feature. + Currently not supported. + hp-feature-bass-boost: + type: boolean + required: false + description: Enable Bass Boost feature. + Currently not supported. + hp-feature-loduness: + type: boolean + required: false + description: Enable Loudness feature. + Currently not supported. diff --git a/dts/bindings/usb/usb-audio-mic.yaml b/dts/bindings/usb/usb-audio-mic.yaml new file mode 100644 index 00000000000..8674d7beb9a --- /dev/null +++ b/dts/bindings/usb/usb-audio-mic.yaml @@ -0,0 +1,126 @@ +# Copyright (c) 2020 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Specific fields for USB audio microphone. + +description: USB Audio microphone specific fields. + +compatible: "usb-audio-mic" + +include: usb-audio.yaml + +properties: + resolution: + type: int + default: 16 + required: false + enum: + - 8 + - 16 + - 24 + - 32 + sync-type: + required: false + default: "Synchronous" + type: string + description: Type of endpoint synchronization for IN devices. + Default value is Sychronous. + Adaptive is not supported. + enum: + - "No Synchronization" + - "Asynchronous" + - "Adaptive" + - "Synchronous" +# channel configuration options + channel-l: + type: boolean + required: false + description: Enable (l) channel. + channel-r: + type: boolean + required: false + description: Enable (r) channel. + channel-c: + type: boolean + required: false + description: Enable (c) channel. + channel-lfe: + type: boolean + required: false + description: Enable (lfe) channel. + channel-ls: + type: boolean + required: false + description: Enable (ls) channel. + channel-rs: + type: boolean + required: false + description: Enable (rs) channel. + channel-lc: + type: boolean + required: false + description: Enable (lc) channel. + channel-rc: + type: boolean + required: false + description: Enable (rc) channel. + channel-s: + type: boolean + required: false + description: Enable (s) channel. + channel-sl: + type: boolean + required: false + description: Enable (sl) channel. + channel-sr: + type: boolean + required: false + description: Enable (sr) channel. + channel-t: + type: boolean + required: false + description: Enable (t) channel. + channel-cfg: + type: boolean + required: false + description: Enable (cfg) channel. +# feature unit configuration options + feature-mute: + type: boolean + required: true + description: Enable Mute feature. + feature-volume: + type: boolean + required: false + description: Enable Volume feature. + Currently not supported. + feature-tone-control: + type: boolean + required: false + description: Enable Tone Control (Bass, Mid, Treble) feature. + Currently not supported. + feature-graphic-equalizer: + type: boolean + required: false + description: Enable Graphic Equalizer feature. + Currently not supported. + feature-automatic-gain-control: + type: boolean + required: false + description: Enable Autoamtic Gain Control feature. + Currently not supported. + feature-delay: + type: boolean + required: false + description: Enable Delay feature. + Currently not supported. + feature-bass-boost: + type: boolean + required: false + description: Enable Bass Boost feature. + Currently not supported. + feature-loduness: + type: boolean + required: false + description: Enable Loudness feature. + Currently not supported. diff --git a/dts/bindings/usb/usb-audio.yaml b/dts/bindings/usb/usb-audio.yaml new file mode 100644 index 00000000000..6fe3f9427c8 --- /dev/null +++ b/dts/bindings/usb/usb-audio.yaml @@ -0,0 +1,13 @@ +# Copyright (c) 2020 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Common fields for USB audio class + +description: Common fields for USB audio devices +compatible: "usb-audio" + +properties: + label: + required: true + type: string + description: Human readable string describing the device (used by Zephyr for API name) diff --git a/include/usb/class/usb_audio.h b/include/usb/class/usb_audio.h index e88c3902870..496109508ab 100644 --- a/include/usb/class/usb_audio.h +++ b/include/usb/class/usb_audio.h @@ -10,11 +10,277 @@ * @file * @brief USB Audio Device Class public header * - * Header follows Device Class Definition for Audio Class - * Version 1.0 document (audio10.pdf). + * Header follows below documentation: + * - USB Device Class Definition for Audio Devices (audio10.pdf) + * + * Additional documentation considered a part of USB Audio v1.0: + * - USB Device Class Definition for Audio Data Formats (frmts10.pdf) + * - USB Device Class Definition for Terminal Types (termt10.pdf) */ #ifndef ZEPHYR_INCLUDE_USB_CLASS_AUDIO_H_ #define ZEPHYR_INCLUDE_USB_CLASS_AUDIO_H_ +#include +#include +#include +#include + +/** Audio Interface Subclass Codes + * Refer to Table A-2 from audio10.pdf + */ +enum usb_audio_int_subclass_codes { + USB_AUDIO_SUBCLASS_UNDEFINED = 0x00, + USB_AUDIO_AUDIOCONTROL = 0x01, + USB_AUDIO_AUDIOSTREAMING = 0x02, + USB_AUDIO_MIDISTREAMING = 0x03 +}; + +/** Audio Class-Specific AC Interface Descriptor Subtypes + * Refer to Table A-5 from audio10.pdf + */ +enum usb_audio_cs_ac_int_desc_subtypes { + USB_AUDIO_AC_DESCRIPTOR_UNDEFINED = 0x00, + USB_AUDIO_HEADER = 0x01, + USB_AUDIO_INPUT_TERMINAL = 0x02, + USB_AUDIO_OUTPUT_TERMINAL = 0x03, + USB_AUDIO_MIXER_UNIT = 0x04, + USB_AUDIO_SELECTOR_UNIT = 0x05, + USB_AUDIO_FEATURE_UNIT = 0x06, + USB_AUDIO_PROCESSING_UNIT = 0x07, + USB_AUDIO_EXTENSION_UNIT = 0x08 +}; + +/** Audio Class-Specific AS Interface Descriptor Subtypes + * Refer to Table A-6 from audio10.pdf + */ +enum usb_audio_cs_as_int_desc_subtypes { + USB_AUDIO_AS_DESCRIPTOR_UNDEFINED = 0x00, + USB_AUDIO_AS_GENERAL = 0x01, + USB_AUDIO_FORMAT_TYPE = 0x02, + USB_AUDIO_FORMAT_SPECIFIC = 0x03 +}; + +/** Audio Class-Specific Request Codes + * Refer to Table A-9 from audio10.pdf + */ +enum usb_audio_cs_req_codes { + USB_AUDIO_REQUEST_CODE_UNDEFINED = 0x00, + USB_AUDIO_SET_CUR = 0x01, + USB_AUDIO_GET_CUR = 0x81, + USB_AUDIO_SET_MIN = 0x02, + USB_AUDIO_GET_MIN = 0x82, + USB_AUDIO_SET_MAX = 0x03, + USB_AUDIO_GET_MAX = 0x83, + USB_AUDIO_SET_RES = 0x04, + USB_AUDIO_GET_RES = 0x84, + USB_AUDIO_SET_MEM = 0x05, + USB_AUDIO_GET_MEM = 0x85, + USB_AUDIO_GET_STAT = 0xFF +}; + +/** Feature Unit Control Selectors + * Refer to Table A-11 from audio10.pdf + */ +enum usb_audio_fucs { + USB_AUDIO_FU_CONTROL_UNDEFINED = 0x00, + USB_AUDIO_FU_MUTE_CONTROL = 0x01, + USB_AUDIO_FU_VOLUME_CONTROL = 0x02, + USB_AUDIO_FU_BASS_CONTROL = 0x03, + USB_AUDIO_FU_MID_CONTROL = 0x04, + USB_AUDIO_FU_TREBLE_CONTROL = 0x05, + USB_AUDIO_FU_GRAPHIC_EQUALIZER_CONTROL = 0x06, + USB_AUDIO_FU_AUTOMATIC_GAIN_CONTROL = 0x07, + USB_AUDIO_FU_DELAY_CONTROL = 0x08, + USB_AUDIO_FU_BASS_BOOST_CONTROL = 0x09, + USB_AUDIO_FU_LOUDNESS_CONTROL = 0x0A +}; + +/** USB Terminal Types + * Refer to Table 2-1 - Table 2-4 from termt10.pdf + */ +enum usb_audio_terminal_types { + /* USB Terminal Types */ + USB_AUDIO_USB_UNDEFINED = 0x0100, + USB_AUDIO_USB_STREAMING = 0x0101, + USB_AUDIO_USB_VENDOR_SPEC = 0x01FF, + + /* Input Terminal Types */ + USB_AUDIO_IN_UNDEFINED = 0x0200, + USB_AUDIO_IN_MICROPHONE = 0x0201, + USB_AUDIO_IN_DESKTOP_MIC = 0x0202, + USB_AUDIO_IN_PERSONAL_MIC = 0x0203, + USB_AUDIO_IN_OM_DIR_MIC = 0x0204, + USB_AUDIO_IN_MIC_ARRAY = 0x0205, + USB_AUDIO_IN_PROC_MIC_ARRAY = 0x0205, + + /* Output Terminal Types */ + USB_AUDIO_OUT_UNDEFINED = 0x0300, + USB_AUDIO_OUT_SPEAKER = 0x0301, + USB_AUDIO_OUT_HEADPHONES = 0x0302, + USB_AUDIO_OUT_HEAD_AUDIO = 0x0303, + USB_AUDIO_OUT_DESKTOP_SPEAKER = 0x0304, + USB_AUDIO_OUT_ROOM_SPEAKER = 0x0305, + USB_AUDIO_OUT_COMM_SPEAKER = 0x0306, + USB_AUDIO_OUT_LOW_FREQ_SPEAKER = 0x0307, + + /* Bi-directional Terminal Types */ + USB_AUDIO_IO_UNDEFINED = 0x0400, + USB_AUDIO_IO_HANDSET = 0x0401, + USB_AUDIO_IO_HEADSET = 0x0402, + USB_AUDIO_IO_SPEAKERPHONE_ECHO_NONE = 0x0403, + USB_AUDIO_IO_SPEAKERPHONE_ECHO_SUP = 0x0404, + USB_AUDIO_IO_SPEAKERPHONE_ECHO_CAN = 0x0405, +}; + +enum usb_audio_direction { + USB_AUDIO_IN = 0x00, + USB_AUDIO_OUT = 0x01 +}; + +/** + * @brief Feature Unit event structure. + * + * The event structure is used by feature_update_cb in order to inform the App + * whenever the Host has modified one of the device features. + * + * @param dir The device direction that has been changed. Applicable for + * Headset device only. + * @param cs Control selector, feature that has been changed. + * @param channel Device channel that has been changed. If 0xFF, then + * all channels have been changed. + * @param val_len Length of the val field. + * @param val Value of the feature that has been set. + */ +struct usb_audio_fu_evt { + enum usb_audio_direction dir; + enum usb_audio_fucs cs; + u8_t channel; + u8_t val_len; + const void *val; +}; + +/** + * @brief Callback type used to inform the app that data were requested + * from the device and may be send to the Host. + * For sending the data usb_audio_send() API function should be used. + * + * @note User may not use this callback and may try to send in 1ms task + * instead. Sending every 1ms may be unsuccessful and may return -EAGAIN + * if Host did not required data. + * + * @param dev The device for which data were requested by the Host. + */ +typedef void (*usb_audio_data_request_cb_t)(const struct device *dev); + +/** + * @brief Callback type used to inform the app that data were successfully + * send/received. + * + * @param dev The device for which the callback was called. + * @param buffer Pointer to the net_buf data chunk that was successfully + * send/received. If the application uses data_written_cb and/or + * data_received_cb callbacks it is responsible for freeing the + * buffer by itself. + * @param size Amount of data that were successfully send/received. + */ +typedef void (*usb_audio_data_completion_cb_t)(const struct device *dev, + struct net_buf *buffer, + size_t size); + +/** + * @brief Callback type used to inform the app that Host has changed + * one of the features configured for the device. + * Applicable for all devices. + * + * @warning Host may not use all of configured features. + * + * @param evt Pointer to an event to be parsed by the App. + * Pointer sturct is temporary and is valid only during the + * execution of this callback. + */ +typedef void (*usb_audio_feature_updated_cb_t)(struct device *dev, + const struct usb_audio_fu_evt *evt); + +/** + * @brief Audio callbacks used to interact with audio devices by user App. + * + * usb_audio_ops structure contains all relevant callbacks to interact with + * USB Audio devices. Each of this callbacks is optional and may be left NULL. + * This will not break the stack but could make USB Audio device useless. + * Depending on the device some of those callbacks are necessary to make USB + * device work as expected sending/receiving data. For more information refer + * to callback documentation above. + */ +struct usb_audio_ops { + /* Callback called when data could be send */ + usb_audio_data_request_cb_t data_request_cb; + + /* Callback called when data were successfully written with sending + * capable device. Applicable for headset and microphone. Unused for + * headphones. + */ + usb_audio_data_completion_cb_t data_written_cb; + + /* Callback called when data were successfully received by receive + * capable device. Applicable for headset and headphones. Unused for + * microphone. + */ + usb_audio_data_completion_cb_t data_received_cb; + + /* Callback called when features were modified by the Host */ + usb_audio_feature_updated_cb_t feature_update_cb; +}; + +/** @brief Get the frame size that is accepted by the Host. + * + * This function returns the frame size for Input Devices that is expected + * by the Host. Returned value rely on Input Device configuration: + * - number of channels + * - sampling frequency + * - sample resolution + * Device configuration is done via DT overlay. + * + * @param dev The Input device that is asked for frame size. + * + * @warning Do not use with OUT only devices (Headphones). + * For OUT only devices this function shall return 0. + */ +size_t usb_audio_get_in_frame_size(const struct device *dev); + +/** + * @brief Register the USB Audio device and make it useable. + * This must be called in order to make the device work + * and respond to all relevant requests. + * + * @param dev USB Audio device + * @param ops USB audio callback structure. Callback are used to + * inform the user about what is happening + */ +void usb_audio_register(struct device *dev, + const struct usb_audio_ops *ops); + +/** + * @brief Send data using USB Audio device + * + * @param dev USB Audio device which will send the data + * over its ISO IN endpoint + * @param buffer Pointer to the buffer that should be send. User is + * responsible for managing the buffer for Input devices. In case + * of sending error user must decide if the buffer should be + * dropped or retransmitted. + * Afther the buffer was sent successfully it is passed to the + * data_written_cb callback if the application uses one or + * automatically freed otherwse. + * User must provide proper net_buf chunk especially when + * it comes to its size. This information can be obtained + * using usb_audio_get_in_frame_size() API function. + * + * @param len Length of the data to be send + * + * @return 0 on success, negative error on fail + */ +int usb_audio_send(const struct device *dev, struct net_buf *buffer, + size_t len); + #endif /* ZEPHYR_INCLUDE_USB_CLASS_AUDIO_H_ */ diff --git a/subsys/usb/class/audio/Kconfig b/subsys/usb/class/audio/Kconfig index 7c23d9c7564..759a03efb97 100644 --- a/subsys/usb/class/audio/Kconfig +++ b/subsys/usb/class/audio/Kconfig @@ -1,13 +1,20 @@ # USB AUDIO configuration options -# Copyright (c) 2019 Nordic Semiconductor ASA +# Copyright (c) 2020 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 config USB_DEVICE_AUDIO bool "USB Audio Device Class Driver" help - USB audio device class driver + USB Audio Device Class driver. + Zephyr USB Audio Class is considered experimental + and not full. + Device configuration is done via dt overlay. if USB_DEVICE_AUDIO +module = USB_AUDIO +module-str = USB Audio +source "subsys/logging/Kconfig.template.log_config" + endif #USB_DEVICE_AUDIO diff --git a/subsys/usb/class/audio/audio.c b/subsys/usb/class/audio/audio.c index 99b4b2df041..cb471220b70 100644 --- a/subsys/usb/class/audio/audio.c +++ b/subsys/usb/class/audio/audio.c @@ -10,3 +10,970 @@ * * Driver for USB Audio device class driver */ + +#include +#include +#include +#include +#include +#include +#include "usb_audio_internal.h" + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(usb_audio, CONFIG_USB_AUDIO_LOG_LEVEL); + +/* Device data structure */ +struct usb_audio_dev_data { + const struct usb_audio_ops *ops; + + u8_t *controls[2]; + + u8_t ch_cnt[2]; + + const struct cs_ac_if_descriptor *desc_hdr; + + struct usb_dev_data common; + + struct net_buf_pool *pool; + + /* Not applicable for Headphones, left with 0 */ + u16_t in_frame_size; + + bool rx_enable; + bool tx_enable; +}; + +static sys_slist_t usb_audio_data_devlist; + +/** + * @brief Fill the USB Audio descriptor + * + * This macro fills USB descriptor for specific type of device + * (Heahphones or Microphone) depending on dev param. + * + * @note Feature unit has variable length and only 1st field of + * .bmaControls is filled. Later its fixed in usb_fix_descriptor() + * @note Audio control and Audio streaming interfaces are numerated starting + * from 0 and are later fixed in usb_fix_descriptor() + * + * @param [in] dev Device type. Must be HP/MIC + * @param [in] i Instance of device of current type (dev) + * @param [in] id Param for counting logic entities + * @param [in] link ID of IN/OUT terminal to which General Descriptor + * is linked. + * @param [in] it_type Input terminal type + * @param [in] ot_type Output terminal type + */ +#define DEFINE_AUDIO_DESCRIPTOR(dev, i, id, link, it_type, ot_type, cb, addr) \ +USBD_CLASS_DESCR_DEFINE(primary, audio) \ +struct dev##_descriptor_##i dev##_desc_##i = { \ + USB_AUDIO_IAD(2) \ + .std_ac_interface = INIT_STD_IF(USB_AUDIO_AUDIOCONTROL, 0, 0, 0), \ + .cs_ac_interface = INIT_CS_AC_IF(dev, i, 1), \ + .input_terminal = INIT_IN_TERMINAL(dev, i, id, it_type), \ + .feature_unit = INIT_FEATURE_UNIT(dev, i, id + 1, id), \ + .output_terminal = INIT_OUT_TERMINAL(id + 2, id + 1, ot_type), \ + .as_interface_alt_0 = INIT_STD_IF(USB_AUDIO_AUDIOSTREAMING, 1, 0, 0), \ + .as_interface_alt_1 = INIT_STD_IF(USB_AUDIO_AUDIOSTREAMING, 1, 1, 1), \ + .as_cs_interface = INIT_AS_GENERAL(link), \ + .format = INIT_AS_FORMAT_I(CH_CNT(dev, i), GET_RES(dev, i)), \ + .std_ep = INIT_STD_AS_AD_EP(dev, i, addr), \ + .cs_ep = INIT_CS_AS_AD_EP, \ +}; \ +static struct usb_ep_cfg_data dev##_usb_audio_ep_data_##i[] = { \ + INIT_EP_DATA(cb, addr), \ +} + +/** + * @brief Fill the USB Audio descriptor + * + * This macro fills USB descriptor for specific type of device. + * Macro is used when the device uses 2 audiostreaming interfaces, + * eg. Headset + * + * @note Feature units have variable length and only 1st field of + * .bmaControls is filled. Its fixed in usb_fix_descriptor() + * @note Audio control and Audio streaming interfaces are numerated starting + * from 0 and are later fixed in usb_fix_descriptor() + * + * @param [in] dev Device type. + * @param [in] i Instance of device of current type (dev) + * @param [in] id Param for counting logic entities + */ +#define DEFINE_AUDIO_DESCRIPTOR_BIDIR(dev, i, id) \ +USBD_CLASS_DESCR_DEFINE(primary, audio) \ +struct dev##_descriptor_##i dev##_desc_##i = { \ + USB_AUDIO_IAD(3) \ + .std_ac_interface = INIT_STD_IF(USB_AUDIO_AUDIOCONTROL, 0, 0, 0), \ + .cs_ac_interface = INIT_CS_AC_IF_BIDIR(dev, i, 2), \ + .input_terminal_0 = INIT_IN_TERMINAL(dev##_MIC, i, id, \ + USB_AUDIO_IO_HEADSET), \ + .feature_unit_0 = INIT_FEATURE_UNIT(dev##_MIC, i, id+1, id), \ + .output_terminal_0 = INIT_OUT_TERMINAL(id+2, id+1, \ + USB_AUDIO_USB_STREAMING), \ + .input_terminal_1 = INIT_IN_TERMINAL(dev##_HP, i, id+3, \ + USB_AUDIO_USB_STREAMING), \ + .feature_unit_1 = INIT_FEATURE_UNIT(dev##_HP, i, id+4, id+3), \ + .output_terminal_1 = INIT_OUT_TERMINAL(id+5, id+4, \ + USB_AUDIO_IO_HEADSET), \ + .as_interface_alt_0_0 = INIT_STD_IF(USB_AUDIO_AUDIOSTREAMING, \ + 1, 0, 0), \ + .as_interface_alt_0_1 = INIT_STD_IF(USB_AUDIO_AUDIOSTREAMING, \ + 1, 1, 1), \ + .as_cs_interface_0 = INIT_AS_GENERAL(id+2), \ + .format_0 = INIT_AS_FORMAT_I(CH_CNT(dev##_MIC, i), \ + GET_RES(dev##_MIC, i)), \ + .std_ep_0 = INIT_STD_AS_AD_EP(dev##_MIC, i, \ + AUTO_EP_IN), \ + .cs_ep_0 = INIT_CS_AS_AD_EP, \ + .as_interface_alt_1_0 = INIT_STD_IF(USB_AUDIO_AUDIOSTREAMING, \ + 2, 0, 0), \ + .as_interface_alt_1_1 = INIT_STD_IF(USB_AUDIO_AUDIOSTREAMING, \ + 2, 1, 1), \ + .as_cs_interface_1 = INIT_AS_GENERAL(id+3), \ + .format_1 = INIT_AS_FORMAT_I(CH_CNT(dev##_HP, i), \ + GET_RES(dev##_HP, i)), \ + .std_ep_1 = INIT_STD_AS_AD_EP(dev##_HP, i, \ + AUTO_EP_OUT), \ + .cs_ep_1 = INIT_CS_AS_AD_EP, \ +}; \ +static struct usb_ep_cfg_data dev##_usb_audio_ep_data_##i[] = { \ + INIT_EP_DATA(usb_transfer_ep_callback, AUTO_EP_IN), \ + INIT_EP_DATA(audio_receive_cb, AUTO_EP_OUT), \ +} + +#define DEFINE_AUDIO_DEV_DATA(dev, i, __out_pool, __in_pool_size) \ + static u8_t dev##_controls_##i[FEATURES_SIZE(dev, i)] = {0};\ + static struct usb_audio_dev_data dev##_audio_dev_data_##i = \ + { .pool = __out_pool, \ + .in_frame_size = __in_pool_size, \ + .controls = {dev##_controls_##i, NULL}, \ + .ch_cnt = {(CH_CNT(dev, i) + 1), 0} \ + } + +#define DEFINE_AUDIO_DEV_DATA_BIDIR(dev, i, __out_pool, __in_pool_size) \ + static u8_t dev##_controls0_##i[FEATURES_SIZE(dev##_MIC, i)] = {0};\ + static u8_t dev##_controls1_##i[FEATURES_SIZE(dev##_HP, i)] = {0}; \ + static struct usb_audio_dev_data dev##_audio_dev_data_##i = \ + { .pool = __out_pool, \ + .in_frame_size = __in_pool_size, \ + .controls = {dev##_controls0_##i, dev##_controls1_##i}, \ + .ch_cnt = {(CH_CNT(dev##_MIC, i) + 1), \ + (CH_CNT(dev##_HP, i) + 1)} \ + } + +/** + * Helper function for getting channel number directly from the + * feature unit descriptor. + */ +static u8_t get_num_of_channels(const struct feature_unit_descriptor *fu) +{ + return (fu->bLength - FU_FIXED_ELEMS_SIZE)/sizeof(u16_t); +} + +/** + * Helper function for getting supported controls directly from + * the feature unit descriptor. + */ +static u16_t get_controls(const struct feature_unit_descriptor *fu) +{ + return *(u16_t *)((u8_t *)fu + BMA_CONTROLS_OFFSET); +} + +/** + * Helper function for getting the device streaming direction + */ +static enum usb_audio_direction get_fu_dir( + const struct feature_unit_descriptor *fu) +{ + const struct output_terminal_descriptor *ot = + (struct output_terminal_descriptor *) + ((u8_t *)fu + fu->bLength); + enum usb_audio_direction dir; + + if (ot->wTerminalType == USB_AUDIO_USB_STREAMING) { + dir = USB_AUDIO_IN; + } else { + dir = USB_AUDIO_OUT; + } + + return dir; +} + +/** + * Helper function for fixing controls in feature units descriptors. + */ +static void fix_fu_descriptors(struct usb_if_descriptor *iface) +{ + struct cs_ac_if_descriptor *header; + struct feature_unit_descriptor *fu; + + header = (struct cs_ac_if_descriptor *) + ((u8_t *)iface + USB_PASSIVE_IF_DESC_SIZE); + + fu = (struct feature_unit_descriptor *)((u8_t *)header + + header->bLength + + INPUT_TERMINAL_DESC_SIZE); + + /* start from 1 as elem 0 is filled when descriptor is declared */ + for (int i = 1; i < get_num_of_channels(fu); i++) { + *(fu->bmaControls + i) = fu->bmaControls[0]; + } + + if (header->bInCollection == 2) { + fu = (struct feature_unit_descriptor *)((u8_t *)fu + + fu->bLength + + INPUT_TERMINAL_DESC_SIZE + + OUTPUT_TERMINAL_DESC_SIZE); + for (int i = 1; i < get_num_of_channels(fu); i++) { + *(fu->bmaControls + i) = fu->bmaControls[0]; + } + } +} + +/** + * Helper function for getting pointer to feature unit descriptor. + * This is needed in order to address audio specific requests to proper + * controls struct. + */ +static struct feature_unit_descriptor *get_feature_unit( + struct usb_audio_dev_data *audio_dev_data, + u8_t *device, u8_t fu_id) +{ + struct feature_unit_descriptor *fu; + + fu = (struct feature_unit_descriptor *) + ((u8_t *)audio_dev_data->desc_hdr + + audio_dev_data->desc_hdr->bLength + + INPUT_TERMINAL_DESC_SIZE); + + if (fu->bUnitID == fu_id) { + *device = 0; + return fu; + } + /* skip to the next Feature Unit */ + fu = (struct feature_unit_descriptor *) + ((u8_t *)fu + fu->bLength + + INPUT_TERMINAL_DESC_SIZE + + OUTPUT_TERMINAL_DESC_SIZE); + *device = 1; + + return fu; +} + +/** + * @brief This is a helper function user to inform the user about + * possibility to write the data to the device. + */ +static void audio_dc_sof(struct usb_cfg_data *cfg, + struct usb_audio_dev_data *dev_data) +{ + u8_t ep_addr; + + /* In endpoint always at index 0 */ + ep_addr = cfg->endpoint[0].ep_addr; + if ((ep_addr & USB_EP_DIR_MASK) && (dev_data->tx_enable)) { + if (dev_data->ops && dev_data->ops->data_request_cb) { + dev_data->ops->data_request_cb( + dev_data->common.dev); + } + } +} + +static void audio_interface_config(struct usb_desc_header *head, + u8_t bInterfaceNumber) +{ + struct usb_if_descriptor *iface = (struct usb_if_descriptor *)head; + struct cs_ac_if_descriptor *header; + +#ifdef CONFIG_USB_COMPOSITE_DEVICE + struct usb_association_descriptor *iad = + (struct usb_association_descriptor *) + ((char *)iface - sizeof(struct usb_association_descriptor)); + iad->bFirstInterface = bInterfaceNumber; +#endif + fix_fu_descriptors(iface); + + /* Audio Control Interface */ + iface->bInterfaceNumber = bInterfaceNumber; + header = (struct cs_ac_if_descriptor *) + ((u8_t *)iface + iface->bLength); + header->baInterfaceNr[0] = bInterfaceNumber + 1; + + /* Audio Streaming Interface Passive */ + iface = (struct usb_if_descriptor *) + ((u8_t *)header + header->wTotalLength); + iface->bInterfaceNumber = bInterfaceNumber + 1; + + /* Audio Streaming Interface Active */ + iface = (struct usb_if_descriptor *) + ((u8_t *)iface + iface->bLength); + iface->bInterfaceNumber = bInterfaceNumber + 1; + + if (header->bInCollection == 2) { + header->baInterfaceNr[1] = bInterfaceNumber + 2; + /* Audio Streaming Interface Passive */ + iface = (struct usb_if_descriptor *) + ((u8_t *)iface + USB_ACTIVE_IF_DESC_SIZE); + iface->bInterfaceNumber = bInterfaceNumber + 2; + + /* Audio Streaming Interface Active */ + iface = (struct usb_if_descriptor *) + ((u8_t *)iface + USB_PASSIVE_IF_DESC_SIZE); + iface->bInterfaceNumber = bInterfaceNumber + 2; + } +} + +static void audio_cb_usb_status(struct usb_cfg_data *cfg, + enum usb_dc_status_code cb_status, + const u8_t *param) +{ + struct usb_audio_dev_data *audio_dev_data; + struct usb_dev_data *dev_data; + + dev_data = usb_get_dev_data_by_cfg(&usb_audio_data_devlist, cfg); + + if (dev_data == NULL) { + LOG_ERR("Device data not found for cfg %p", cfg); + return; + } + + audio_dev_data = CONTAINER_OF(dev_data, struct usb_audio_dev_data, + common); + + switch (cb_status) { + case USB_DC_SOF: + audio_dc_sof(cfg, audio_dev_data); + break; + default: + break; + } +} + +/** + * @brief Helper funciton for checking if particular entity is a part of + * the audio device. + * + * This function checks if given entity is a part of given audio device. + * If so then true is returned and audio_dev_data is considered correct device + * data. + * + * @note For now this function searches through feature units only. The + * descriptors are known and are not using any other entity type. + * If there is a need to add other units to audio function then this + * must be reworked. + * + * @param [in] audio_dev_data USB audio device data. + * @param [in, out] entity USB Audio entity. + * .id [in] id of searched entity + * .subtype [out] subtype of entity (if found) + * + * @return true if entity matched audio_dev_data, false otherwise. + */ +static bool is_entity_valid(struct usb_audio_dev_data *audio_dev_data, + struct usb_audio_entity *entity) +{ + const struct cs_ac_if_descriptor *header; + const struct feature_unit_descriptor *fu; + + header = audio_dev_data->desc_hdr; + fu = (struct feature_unit_descriptor *)((u8_t *)header + + header->bLength + + INPUT_TERMINAL_DESC_SIZE); + if (fu->bUnitID == entity->id) { + entity->subtype = fu->bDescriptorSubtype; + return true; + } + + if (header->bInCollection == 2) { + fu = (struct feature_unit_descriptor *)((u8_t *)fu + + fu->bLength + + INPUT_TERMINAL_DESC_SIZE + + OUTPUT_TERMINAL_DESC_SIZE); + if (fu->bUnitID == entity->id) { + entity->subtype = fu->bDescriptorSubtype; + return true; + } + } + + return false; +} + +/** + * @brief Helper funciton for getting the audio_dev_data by the entity number. + * + * This function searches through all audio devices the one with given + * entity number and return the audio_dev_data structure for this entity. + * + * @param [in, out] entity USB Audio entity addressed by the request. + * .id [in] id of searched entity + * .subtype [out] subtype of entity (if found) + * + * @return audio_dev_data for given entity, NULL if not found. + */ +static struct usb_audio_dev_data *get_audio_dev_data_by_entity( + struct usb_audio_entity *entity) +{ + struct usb_dev_data *dev_data; + struct usb_audio_dev_data *audio_dev_data; + + SYS_SLIST_FOR_EACH_CONTAINER(&usb_audio_data_devlist, dev_data, node) { + audio_dev_data = CONTAINER_OF(dev_data, + struct usb_audio_dev_data, + common); + + if (is_entity_valid(audio_dev_data, entity)) { + return audio_dev_data; + } + } + return NULL; +} + +/** + * @brief Helper funciton for checking if particular interface is a part of + * the audio device. + * + * This function checks if given interface is a part of given audio device. + * If so then true is returned and audio_dev_data is considered correct device + * data. + * + * @param [in] audio_dev_data USB audio device data. + * @param [in] interface USB Audio interface number. + * + * @return true if interface matched audio_dev_data, false otherwise. + */ +static bool is_interface_valid(struct usb_audio_dev_data *audio_dev_data, + u8_t interface) +{ + const struct cs_ac_if_descriptor *header; + + header = audio_dev_data->desc_hdr; + u8_t desc_iface = 0; + + for (size_t i = 0; i < header->bInCollection; i++) { + desc_iface = header->baInterfaceNr[i]; + if (desc_iface == interface) { + return true; + } + } + + return false; +} + +/** + * @brief Helper funciton for getting the audio_dev_data by the interface + * number. + * + * This function searches through all audio devices the one with given + * interface number and returns the audio_dev_data structure for this device. + * + * @param [in] interface USB Audio interface addressed by the request. + * + * @return audio_dev_data for given interface, NULL if not found. + */ +static struct usb_audio_dev_data *get_audio_dev_data_by_iface(u8_t interface) +{ + struct usb_dev_data *dev_data; + struct usb_audio_dev_data *audio_dev_data; + + SYS_SLIST_FOR_EACH_CONTAINER(&usb_audio_data_devlist, dev_data, node) { + audio_dev_data = CONTAINER_OF(dev_data, + struct usb_audio_dev_data, + common); + + if (is_interface_valid(audio_dev_data, interface)) { + return audio_dev_data; + } + } + return NULL; +} + +/** + * @brief Handler for feature unit mute control requests. + * + * This function handles feature unit mute control request. + * + * @param audio_dev_data USB audio device data. + * @param pSetup Information about the executed request. + * @param len Size of the buffer. + * @param data Buffer containing the request result. + * @param evt Feature Unit Event info. + * @param device Device part that has been addressed. Applicable for + * bidirectional device. + * + * @return 0 if succesfulf, negative errno otherwise. + */ +static int handle_fu_mute_req(struct usb_audio_dev_data *audio_dev_data, + struct usb_setup_packet *pSetup, + s32_t *len, u8_t **data, + struct usb_audio_fu_evt *evt, + u8_t device) +{ + u8_t ch = (pSetup->wValue) & 0xFF; + u8_t ch_cnt = audio_dev_data->ch_cnt[device]; + u8_t *controls = audio_dev_data->controls[device]; + u8_t *control_val = &controls[POS(MUTE, ch, ch_cnt)]; + + /* Check if *len has valid value */ + if (*len != LEN(1, MUTE)) { + return -EINVAL; + } + + switch (pSetup->bRequest) { + case USB_AUDIO_SET_CUR: + evt->val = control_val; + evt->val_len = *len; + memcpy(control_val, *data, *len); + return 0; + case USB_AUDIO_GET_CUR: + *data = control_val; + return 0; + default: + break; + } + + return -EINVAL; +} + +/** + * @brief Handler for feature unit requests. + * + * This function handles feature unit specific requests. + * If request is properly served 0 is returned. Negative errno + * is returned in case of an error. This leads to setting stall on IN EP0. + * + * @param audio_dev_data USB audio device data. + * @param pSetup Information about the executed request. + * @param len Size of the buffer. + * @param data Buffer containing the request result. + * + * @return 0 if succesfulf, negative errno otherwise. + */ +static int handle_feature_unit_req(struct usb_audio_dev_data *audio_dev_data, + struct usb_setup_packet *pSetup, + s32_t *len, u8_t **data) +{ + const struct feature_unit_descriptor *fu; + struct usb_audio_fu_evt evt; + enum usb_audio_fucs cs; + u8_t device; + u8_t fu_id; + u8_t ch_cnt; + u8_t ch; + int ret; + + fu_id = ((pSetup->wIndex) >> 8) & 0xFF; + fu = get_feature_unit(audio_dev_data, &device, fu_id); + ch = (pSetup->wValue) & 0xFF; + cs = ((pSetup->wValue) >> 8) & 0xFF; + ch_cnt = audio_dev_data->ch_cnt[device]; + + LOG_DBG("CS: %d, CN: %d, len: %d", cs, ch, *len); + + /* Error checking */ + if (!(BIT(cs) & (get_controls(fu) << 1))) { + /* Feature not supported by this FU */ + return -EINVAL; + } else if (ch >= ch_cnt) { + /* Invalid ch */ + return -EINVAL; + } + + switch (cs) { + case USB_AUDIO_FU_MUTE_CONTROL: + ret = handle_fu_mute_req(audio_dev_data, pSetup, + len, data, &evt, device); + break; + default: + return -ENOTSUP; + } + + if (ret) { + return ret; + } + + /* Inform the app */ + if (audio_dev_data->ops && audio_dev_data->ops->feature_update_cb) { + if (pSetup->bRequest == USB_AUDIO_SET_CUR) { + evt.cs = cs; + evt.channel = ch; + evt.dir = get_fu_dir(fu); + audio_dev_data->ops->feature_update_cb( + audio_dev_data->common.dev, &evt); + } + } + + return 0; +} + +/** + * @brief Handler called for class specific interface request. + * + * This function handles all class specific interface requests to a usb audio + * device. If request is properly server then 0 is returned. Returning negative + * value will lead to set stall on IN EP0. + * + * @param pSetup Information about the executed request. + * @param len Size of the buffer. + * @param data Buffer containing the request result. + * + * @return 0 on success, negative errno code on fail. + */ +static int handle_interface_req(struct usb_setup_packet *pSetup, + s32_t *len, + u8_t **data) +{ + struct usb_audio_dev_data *audio_dev_data; + struct usb_audio_entity entity; + + /* parse wIndex for interface request */ + u8_t entity_id = ((pSetup->wIndex) >> 8) & 0xFF; + + entity.id = entity_id; + + /** Normally there should be a call to usb_get_dev_data_by_iface() + * and addressed interface should be read from wIndex low byte. + * + * u8_t interface = (pSetup->wIndex) & 0xFF; + * + * However, Linux is using special form of Audio Requests + * which always left wIndex low byte 0 no matter which device and + * entity is addressed. Because of that there is a need to obtain + * this information from the device descriptor using entity id. + */ + audio_dev_data = get_audio_dev_data_by_entity(&entity); + + if (audio_dev_data == NULL) { + LOG_ERR("Device data not found for entity %u", entity.id); + return -ENODEV; + } + + switch (entity.subtype) { + case USB_AUDIO_FEATURE_UNIT: + return handle_feature_unit_req(audio_dev_data, + pSetup, len, data); + default: + LOG_INF("Currently not supported"); + return -ENODEV; + } + + return 0; +} + +/** + * @brief Custom callback for USB Device requests. + * + * This callback is called when set/get interface request is directed + * to the device. This is Zephyr way to address those requests. + * It's not possible to do that in the core stack as common USB device + * stack does not know the amount of devices that has alternate interfaces. + * + * @param pSetup Information about the request to execute. + * @param len Size of the buffer. + * @param data Buffer containing the request result. + * + * @return 0 on success, positive value if request is intended to be handled + * by the core USB stack. Negative error code on fail. + */ +static int audio_custom_handler(struct usb_setup_packet *pSetup, s32_t *len, + u8_t **data) +{ + const struct cs_ac_if_descriptor *header; + struct usb_audio_dev_data *audio_dev_data; + const struct usb_if_descriptor *if_desc; + const struct usb_ep_descriptor *ep_desc; + + u8_t iface = (pSetup->wIndex) & 0xFF; + + audio_dev_data = get_audio_dev_data_by_iface(iface); + if (audio_dev_data == NULL) { + return -EINVAL; + } + + /* Search for endpoint associated to addressed interface + * Endpoint is searched in order to know the direction of + * addressed interface. + */ + header = audio_dev_data->desc_hdr; + + /* Skip to the first interface */ + if_desc = (struct usb_if_descriptor *)((u8_t *)header + + header->wTotalLength + + USB_PASSIVE_IF_DESC_SIZE); + + if (if_desc->bInterfaceNumber == iface) { + ep_desc = (struct usb_ep_descriptor *)((u8_t *)if_desc + + USB_PASSIVE_IF_DESC_SIZE + + USB_AC_CS_IF_DESC_SIZE + + USB_FORMAT_TYPE_I_DESC_SIZE); + } else { + /* In case first interface address is not the one addressed + * we can be sure the second one is because + * get_audio_dev_data_by_iface() found the device. It + * must be the second interface associated with the device. + */ + if_desc = (struct usb_if_descriptor *)((u8_t *)if_desc + + USB_ACTIVE_IF_DESC_SIZE); + ep_desc = (struct usb_ep_descriptor *)((u8_t *)if_desc + + USB_PASSIVE_IF_DESC_SIZE + + USB_AC_CS_IF_DESC_SIZE + + USB_FORMAT_TYPE_I_DESC_SIZE); + } + + if (REQTYPE_GET_RECIP(pSetup->bmRequestType) == + REQTYPE_RECIP_INTERFACE) { + switch (pSetup->bRequest) { + case REQ_SET_INTERFACE: + if (ep_desc->bEndpointAddress & USB_EP_DIR_MASK) { + audio_dev_data->tx_enable = pSetup->wValue; + } else { + audio_dev_data->rx_enable = pSetup->wValue; + } + return -EINVAL; + case REQ_GET_INTERFACE: + if (ep_desc->bEndpointAddress & USB_EP_DIR_MASK) { + *data[0] = audio_dev_data->tx_enable; + } else { + *data[0] = audio_dev_data->rx_enable; + } + return 0; + default: + break; + } + } + + return -ENOTSUP; +} + +/** + * @brief Handler called for Class requests not handled by the USB stack. + * + * @param pSetup Information about the request to execute. + * @param len Size of the buffer. + * @param data Buffer containing the request result. + * + * @return 0 on success, negative errno code on fail. + */ +static int audio_class_handle_req(struct usb_setup_packet *pSetup, + s32_t *len, u8_t **data) +{ + LOG_INF("bmRequestType 0x%02x, bRequest 0x%02x, wValue 0x%04x," + "wIndex 0x%04x, wLength 0x%04x", + pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, + pSetup->wIndex, pSetup->wLength); + + switch (REQTYPE_GET_RECIP(pSetup->bmRequestType)) { + case REQTYPE_RECIP_INTERFACE: + return handle_interface_req(pSetup, len, data); + default: + LOG_ERR("Request recipient invalid"); + return -EINVAL; + } +} + +static int usb_audio_device_init(struct device *dev) +{ + LOG_DBG("Init Audio Device: dev %p (%s)", dev, dev->config->name); + + return 0; +} + +static void audio_write_cb(u8_t ep, int size, void *priv) +{ + struct usb_dev_data *dev_data; + struct usb_audio_dev_data *audio_dev_data; + struct net_buf *buffer = priv; + + dev_data = usb_get_dev_data_by_ep(&usb_audio_data_devlist, ep); + audio_dev_data = dev_data->dev->driver_data; + + LOG_DBG("Written %d bytes on ep 0x%02x, *audio_dev_data %p", + size, ep, audio_dev_data); + + /* Ask installed callback to process the data. + * User is responsible for freeing the buffer. + * In case no callback is installed free the buffer. + */ + if (audio_dev_data->ops && audio_dev_data->ops->data_written_cb) { + audio_dev_data->ops->data_written_cb(dev_data->dev, + buffer, size); + } else { + /* Release net_buf back to the pool */ + net_buf_unref(buffer); + } +} + +int usb_audio_send(const struct device *dev, struct net_buf *buffer, + size_t len) +{ + struct usb_audio_dev_data *audio_dev_data = dev->driver_data; + struct usb_cfg_data *cfg = (void *)dev->config->config_info; + /* EP ISO IN is always placed first in the endpoint table */ + u8_t ep = cfg->endpoint[0].ep_addr; + + if (!(ep & USB_EP_DIR_MASK)) { + LOG_ERR("Wrong device"); + return -EINVAL; + } + + if (!audio_dev_data->tx_enable) { + LOG_DBG("sending dropped -> Host chose passive interface"); + return -EAGAIN; + } + + if (len > buffer->size) { + LOG_ERR("Cannot send %d bytes, to much data", len); + return -EINVAL; + } + + /** buffer passed to *priv because completion callback + * needs to release it to the pool + */ + usb_transfer(ep, buffer->data, len, USB_TRANS_WRITE | USB_TRANS_NO_ZLP, + audio_write_cb, buffer); + return 0; +} + +size_t usb_audio_get_in_frame_size(const struct device *dev) +{ + struct usb_audio_dev_data *audio_dev_data = dev->driver_data; + + return audio_dev_data->in_frame_size; +} + +static void audio_receive_cb(u8_t ep, enum usb_dc_ep_cb_status_code status) +{ + struct usb_audio_dev_data *audio_dev_data; + struct usb_dev_data *common; + struct net_buf *buffer; + int ret_bytes; + int ret; + + __ASSERT(status == USB_DC_EP_DATA_OUT, "Invalid ep status"); + + common = usb_get_dev_data_by_ep(&usb_audio_data_devlist, ep); + if (common == NULL) { + return; + } + + audio_dev_data = CONTAINER_OF(common, struct usb_audio_dev_data, + common); + + /** Check if active audiostreaming interface is selected + * If no there is no point to read the data. Return from callback + */ + if (!audio_dev_data->rx_enable) { + return; + } + + /* Check if application installed callback and process the data. + * In case no callback is installed do not alloc the buffer at all. + */ + if (audio_dev_data->ops && audio_dev_data->ops->data_received_cb) { + buffer = net_buf_alloc(audio_dev_data->pool, K_NO_WAIT); + if (!buffer) { + LOG_ERR("Failed to allocate data buffer"); + return; + } + + ret = usb_read(ep, buffer->data, buffer->size, &ret_bytes); + + if (ret) { + LOG_ERR("ret=%d ", ret); + net_buf_unref(buffer); + return; + } + + if (!ret_bytes) { + net_buf_unref(buffer); + return; + } + audio_dev_data->ops->data_received_cb(common->dev, + buffer, ret_bytes); + } +} + +void usb_audio_register(struct device *dev, + const struct usb_audio_ops *ops) +{ + struct usb_audio_dev_data *audio_dev_data = dev->driver_data; + const struct usb_cfg_data *cfg = dev->config->config_info; + const struct std_if_descriptor *iface_descr = + cfg->interface_descriptor; + const struct cs_ac_if_descriptor *header = + (struct cs_ac_if_descriptor *) + ((u8_t *)iface_descr + USB_PASSIVE_IF_DESC_SIZE); + + audio_dev_data->ops = ops; + audio_dev_data->common.dev = dev; + audio_dev_data->rx_enable = false; + audio_dev_data->tx_enable = false; + audio_dev_data->desc_hdr = header; + + sys_slist_append(&usb_audio_data_devlist, &audio_dev_data->common.node); + + LOG_DBG("Device dev %p dev_data %p cfg %p added to devlist %p", + dev, audio_dev_data, dev->config->config_info, + &usb_audio_data_devlist); +} + +#define DEFINE_AUDIO_DEVICE(dev, i) \ + USBD_CFG_DATA_DEFINE(primary, audio) \ + struct usb_cfg_data dev##_audio_config_##i = { \ + .usb_device_description = NULL, \ + .interface_config = audio_interface_config, \ + .interface_descriptor = &dev##_desc_##i.std_ac_interface, \ + .cb_usb_status = audio_cb_usb_status, \ + .interface = { \ + .class_handler = audio_class_handle_req, \ + .custom_handler = audio_custom_handler, \ + .vendor_handler = NULL, \ + }, \ + .num_endpoints = ARRAY_SIZE(dev##_usb_audio_ep_data_##i), \ + .endpoint = dev##_usb_audio_ep_data_##i, \ + }; \ + DEVICE_AND_API_INIT(dev##_usb_audio_device_##i, \ + DT_LABEL(DT_INST(i, COMPAT_##dev)), \ + &usb_audio_device_init, \ + &dev##_audio_dev_data_##i, \ + &dev##_audio_config_##i, APPLICATION, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ + DUMMY_API) + +#define DEFINE_BUF_POOL(name, size) \ + NET_BUF_POOL_FIXED_DEFINE(name, 5, size, net_buf_destroy) + +#define UNIDIR_DEVICE(dev, i, out_pool, in_size, it_type, ot_type, cb, addr) \ + UTIL_EXPAND( \ + DEFINE_AUDIO_DEV_DATA(dev, i, out_pool, in_size); \ + DECLARE_DESCRIPTOR(dev, i, 1); \ + DEFINE_AUDIO_DESCRIPTOR(dev, i, dev##_ID(i), dev##_LINK(i), \ + it_type, ot_type, cb, addr); \ + DEFINE_AUDIO_DEVICE(dev, i)) + +#define HEADPHONES_DEVICE(i, dev) UTIL_EXPAND( \ + DEFINE_BUF_POOL(audio_data_pool_hp_##i, EP_SIZE(dev, i)); \ + UNIDIR_DEVICE(dev, i, &audio_data_pool_hp_##i, 0, \ + USB_AUDIO_USB_STREAMING, USB_AUDIO_OUT_HEADPHONES, \ + audio_receive_cb, AUTO_EP_OUT);) + +#define MICROPHONE_DEVICE(i, dev) UTIL_EXPAND( \ + UNIDIR_DEVICE(dev, i, NULL, EP_SIZE(dev, i), \ + USB_AUDIO_IN_MICROPHONE, USB_AUDIO_USB_STREAMING, \ + usb_transfer_ep_callback, AUTO_EP_IN);) + +#define HEADSET_DEVICE(i, dev) UTIL_EXPAND( \ + DEFINE_BUF_POOL(audio_data_pool_hs_##i, EP_SIZE(dev##_HP, i)); \ + DEFINE_AUDIO_DEV_DATA_BIDIR(dev, i, &audio_data_pool_hs_##i, \ + EP_SIZE(dev##_MIC, i)); \ + DECLARE_DESCRIPTOR_BIDIR(dev, i, 2); \ + DEFINE_AUDIO_DESCRIPTOR_BIDIR(dev, i, dev##_ID(i)); \ + DEFINE_AUDIO_DEVICE(dev, i);) + +UTIL_LISTIFY(HEADPHONES_DEVICE_COUNT, HEADPHONES_DEVICE, HP) +UTIL_LISTIFY(MICROPHONE_DEVICE_COUNT, MICROPHONE_DEVICE, MIC) +UTIL_LISTIFY(HEADSET_DEVICE_COUNT, HEADSET_DEVICE, HS) diff --git a/subsys/usb/class/audio/usb_audio_internal.h b/subsys/usb/class/audio/usb_audio_internal.h new file mode 100644 index 00000000000..2f41767f0a8 --- /dev/null +++ b/subsys/usb/class/audio/usb_audio_internal.h @@ -0,0 +1,525 @@ +/* + * USB audio class internal header + * + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief USB Audio Device Class internal header + * + * This header file is used to store internal configuration + * defines. + */ + +#ifndef ZEPHYR_INCLUDE_USB_CLASS_AUDIO_INTERNAL_H_ +#define ZEPHYR_INCLUDE_USB_CLASS_AUDIO_INTERNAL_H_ + +#define DUMMY_API (const void *)1 + +#define USB_PASSIVE_IF_DESC_SIZE sizeof(struct usb_if_descriptor) +#define USB_AC_CS_IF_DESC_SIZE sizeof(struct as_cs_interface_descriptor) +#define USB_FORMAT_TYPE_I_DESC_SIZE sizeof(struct format_type_i_descriptor) +#define USB_STD_AS_AD_EP_DESC_SIZE sizeof(struct std_as_ad_endpoint_descriptor) +#define USB_CS_AS_AD_EP_DESC_SIZE sizeof(struct cs_as_ad_ep_descriptor) +#define USB_ACTIVE_IF_DESC_SIZE (USB_PASSIVE_IF_DESC_SIZE + \ + USB_AC_CS_IF_DESC_SIZE + \ + USB_FORMAT_TYPE_I_DESC_SIZE + \ + USB_STD_AS_AD_EP_DESC_SIZE + \ + USB_CS_AS_AD_EP_DESC_SIZE) + +#define INPUT_TERMINAL_DESC_SIZE sizeof(struct input_terminal_descriptor) +#define OUTPUT_TERMINAL_DESC_SIZE sizeof(struct output_terminal_descriptor) + +#define BMA_CONTROLS_OFFSET 6 +#define FU_FIXED_ELEMS_SIZE 7 + +/* Macros for maitaining features of feature unit entity */ +#define FEATURE_MUTE_SIZE 0x01 +#define FEATURE_VOLUME_SIZE 0x02 +#define FEATURE_BASS_SIZE 0x01 +#define FEATURE_MID_SIZE 0x01 +#define FEATURE_TREBLE_SIZE 0x01 +#define FEATURE_TONE_CONTROL_SIZE (FEATURE_BASS_SIZE +\ + FEATURE_MID_SIZE + \ + FEATURE_TREBLE_SIZE) +#define FEATURE_GRAPHIC_EQUALIZER_SIZE 0x01 +#define FEATURE_AUTOMATIC_GAIN_CONTROL_SIZE 0x01 +#define FEATURE_DELAY_SIZE 0x02 +#define FEATURE_BASS_BOOST_SIZE 0x01 +#define FEATURE_LOUDNESS_SIZE 0x01 + +#define POS_MUTE 0 +#define POS_VOLUME (POS_MUTE + FEATURE_MUTE_SIZE) +#define POS_BASS (POS_VOLUME + FEATURE_VOLUME_SIZE) +#define POS_MID (POS_BASS + FEATURE_BASS_SIZE) +#define POS_TREBLE (POS_MID + FEATURE_MID_SIZE) +#define POS_GRAPHIC_EQUALIZER (POS_TREBLE + FEATURE_TREBLE_SIZE) +#define POS_AUTOMATIC_GAIN_CONTROL (POS_GRAPHIC_EQUALIZER + \ + FEATURE_GRAPHIC_EQUALIZER_SIZE) +#define POS_DELAY (POS_AUTOMATIC_GAIN_CONTROL + \ + FEATURE_AUTOMATIC_GAIN_CONTROL_SIZE) +#define POS_BASS_BOOST (POS_DELAY + FEATURE_DELAY_SIZE) +#define POS_LOUDNESS (POS_BASS_BOOST + FEATURE_BASS_BOOST_SIZE) + +#define POS(prop, ch_idx, ch_cnt) (ch_cnt * POS_##prop + \ + (ch_idx * FEATURE_##prop##_SIZE)) +#define LEN(ch_cnt, prop) (ch_cnt * FEATURE_##prop##_SIZE) + +/* Names of compatibles used for configuration of the device */ +#define COMPAT_HP usb_audio_hp +#define COMPAT_MIC usb_audio_mic +#define COMPAT_HS usb_audio_hs + +#define HEADPHONES_DEVICE_COUNT DT_NUM_INST(COMPAT_HP) +#define MICROPHONE_DEVICE_COUNT DT_NUM_INST(COMPAT_MIC) +#define HEADSET_DEVICE_COUNT DT_NUM_INST(COMPAT_HS) + +#define IF_USB_AUDIO_PROP_HP(i, prop, bitmask) \ + COND_CODE_1(DT_PROP(DT_INST(i, COMPAT_HP), prop), (bitmask), (0)) +#define IF_USB_AUDIO_PROP_MIC(i, prop, bitmask) \ + COND_CODE_1(DT_PROP(DT_INST(i, COMPAT_MIC), prop), (bitmask), (0)) +#define IF_USB_AUDIO_PROP_HS_HP(i, prop, bitmask) \ + COND_CODE_1(DT_PROP(DT_INST(i, COMPAT_HS), hp_##prop), (bitmask), (0)) +#define IF_USB_AUDIO_PROP_HS_MIC(i, prop, bitmask) \ + COND_CODE_1(DT_PROP(DT_INST(i, COMPAT_HS), mic_##prop), (bitmask), (0)) +#define IF_USB_AUDIO_PROP(dev, i, prop, bitmask) \ + IF_USB_AUDIO_PROP_##dev(i, prop, bitmask) + +/* Macro for getting the bitmask of configured channels for given device */ +#define CH_CFG(dev, i) (0x0000 \ +| IF_USB_AUDIO_PROP(dev, i, channel_l, BIT(0)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_r, BIT(1)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_c, BIT(2)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_lfe, BIT(3)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_ls, BIT(4)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_rs, BIT(5)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_lc, BIT(6)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_rc, BIT(7)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_s, BIT(8)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_sl, BIT(9)) \ +| IF_USB_AUDIO_PROP(dev, i, channel_sr, BIT(10))\ +| IF_USB_AUDIO_PROP(dev, i, channel_t, BIT(11))\ +) + +/* Macro for getting the number of configured channles for given device. + * Master channel (0) excluded. + */ +#define CH_CNT(dev, i) (0 \ ++ IF_USB_AUDIO_PROP(dev, i, channel_l, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_r, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_c, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_lfe, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_ls, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_rs, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_lc, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_rc, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_s, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_sl, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_sr, 1)\ ++ IF_USB_AUDIO_PROP(dev, i, channel_t, 1)\ +) + +/* Macro for getting bitmask of supported features for given device */ +#define FEATURES(dev, i) (0x0000 \ +| IF_USB_AUDIO_PROP(dev, i, feature_mute, BIT(0)) \ +| IF_USB_AUDIO_PROP(dev, i, feature_volume, BIT(1)) \ +| IF_USB_AUDIO_PROP(dev, i, feature_tone_control, BIT(2) | BIT(3) | BIT(4))\ +| IF_USB_AUDIO_PROP(dev, i, feature_graphic_equalizer, BIT(5)) \ +| IF_USB_AUDIO_PROP(dev, i, feature_automatic_gain_control, BIT(6)) \ +| IF_USB_AUDIO_PROP(dev, i, feature_delay, BIT(7)) \ +| IF_USB_AUDIO_PROP(dev, i, feature_bass_boost, BIT(8)) \ +| IF_USB_AUDIO_PROP(dev, i, feature_loudness, BIT(9)) \ +) + +/* Macro for getting required size to store values for supported features. */ +#define FEATURES_SIZE(dev, i) ((CH_CNT(dev, i) + 1) * (0x0000 \ ++ IF_USB_AUDIO_PROP(dev, i, feature_mute, \ + FEATURE_MUTE_SIZE) \ ++ IF_USB_AUDIO_PROP(dev, i, feature_volume, \ + FEATURE_VOLUME_SIZE) \ ++ IF_USB_AUDIO_PROP(dev, i, feature_tone_control, \ + FEATURE_TONE_CONTROL_SIZE) \ ++ IF_USB_AUDIO_PROP(dev, i, feature_graphic_equalizer, \ + FEATURE_GRAPHIC_EQUALIZER_SIZE) \ ++ IF_USB_AUDIO_PROP(dev, i, feature_automatic_gain_control, \ + FEATURE_AUTOMATIC_GAIN_CONTROL_SIZE)\ ++ IF_USB_AUDIO_PROP(dev, i, feature_delay, \ + FEATURE_DELAY_SIZE) \ ++ IF_USB_AUDIO_PROP(dev, i, feature_bass_boost, \ + FEATURE_BASS_BOOST_SIZE) \ ++ IF_USB_AUDIO_PROP(dev, i, feature_loudness, \ + FEATURE_LOUDNESS_SIZE) \ +)) + +#define GET_RES_HP(i) DT_PROP(DT_INST(i, COMPAT_HP), resolution) +#define GET_RES_MIC(i) DT_PROP(DT_INST(i, COMPAT_MIC), resolution) +#define GET_RES_HS_HP(i) DT_PROP(DT_INST(i, COMPAT_HS), hp_resolution) +#define GET_RES_HS_MIC(i) DT_PROP(DT_INST(i, COMPAT_HS), mic_resolution) +#define GET_RES(dev, i) GET_RES_##dev(i) + +#define SYNC_TYPE_HP(i) 3 +#define SYNC_TYPE_MIC(i) DT_ENUM_IDX(DT_INST(i, COMPAT_MIC), sync_type) +#define SYNC_TYPE_HS_HP(i) 3 +#define SYNC_TYPE_HS_MIC(i) DT_ENUM_IDX(DT_INST(i, COMPAT_HS), mic_sync_type) +#define SYNC_TYPE(dev, i) (SYNC_TYPE_##dev(i) << 2) + +#define EP_SIZE(dev, i) \ + ((GET_RES(dev, i)/8) * CH_CNT(dev, i) * 48) + +/* *_ID() macros are used to give proper Id to each entity describing + * the device. Entities Id must start from 1 that's why 1 is added. + * Multiplication by 3 for HP/MIC comes from the fact that 3 entities are + * required to describe the device, 6 in case of HS. + */ +#define HP_ID(i) ((3*i) + 1) +#define MIC_ID(i) ((3*(HEADPHONES_DEVICE_COUNT + i)) + 1) +#define HS_ID(i) ((3*(HEADPHONES_DEVICE_COUNT + \ + MICROPHONE_DEVICE_COUNT)) + 6*i + 1) + +/* *_LINK() macros are used to properly connect relevant audio streaming + * class specific interface with valid entity. In case of Headphones this + * will always be 1st entity describing the device (Input terminal). This + * is where addition of 1 comes from. In case of Headphones thill will always + * be 3rd entity (Output terminal) - addition of 3. + */ +#define HP_LINK(i) ((3*i) + 1) +#define MIC_LINK(i) ((3*(HEADPHONES_DEVICE_COUNT + i)) + 3) + +/** + * Addressable logical object inside an audio function. + * Entity is one of: Terminal or Unit. + * Refer to 1.4 Terms and Abbreviations from audio10.pdf + */ +struct usb_audio_entity { + enum usb_audio_cs_ac_int_desc_subtypes subtype; + u8_t id; +}; + +/** + * @warning Size of baInterface is 2 just to make it useable + * for all kind of devices: headphones, microphone and headset. + * Actual size of the struct should be checked by reading + * .bLength. + */ +struct cs_ac_if_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bDescriptorSubtype; + u16_t bcdADC; + u16_t wTotalLength; + u8_t bInCollection; + u8_t baInterfaceNr[2]; +} __packed; + +struct input_terminal_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bDescriptorSubtype; + u8_t bTerminalID; + u16_t wTerminalType; + u8_t bAssocTerminal; + u8_t bNrChannels; + u16_t wChannelConfig; + u8_t iChannelNames; + u8_t iTerminal; +} __packed; + +/** + * @note Size of Feature unit descriptor is not fixed. + * This structure is just a helper not a common type. + */ +struct feature_unit_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bDescriptorSubtype; + u8_t bUnitID; + u8_t bSourceID; + u8_t bControlSize; + u16_t bmaControls[1]; +} __packed; + +struct output_terminal_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bDescriptorSubtype; + u8_t bTerminalID; + u16_t wTerminalType; + u8_t bAssocTerminal; + u8_t bSourceID; + u8_t iTerminal; +} __packed; + +struct as_cs_interface_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bDescriptorSubtype; + u8_t bTerminalLink; + u8_t bDelay; + u16_t wFormatTag; +} __packed; + +struct format_type_i_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bDescriptorSubtype; + u8_t bFormatType; + u8_t bNrChannels; + u8_t bSubframeSize; + u8_t bBitResolution; + u8_t bSamFreqType; + u8_t tSamFreq[3]; +} __packed; + +struct std_as_ad_endpoint_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bEndpointAddress; + u8_t bmAttributes; + u16_t wMaxPacketSize; + u8_t bInterval; + u8_t bRefresh; + u8_t bSynchAddress; +} __packed; + +struct cs_as_ad_ep_descriptor { + u8_t bLength; + u8_t bDescriptorType; + u8_t bDescriptorSubtype; + u8_t bmAttributes; + u8_t bLockDelayUnits; + u16_t wLockDelay; +} __packed; + +#define DECLARE_HEADER(dev, i, ifaces) \ +struct dev##_cs_ac_if_descriptor_##i { \ + u8_t bLength; \ + u8_t bDescriptorType; \ + u8_t bDescriptorSubtype; \ + u16_t bcdADC; \ + u16_t wTotalLength; \ + u8_t bInCollection; \ + u8_t baInterfaceNr[ifaces]; \ +} __packed + +#define DECLARE_FEATURE_UNIT(dev, i) \ +struct dev##_feature_unit_descriptor_##i { \ + u8_t bLength; \ + u8_t bDescriptorType; \ + u8_t bDescriptorSubtype; \ + u8_t bUnitID; \ + u8_t bSourceID; \ + u8_t bControlSize; \ + u16_t bmaControls[CH_CNT(dev, i) + 1]; \ + u8_t iFeature; \ +} __packed + +#define INIT_IAD(iface_subclass, if_cnt) \ +{ \ + .bLength = sizeof(struct usb_association_descriptor), \ + .bDescriptorType = USB_ASSOCIATION_DESC, \ + .bFirstInterface = 0, \ + .bInterfaceCount = if_cnt, \ + .bFunctionClass = AUDIO_CLASS, \ + .bFunctionSubClass = iface_subclass, \ + .bFunctionProtocol = 0, \ + .iFunction = 0, \ +} + +#ifdef CONFIG_USB_COMPOSITE_DEVICE +#define USB_AUDIO_IAD_DECLARE struct usb_association_descriptor iad; +#define USB_AUDIO_IAD(if_cnt) .iad = INIT_IAD(USB_AUDIO_AUDIOCONTROL, if_cnt), +#else +#define USB_AUDIO_IAD_DECLARE +#define USB_AUDIO_IAD(if_cnt) +#endif + +#define DECLARE_DESCRIPTOR(dev, i, ifaces) \ +DECLARE_HEADER(dev, i, ifaces); \ +DECLARE_FEATURE_UNIT(dev, i); \ +struct dev##_descriptor_##i { \ + USB_AUDIO_IAD_DECLARE \ + struct usb_if_descriptor std_ac_interface; \ + struct dev##_cs_ac_if_descriptor_##i cs_ac_interface; \ + struct input_terminal_descriptor input_terminal; \ + struct dev##_feature_unit_descriptor_##i feature_unit; \ + struct output_terminal_descriptor output_terminal; \ + struct usb_if_descriptor as_interface_alt_0; \ + struct usb_if_descriptor as_interface_alt_1; \ + struct as_cs_interface_descriptor as_cs_interface; \ + struct format_type_i_descriptor format; \ + struct std_as_ad_endpoint_descriptor std_ep; \ + struct cs_as_ad_ep_descriptor cs_ep; \ +} __packed + +#define DECLARE_DESCRIPTOR_BIDIR(dev, i, ifaces) \ +DECLARE_HEADER(dev, i, ifaces); \ +DECLARE_FEATURE_UNIT(dev##_MIC, i); \ +DECLARE_FEATURE_UNIT(dev##_HP, i); \ +struct dev##_descriptor_##i { \ + USB_AUDIO_IAD_DECLARE \ + struct usb_if_descriptor std_ac_interface; \ + struct dev##_cs_ac_if_descriptor_##i cs_ac_interface; \ + struct input_terminal_descriptor input_terminal_0; \ + struct dev##_MIC_feature_unit_descriptor_##i feature_unit_0; \ + struct output_terminal_descriptor output_terminal_0; \ + struct input_terminal_descriptor input_terminal_1; \ + struct dev##_HP_feature_unit_descriptor_##i feature_unit_1; \ + struct output_terminal_descriptor output_terminal_1; \ + struct usb_if_descriptor as_interface_alt_0_0; \ + struct usb_if_descriptor as_interface_alt_0_1; \ + struct as_cs_interface_descriptor as_cs_interface_0; \ + struct format_type_i_descriptor format_0; \ + struct std_as_ad_endpoint_descriptor std_ep_0; \ + struct cs_as_ad_ep_descriptor cs_ep_0; \ + struct usb_if_descriptor as_interface_alt_1_0; \ + struct usb_if_descriptor as_interface_alt_1_1; \ + struct as_cs_interface_descriptor as_cs_interface_1; \ + struct format_type_i_descriptor format_1; \ + struct std_as_ad_endpoint_descriptor std_ep_1; \ + struct cs_as_ad_ep_descriptor cs_ep_1; \ +} __packed + +#define INIT_STD_IF(iface_subclass, iface_num, alt_setting, eps_num) \ +{ \ + .bLength = sizeof(struct usb_if_descriptor), \ + .bDescriptorType = USB_INTERFACE_DESC, \ + .bInterfaceNumber = iface_num, \ + .bAlternateSetting = alt_setting, \ + .bNumEndpoints = eps_num, \ + .bInterfaceClass = AUDIO_CLASS, \ + .bInterfaceSubClass = iface_subclass, \ + .bInterfaceProtocol = 0, \ + .iInterface = 0, \ +} + +#define INIT_CS_AC_IF(dev, i, ifaces) \ +{ \ + .bLength = sizeof(struct dev##_cs_ac_if_descriptor_##i), \ + .bDescriptorType = USB_CS_INTERFACE_DESC, \ + .bDescriptorSubtype = USB_AUDIO_HEADER, \ + .bcdADC = sys_cpu_to_le16(0x0100), \ + .wTotalLength = sys_cpu_to_le16( \ + sizeof(struct dev##_cs_ac_if_descriptor_##i) + \ + INPUT_TERMINAL_DESC_SIZE + \ + sizeof(struct dev##_feature_unit_descriptor_##i) + \ + OUTPUT_TERMINAL_DESC_SIZE), \ + .bInCollection = ifaces, \ + .baInterfaceNr = {0}, \ +} + +#define INIT_CS_AC_IF_BIDIR(dev, i, ifaces) \ +{ \ + .bLength = sizeof(struct dev##_cs_ac_if_descriptor_##i), \ + .bDescriptorType = USB_CS_INTERFACE_DESC, \ + .bDescriptorSubtype = USB_AUDIO_HEADER, \ + .bcdADC = sys_cpu_to_le16(0x0100), \ + .wTotalLength = sys_cpu_to_le16( \ + sizeof(struct dev##_cs_ac_if_descriptor_##i) + \ + 2*INPUT_TERMINAL_DESC_SIZE + \ + sizeof(struct dev##_MIC_feature_unit_descriptor_##i) + \ + sizeof(struct dev##_HP_feature_unit_descriptor_##i) + \ + 2*OUTPUT_TERMINAL_DESC_SIZE), \ + .bInCollection = ifaces, \ + .baInterfaceNr = {0}, \ +} + +#define INIT_IN_TERMINAL(dev, i, terminal_id, type) \ +{ \ + .bLength = INPUT_TERMINAL_DESC_SIZE, \ + .bDescriptorType = USB_CS_INTERFACE_DESC, \ + .bDescriptorSubtype = USB_AUDIO_INPUT_TERMINAL, \ + .bTerminalID = terminal_id, \ + .wTerminalType = sys_cpu_to_le16(type), \ + .bAssocTerminal = 0, \ + .bNrChannels = MAX(1, CH_CNT(dev, i)), \ + .wChannelConfig = sys_cpu_to_le16(CH_CFG(dev, i)), \ + .iChannelNames = 0, \ + .iTerminal = 0, \ +} + +#define INIT_OUT_TERMINAL(terminal_id, source_id, type) \ +{ \ + .bLength = OUTPUT_TERMINAL_DESC_SIZE, \ + .bDescriptorType = USB_CS_INTERFACE_DESC, \ + .bDescriptorSubtype = USB_AUDIO_OUTPUT_TERMINAL,\ + .bTerminalID = terminal_id, \ + .wTerminalType = sys_cpu_to_le16(type), \ + .bAssocTerminal = 0, \ + .bSourceID = source_id, \ + .iTerminal = 0, \ +} + +/** refer to Table 4-7 from audio10.pdf + */ +#define INIT_FEATURE_UNIT(dev, i, unit_id, source_id) \ +{ \ + .bLength = sizeof(struct dev##_feature_unit_descriptor_##i), \ + .bDescriptorType = USB_CS_INTERFACE_DESC, \ + .bDescriptorSubtype = USB_AUDIO_FEATURE_UNIT, \ + .bUnitID = unit_id, \ + .bSourceID = source_id, \ + .bControlSize = sizeof(u16_t), \ + .bmaControls = { FEATURES(dev, i) }, \ + .iFeature = 0, \ +} + +/* Class-Specific AS Interface Descriptor 4.5.2 audio10.pdf */ +#define INIT_AS_GENERAL(link) \ +{ \ + .bLength = USB_AC_CS_IF_DESC_SIZE, \ + .bDescriptorType = USB_CS_INTERFACE_DESC, \ + .bDescriptorSubtype = USB_AUDIO_AS_GENERAL, \ + .bTerminalLink = link, \ + .bDelay = 0, \ + .wFormatTag = sys_cpu_to_le16(0x0001), \ +} + +/** Class-Specific AS Format Type Descriptor 4.5.3 audio10.pdf + * For more information refer to 2.2.5 Type I Format Type Descriptor + * from frmts10.pdf + */ +#define INIT_AS_FORMAT_I(ch_cnt, res) \ +{ \ + .bLength = sizeof(struct format_type_i_descriptor), \ + .bDescriptorType = USB_CS_INTERFACE_DESC, \ + .bDescriptorSubtype = USB_AUDIO_FORMAT_TYPE, \ + .bFormatType = 0x01, \ + .bNrChannels = MAX(1, ch_cnt), \ + .bSubframeSize = res/8, \ + .bBitResolution = res, \ + .bSamFreqType = 1, \ + .tSamFreq = {0x80, 0xBB, 0x00}, \ +} + +#define INIT_STD_AS_AD_EP(dev, i, addr) \ +{ \ + .bLength = sizeof(struct std_as_ad_endpoint_descriptor), \ + .bDescriptorType = USB_ENDPOINT_DESC, \ + .bEndpointAddress = addr, \ + .bmAttributes = (USB_DC_EP_ISOCHRONOUS | SYNC_TYPE(dev, i)), \ + .wMaxPacketSize = sys_cpu_to_le16(EP_SIZE(dev, i)), \ + .bInterval = 0x01, \ + .bRefresh = 0x00, \ + .bSynchAddress = 0x00, \ +} + +#define INIT_CS_AS_AD_EP \ +{ \ + .bLength = sizeof(struct cs_as_ad_ep_descriptor), \ + .bDescriptorType = USB_CS_ENDPOINT_DESC, \ + .bDescriptorSubtype = 0x01, \ + .bmAttributes = 0x00, \ + .bLockDelayUnits = 0x00, \ + .wLockDelay = 0, \ +} + +#define INIT_EP_DATA(cb, addr) \ + { \ + .ep_cb = cb, \ + .ep_addr = addr,\ + } + +#endif /* ZEPHYR_INCLUDE_USB_CLASS_AUDIO_INTERNAL_H_ */