Replace all macros and types with the new ones from usb/usb_ch9.h header. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
970 lines
29 KiB
C
970 lines
29 KiB
C
/*
|
|
* Copyright (c) 2020 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
* @brief Audio device class driver
|
|
*
|
|
* Driver for USB Audio device class driver
|
|
*/
|
|
|
|
#include <kernel.h>
|
|
#include <usb/usb_device.h>
|
|
#include <usb_descriptor.h>
|
|
#include <usb/class/usb_audio.h>
|
|
#include "usb_audio_internal.h"
|
|
|
|
#include <sys/byteorder.h>
|
|
#include <sys/util.h>
|
|
#include <net/buf.h>
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(usb_audio, CONFIG_USB_AUDIO_LOG_LEVEL);
|
|
|
|
/* Device data structure */
|
|
struct usb_audio_dev_data {
|
|
const struct usb_audio_ops *ops;
|
|
|
|
uint8_t *controls[2];
|
|
|
|
uint8_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 */
|
|
uint16_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 uint8_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 uint8_t dev##_controls0_##i[FEATURES_SIZE(dev##_MIC, i)] = {0};\
|
|
static uint8_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 uint8_t get_num_of_channels(const struct feature_unit_descriptor *fu)
|
|
{
|
|
return (fu->bLength - FU_FIXED_ELEMS_SIZE)/sizeof(uint16_t);
|
|
}
|
|
|
|
/**
|
|
* Helper function for getting supported controls directly from
|
|
* the feature unit descriptor.
|
|
*/
|
|
static uint16_t get_controls(const struct feature_unit_descriptor *fu)
|
|
{
|
|
return sys_get_le16((uint8_t *)&fu->bmaControls[0]);
|
|
}
|
|
|
|
/**
|
|
* 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 *)
|
|
((uint8_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 *)
|
|
((uint8_t *)iface + USB_PASSIVE_IF_DESC_SIZE);
|
|
|
|
fu = (struct feature_unit_descriptor *)((uint8_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++) {
|
|
(void)memcpy(&fu->bmaControls[i],
|
|
&fu->bmaControls[0],
|
|
sizeof(uint16_t));
|
|
}
|
|
|
|
if (header->bInCollection == 2) {
|
|
fu = (struct feature_unit_descriptor *)((uint8_t *)fu +
|
|
fu->bLength +
|
|
INPUT_TERMINAL_DESC_SIZE +
|
|
OUTPUT_TERMINAL_DESC_SIZE);
|
|
for (int i = 1; i < get_num_of_channels(fu); i++) {
|
|
(void)memcpy(&fu->bmaControls[i],
|
|
&fu->bmaControls[0],
|
|
sizeof(uint16_t));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
uint8_t *device, uint8_t fu_id)
|
|
{
|
|
struct feature_unit_descriptor *fu;
|
|
|
|
fu = (struct feature_unit_descriptor *)
|
|
((uint8_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 *)
|
|
((uint8_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)
|
|
{
|
|
uint8_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,
|
|
uint8_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 *)
|
|
((uint8_t *)iface + iface->bLength);
|
|
header->baInterfaceNr[0] = bInterfaceNumber + 1;
|
|
|
|
/* Audio Streaming Interface Passive */
|
|
iface = (struct usb_if_descriptor *)
|
|
((uint8_t *)header + header->wTotalLength);
|
|
iface->bInterfaceNumber = bInterfaceNumber + 1;
|
|
|
|
/* Audio Streaming Interface Active */
|
|
iface = (struct usb_if_descriptor *)
|
|
((uint8_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 *)
|
|
((uint8_t *)iface + USB_ACTIVE_IF_DESC_SIZE);
|
|
iface->bInterfaceNumber = bInterfaceNumber + 2;
|
|
|
|
/* Audio Streaming Interface Active */
|
|
iface = (struct usb_if_descriptor *)
|
|
((uint8_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 uint8_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 *)((uint8_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 *)((uint8_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,
|
|
uint8_t interface)
|
|
{
|
|
const struct cs_ac_if_descriptor *header;
|
|
|
|
header = audio_dev_data->desc_hdr;
|
|
uint8_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(uint8_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,
|
|
int32_t *len, uint8_t **data,
|
|
struct usb_audio_fu_evt *evt,
|
|
uint8_t device)
|
|
{
|
|
uint8_t ch = (pSetup->wValue) & 0xFF;
|
|
uint8_t ch_cnt = audio_dev_data->ch_cnt[device];
|
|
uint8_t *controls = audio_dev_data->controls[device];
|
|
uint8_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,
|
|
int32_t *len, uint8_t **data)
|
|
{
|
|
const struct feature_unit_descriptor *fu;
|
|
struct usb_audio_fu_evt evt;
|
|
enum usb_audio_fucs cs;
|
|
uint8_t device;
|
|
uint8_t fu_id;
|
|
uint8_t ch_cnt;
|
|
uint8_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,
|
|
int32_t *len,
|
|
uint8_t **data)
|
|
{
|
|
struct usb_audio_dev_data *audio_dev_data;
|
|
struct usb_audio_entity entity;
|
|
|
|
/* parse wIndex for interface request */
|
|
uint8_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.
|
|
*
|
|
* uint8_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, int32_t *len,
|
|
uint8_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;
|
|
|
|
uint8_t iface = (pSetup->wIndex) & 0xFF;
|
|
|
|
if (pSetup->RequestType.recipient != USB_REQTYPE_RECIPIENT_INTERFACE) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
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 *)((uint8_t *)header +
|
|
header->wTotalLength +
|
|
USB_PASSIVE_IF_DESC_SIZE);
|
|
|
|
if (if_desc->bInterfaceNumber == iface) {
|
|
ep_desc = (struct usb_ep_descriptor *)((uint8_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 *)((uint8_t *)if_desc +
|
|
USB_ACTIVE_IF_DESC_SIZE);
|
|
ep_desc = (struct usb_ep_descriptor *)((uint8_t *)if_desc +
|
|
USB_PASSIVE_IF_DESC_SIZE +
|
|
USB_AC_CS_IF_DESC_SIZE +
|
|
USB_FORMAT_TYPE_I_DESC_SIZE);
|
|
}
|
|
|
|
if (pSetup->bRequest == USB_SREQ_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;
|
|
}
|
|
|
|
/**
|
|
* @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,
|
|
int32_t *len, uint8_t **data)
|
|
{
|
|
LOG_DBG("bmRT 0x%02x, bR 0x%02x, wV 0x%04x, wI 0x%04x, wL 0x%04x",
|
|
pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue,
|
|
pSetup->wIndex, pSetup->wLength);
|
|
|
|
switch (pSetup->RequestType.recipient) {
|
|
case USB_REQTYPE_RECIPIENT_INTERFACE:
|
|
return handle_interface_req(pSetup, len, data);
|
|
default:
|
|
LOG_ERR("Request recipient invalid");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
static int usb_audio_device_init(const struct device *dev)
|
|
{
|
|
LOG_DBG("Init Audio Device: dev %p (%s)", dev, dev->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void audio_write_cb(uint8_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->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->data;
|
|
struct usb_cfg_data *cfg = (void *)dev->config;
|
|
/* EP ISO IN is always placed first in the endpoint table */
|
|
uint8_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->data;
|
|
|
|
return audio_dev_data->in_frame_size;
|
|
}
|
|
|
|
static void audio_receive_cb(uint8_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(const struct device *dev,
|
|
const struct usb_audio_ops *ops)
|
|
{
|
|
struct usb_audio_dev_data *audio_dev_data = dev->data;
|
|
const struct usb_cfg_data *cfg = dev->config;
|
|
const struct std_if_descriptor *iface_descr =
|
|
cfg->interface_descriptor;
|
|
const struct cs_ac_if_descriptor *header =
|
|
(struct cs_ac_if_descriptor *)
|
|
((uint8_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,
|
|
&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_DT_DEFINE(DT_INST(i, COMPAT_##dev), \
|
|
&usb_audio_device_init, \
|
|
NULL, \
|
|
&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)
|