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

usb: device_next: new USB Audio 2 implementation #67614

Merged
merged 3 commits into from
Feb 1, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/connectivity/usb/device/usb_device.rst
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,8 @@ The following Product IDs are currently used:
+----------------------------------------------------+--------+
| :zephyr:code-sample:`wpan-usb` | 0x000D |
+----------------------------------------------------+--------+
| :zephyr:code-sample:`uac2-explicit-feedback` | 0x000E |
+----------------------------------------------------+--------+

The USB device descriptor field ``bcdDevice`` (Device Release Number) represents
the Zephyr kernel major and minor versions as a binary coded decimal value.
139 changes: 139 additions & 0 deletions include/zephyr/usb/class/usbd_uac2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2023-2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @file
* @brief USB Audio Class 2 device public header
*
* This header describes only class API interaction with application.
* The audio device itself is modelled with devicetree zephyr,uac2 compatible.
*
* This API is currently considered experimental.
*/

#ifndef ZEPHYR_INCLUDE_USB_CLASS_USBD_UAC2_H_
#define ZEPHYR_INCLUDE_USB_CLASS_USBD_UAC2_H_

#include <zephyr/device.h>

#define UAC2_ENTITY_ID(node) \
({ \
BUILD_ASSERT(DT_NODE_HAS_COMPAT(DT_PARENT(node), zephyr_uac2)); \
UTIL_INC(DT_NODE_CHILD_IDX(node)); \
})

/**
* @brief USB Audio 2 application event handlers
*/
struct uac2_ops {
/**
* @brief Start of Frame callback
*
* Notifies application about SOF event on the bus.
*
* @param dev USB Audio 2 device
* @param user_data Opaque user data pointer
*/
void (*sof_cb)(const struct device *dev, void *user_data);
/**
* @brief Terminal update callback
*
* Notifies application that host has enabled or disabled a terminal.
*
* @param dev USB Audio 2 device
* @param terminal Terminal ID linked to AudioStreaming interface
* @param enabled True if host enabled terminal, False otherwise
* @param microframes True if USB connection speed uses microframes
* @param user_data Opaque user data pointer
*/
void (*terminal_update_cb)(const struct device *dev, uint8_t terminal,
bool enabled, bool microframes,
void *user_data);
/**
* @brief Get receive buffer address
*
* USB stack calls this function to obtain receive buffer address for
* AudioStreaming interface. The buffer is owned by USB stack until
* @ref data_recv_cb callback is called. The buffer must be sufficiently
* aligned for use by UDC driver.
*
* @param dev USB Audio 2 device
* @param terminal Input Terminal ID linked to AudioStreaming interface
* @param size Maximum number of bytes USB stack will write to buffer.
* @param user_data Opaque user data pointer
*/
void *(*get_recv_buf)(const struct device *dev, uint8_t terminal,
uint16_t size, void *user_data);
/**
* @brief Data received
*
* This function releases buffer obtained in @ref get_recv_buf after USB
* has written data to the buffer and/or no longer needs it.
*
* @param dev USB Audio 2 device
* @param terminal Input Terminal ID linked to AudioStreaming interface
* @param buf Buffer previously obtained via @ref get_recv_buf
* @param size Number of bytes written to buffer
* @param user_data Opaque user data pointer
*/
void (*data_recv_cb)(const struct device *dev, uint8_t terminal,
void *buf, uint16_t size, void *user_data);
/**
* @brief Transmit buffer release callback
*
* This function releases buffer provided in @ref usbd_uac2_send when
* the class no longer needs it.
*
* @param dev USB Audio 2 device
* @param terminal Output Terminal ID linked to AudioStreaming interface
* @param buf Buffer previously provided via @ref usbd_uac2_send
* @param user_data Opaque user data pointer
*/
void (*buf_release_cb)(const struct device *dev, uint8_t terminal,
void *buf, void *user_data);
/**
* @brief Get Explicit Feedback value
*
* Explicit feedback value format depends terminal connection speed.
* If device is High-Speed capable, it must use Q16.16 format if and
* only if the @ref terminal_update_cb was called with microframes
* parameter set to true. On Full-Speed only devices, or if High-Speed
* capable device is operating at Full-Speed (microframes was false),
* the format is Q10.14 stored on 24 least significant bits (i.e. 8 most
* significant bits are ignored).
*
* @param dev USB Audio 2 device
* @param terminal Input Terminal ID whose feedback should be returned
* @param user_data Opaque user data pointer
*/
uint32_t (*feedback_cb)(const struct device *dev, uint8_t terminal,
void *user_data);
};

/**
* @brief Register USB Audio 2 application callbacks.
*
* @param dev USB Audio 2 device instance
* @param ops USB Audio 2 callback structure
* @param user_data Opaque user data to pass to ops callbacks
*/
void usbd_uac2_set_ops(const struct device *dev,
const struct uac2_ops *ops, void *user_data);

/**
* @brief Send audio data to output terminal
*
* @param dev USB Audio 2 device
* @param terminal Output Terminal ID linked to AudioStreaming interface
* @param data Buffer containing outgoing data
* @param size Number of bytes to send
*
* @return 0 on success, negative value on error
*/
int usbd_uac2_send(const struct device *dev, uint8_t terminal,
void *data, uint16_t size);

#endif /* ZEPHYR_INCLUDE_USB_CLASS_USBD_UAC2_H_ */
3 changes: 3 additions & 0 deletions include/zephyr/usb/usbd.h
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,9 @@ struct usbd_class_api {
/** USB power management handler resumed */
void (*resumed)(struct usbd_class_node *const node);

/** Start of Frame */
void (*sof)(struct usbd_class_node *const node);

/** Class associated configuration is selected */
void (*enable)(struct usbd_class_node *const node);

Expand Down
3 changes: 2 additions & 1 deletion samples/subsys/usb/common/sample_usbd_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ struct usbd_contex *sample_usbd_init_device(void)

/* Always use class code information from Interface Descriptors */
if (IS_ENABLED(CONFIG_USBD_CDC_ACM_CLASS) ||
IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS)) {
IS_ENABLED(CONFIG_USBD_CDC_ECM_CLASS) ||
IS_ENABLED(CONFIG_USBD_AUDIO2_CLASS)) {
/*
* Class with multiple interfaces have an Interface
* Association Descriptor available, use an appropriate triple
Expand Down
14 changes: 14 additions & 0 deletions samples/subsys/usb/uac2_explicit_feedback/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(usb_audio_async_i2s)

include(${ZEPHYR_BASE}/samples/subsys/usb/common/common.cmake)
target_sources(app PRIVATE src/main.c)

if (CONFIG_SOC_COMPATIBLE_NRF5340_CPUAPP)
target_sources(app PRIVATE src/feedback_nrf53.c)
else()
target_sources(app PRIVATE src/feedback_dummy.c)
endif()
19 changes: 19 additions & 0 deletions samples/subsys/usb/uac2_explicit_feedback/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2023-2024 Nordic Semiconductor ASA
tmon-nordic marked this conversation as resolved.
Show resolved Hide resolved
# SPDX-License-Identifier: Apache-2.0

menu "UAC2 external feedback sample options"

config APP_USE_I2S_LRCLK_EDGES_COUNTER
bool "Measure I2S LRCLK edges directly"
help
Use this to use I2S LRCLK edge counting for calculating feedback.
On nRF53 this option requires externally connecting I2S LRCLK back to
separate GPIOTE input pin (P1.09).
endmenu

# Source common USB sample options used to initialize new experimental USB
# device stack. The scope of these options is limited to USB samples in project
# tree, you cannot use them in your own application.
source "samples/subsys/usb/common/Kconfig.sample_usbd"

source "Kconfig.zephyr"
64 changes: 64 additions & 0 deletions samples/subsys/usb/uac2_explicit_feedback/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
.. zephyr:code-sample:: uac2-explicit-feedback
:name: USB Audio asynchronous explicit feedback sample
:relevant-api: _usb_device_core_api i2s_interface

USB Audio 2 explicit feedback sample playing audio on I2S.

Overview
********

This sample demonstrates how to implement USB asynchronous audio playback with
explicit feedback. It can run on any board with USB and I2S support, but the
feedback calculation is currently only implemented for the Nordic nRF5340 IC.

The device running this sample presents itself to the host as a Full-Speed
Asynchronous USB Audio 2 class device supporting 48 kHz 16-bit 2-channel
(stereo) playback.

Explicit Feedback
*****************

Asynchronous USB Audio is used when the actual sample clock is not controlled by
USB host. Because the sample clock is independent from USB SOF it is inevitable
that 1 ms according to audio sink (I2S) will be either slightly longer or
shorter than 1 ms according to audio source (USB host). In the long run, this
discrepancy leads to overruns or underruns. By providing explicit feedback to
host, the device can tell host how many samples on average it needs every frame.
The host achieves the average by sending either nominal or nominal ±1 sample
packets every frame.

The dummy feedback implementation, used when there is no target-specific
feedback code available, returns a feedback value that results in host sending
nominal number of samples every frame. Theoretically it should be possible to
obtain the timing information based on I2S and USB interrupts, but currently
neither subsystem provides the necessary timestamp information.

Explcit Feedback on nRF5340
***************************

The nRF5340 is capable of counting both edges of I2S LRCLK relative to USB SOF
with the use of DPPI, TIMER and GPIOTE input. Alternatively, if the GPIOTE input
is not available, the DPPI and TIMER peripherals on nRF5340 can be configured to
provide relative timing information between I2S FRAMESTART and USB SOF.

This sample in both modes (direct sample counting and indirect I2S buffer output
to USB SOF offset) has been tested on :ref:`nrf5340dk_nrf5340`.

The sample defaults to indirect feedback calculation because direct sample
counting requires external connection between I2S LRCLK output pin to GPIOTE
input pin (hardcoded to P1.09) on :ref:`nrf5340dk_nrf5340`. In the indirect mode
no extra connections are necessary and the sample can even be used without any
I2S device connected where I2S signals can be checked e.g. on logic analyzer.

Building and Running
********************

The code can be found in :zephyr_file:`samples/subsys/usb/uac2_explicit_feedback`.

To build and flash the application:

.. zephyr-app-commands::
:zephyr-app: samples/subsys/usb/uac2_explicit_feedback
:board: nrf5340dk_nrf5340_cpuapp
:goals: build flash
:compact:
44 changes: 44 additions & 0 deletions samples/subsys/usb/uac2_explicit_feedback/app.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <dt-bindings/usb/audio.h>

/ {
uac2_headphones: usb_audio2 {
compatible = "zephyr,uac2";
status = "okay";
audio-function = <AUDIO_FUNCTION_OTHER>;

uac_aclk: aclk {
compatible = "zephyr,uac2-clock-source";
clock-type = "internal-programmable";
frequency-control = "host-programmable";
sampling-frequencies = <48000>;
};

out_terminal: out_terminal {
compatible = "zephyr,uac2-input-terminal";
clock-source = <&uac_aclk>;
terminal-type = <USB_TERMINAL_STREAMING>;
front-left;
front-right;
};

headphones_output: headphones {
compatible = "zephyr,uac2-output-terminal";
data-source = <&out_terminal>;
clock-source = <&uac_aclk>;
terminal-type = <OUTPUT_TERMINAL_HEADPHONES>;
};

as_iso_out: out_interface {
compatible = "zephyr,uac2-audio-streaming";
linked-terminal = <&out_terminal>;
subslot-size = <2>;
bit-resolution = <16>;
};
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#Enable timer for asynchronous feedback
CONFIG_NRFX_TIMER2=y
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#include "../app.overlay"

&pinctrl {
Copy link
Collaborator

@jfischer-no jfischer-no Jan 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone might try to use an Arduino shield with this sample code or copy&paste it to own application, for whatever reason. Should arduino_spi node be deleted? @gmarull

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i2s0_default_alt: i2s0_default_alt {
group1 {
psels = <NRF_PSEL(I2S_SCK_M, 1, 15)>,
<NRF_PSEL(I2S_LRCK_M, 1, 12)>,
<NRF_PSEL(I2S_SDOUT, 1, 13)>;
};
};
};

&clock {
hfclkaudio-frequency = <12288000>;
};

i2s_tx: &i2s0 {
status = "okay";
pinctrl-0 = <&i2s0_default_alt>;
pinctrl-names = "default";
clock-source = "ACLK";
};
12 changes: 12 additions & 0 deletions samples/subsys/usb/uac2_explicit_feedback/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
CONFIG_I2S=y

#USB related configs
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_USBD_AUDIO2_CLASS=y
CONFIG_SAMPLE_USBD_PID=0x000E
CONFIG_SAMPLE_USBD_PRODUCT="UAC2 explicit feedback sample"

#LOG subsystem related configs
CONFIG_LOG=y
CONFIG_USBD_LOG_LEVEL_WRN=y
CONFIG_UDC_DRIVER_LOG_LEVEL_WRN=y
6 changes: 6 additions & 0 deletions samples/subsys/usb/uac2_explicit_feedback/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
sample:
name: USB Audio 2 asynchronous explicit feedback sample
tests:
sample.subsys.usb.uac2_explicit_feedback:
tags: usb i2s
platform_allow: nrf5340dk_nrf5340_cpuapp
23 changes: 23 additions & 0 deletions samples/subsys/usb/uac2_explicit_feedback/src/feedback.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef FEEDBACK_H_
#define FEEDBACK_H_

#include <stdint.h>

/* Nominal number of samples received on each SOF. This sample is currently
* supporting only 48 kHz sample rate.
*/
#define SAMPLES_PER_SOF 48

struct feedback_ctx *feedback_init(void);
void feedback_reset_ctx(struct feedback_ctx *ctx);
void feedback_process(struct feedback_ctx *ctx);
void feedback_start(struct feedback_ctx *ctx, int i2s_blocks_queued);
uint32_t feedback_value(struct feedback_ctx *ctx);

#endif /* FEEDBACK_H_ */
Loading
Loading