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

subsys:usb:device:audio: Support volume control #60973

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 37 additions & 0 deletions dts/bindings/usb/usb-audio-feature-volume.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) 2023, LISTENAI
# SPDX-License-Identifier: Apache-2.0
#
# Specific fields for USB volume control.

description: USB volume control specific fields.

compatible: "usb-audio-feature-volume"

include: base.yaml

properties:
volume-max:
type: int
default: 0x0A00
description: |
attention: this attribute is a signed value.
This attribute represents the maximum volume level.
The range from +127.9961 dB (0x7FFF) down to -127.9961 dB (0x8001).
Valid range: 0 - 0xFFFF
volume-min:
type: int
default: 0xBA00
description: |
attention: this attribute is a signed value.
This attribute represents the minimum volume level.
The range from +127.9961 dB (0x7FFF) down to -127.9961 dB (0x8001).
Valid range: 0 - 0xFFFF
volume-res:
type: int
default: 0x100
description: |
attention: this attribute can only take positive values.
This attribute represents the volume resolution(step).
1 = 1/256 dB or 0.00390625 dB.
0x100(256) = 1dB.
Valid range: 1 - 0x7FFF
2 changes: 1 addition & 1 deletion dts/bindings/usb/usb-audio-hp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: USB Audio headphones specific fields.

compatible: "usb-audio-hp"

include: usb-audio.yaml
include: [usb-audio.yaml, usb-audio-feature-volume.yaml]

properties:
resolution:
Expand Down
3 changes: 1 addition & 2 deletions dts/bindings/usb/usb-audio-hs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: USB Audio headset specific fields.

compatible: "usb-audio-hs"

include: usb-audio.yaml
include: [usb-audio.yaml, usb-audio-feature-volume.yaml]

properties:
mic-resolution:
Expand Down Expand Up @@ -167,7 +167,6 @@ properties:
type: boolean
description: |
Enable Volume feature.
Currently not supported.
hp-feature-tone-control:
type: boolean
description: |
Expand Down
5 changes: 5 additions & 0 deletions samples/subsys/usb/audio/headphones_microphone/app.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
feature-mute;
channel-l;
channel-r;

feature-volume;
volume-max = <0x0500>;
volume-min = <0xBA00>;
volume-res = <0x100>;
};
mic_0 {
compatible = "usb-audio-mic";
Expand Down
7 changes: 7 additions & 0 deletions samples/subsys/usb/audio/headphones_microphone/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,17 @@ static void data_received(const struct device *dev,
static void feature_update(const struct device *dev,
const struct usb_audio_fu_evt *evt)
{
int16_t volume = 0;

LOG_DBG("Control selector %d for channel %d updated",
evt->cs, evt->channel);
switch (evt->cs) {
case USB_AUDIO_FU_MUTE_CONTROL:
break;
case USB_AUDIO_FU_VOLUME_CONTROL:
volume = *((int16_t *)(evt->val));
LOG_INF("set volume: %d", volume);
break;
default:
break;
}
Expand Down
5 changes: 5 additions & 0 deletions samples/subsys/usb/audio/headset/app.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@
hp-feature-mute;
hp-channel-l;
hp-channel-r;

hp-feature-volume;
volume-max = <0x0500>;
volume-min = <0xBA00>;
volume-res = <0x100>;
};
};
7 changes: 7 additions & 0 deletions samples/subsys/usb/audio/headset/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,17 @@ static void data_received(const struct device *dev,
static void feature_update(const struct device *dev,
const struct usb_audio_fu_evt *evt)
{
int16_t volume = 0;

LOG_DBG("Control selector %d for channel %d updated",
evt->cs, evt->channel);
switch (evt->cs) {
case USB_AUDIO_FU_MUTE_CONTROL:
break;
case USB_AUDIO_FU_VOLUME_CONTROL:
volume = *((int16_t *)(evt->val));
LOG_INF("set volume: %d", volume);
break;
default:
break;
}
Expand Down
92 changes: 86 additions & 6 deletions subsys/usb/device/class/audio/audio.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(usb_audio, CONFIG_USB_AUDIO_LOG_LEVEL);

struct feature_volume {
int16_t volume_max;
int16_t volume_min;
int16_t volume_res;
};

/* Device data structure */
struct usb_audio_dev_data {
const struct usb_audio_ops *ops;
Expand All @@ -41,6 +47,9 @@ struct usb_audio_dev_data {
/* Not applicable for Headphones, left with 0 */
uint16_t in_frame_size;

/* Not applicable for not support volume feature device */
struct feature_volume volumes;

bool rx_enable;
bool tx_enable;
};
Expand Down Expand Up @@ -146,11 +155,14 @@ static struct usb_ep_cfg_data dev##_usb_audio_ep_data_##i[] = { \

#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} \
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}, \
.volumes.volume_max = GET_VOLUME(dev, i, volume_max), \
.volumes.volume_min = GET_VOLUME(dev, i, volume_min), \
.volumes.volume_res = GET_VOLUME(dev, i, volume_res), \
}

#define DEFINE_AUDIO_DEV_DATA_BIDIR(dev, i, __out_pool, __in_pool_size) \
Expand All @@ -161,7 +173,10 @@ static struct usb_ep_cfg_data dev##_usb_audio_ep_data_##i[] = { \
.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)} \
(CH_CNT(dev##_HP, i) + 1)}, \
.volumes.volume_max = GET_VOLUME(dev, i, volume_max), \
.volumes.volume_min = GET_VOLUME(dev, i, volume_min), \
.volumes.volume_res = GET_VOLUME(dev, i, volume_res), \
}

/**
Expand Down Expand Up @@ -541,6 +556,67 @@ static int handle_fu_mute_req(struct usb_audio_dev_data *audio_dev_data,
return -EINVAL;
}


static int handle_fu_volume_req(struct usb_audio_dev_data *audio_dev_data,
struct usb_setup_packet *setup, int32_t *len, uint8_t **data,
struct usb_audio_fu_evt *evt, uint8_t device)
{
uint8_t ch = (setup->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(VOLUME, ch, ch_cnt)];
tmon-nordic marked this conversation as resolved.
Show resolved Hide resolved
int16_t target_vol = 0;
int16_t temp_vol = 0;

if (usb_reqtype_is_to_device(setup)) {
/* Check if *len has valid value */
if (*len != LEN(1, VOLUME)) {
LOG_ERR("*len: %d, LEN(1, VOLUME): %d", *len, LEN(1, VOLUME));
return -EINVAL;
}
if (setup->bRequest == USB_AUDIO_SET_CUR) {
target_vol = *((int16_t *)*data);
if (!IN_RANGE(target_vol, audio_dev_data->volumes.volume_min,
audio_dev_data->volumes.volume_max)) {
LOG_ERR("Volume out of range: %d", target_vol);
return -EINVAL;
}
if (target_vol % audio_dev_data->volumes.volume_res != 0) {
target_vol = ROUND_UP(target_vol,
audio_dev_data->volumes.volume_res);
}
evt->val = control_val;
evt->val_len = *len;
*((int16_t *)evt->val) = sys_le16_to_cpu(target_vol);
return 0;
}
} else {
if (setup->bRequest == USB_AUDIO_GET_CUR) {
*len = LEN(ch_cnt, VOLUME);
temp_vol = sys_cpu_to_le16(*(int16_t *)control_val);
memcpy(*data, &temp_vol, *len);
return 0;
} else if (setup->bRequest == USB_AUDIO_GET_MIN) {
*len = sizeof(audio_dev_data->volumes.volume_min);
temp_vol = sys_cpu_to_le16(audio_dev_data->volumes.volume_min);
memcpy(*data, &temp_vol, *len);
return 0;
} else if (setup->bRequest == USB_AUDIO_GET_MAX) {
*len = sizeof(audio_dev_data->volumes.volume_max);
temp_vol = sys_cpu_to_le16(audio_dev_data->volumes.volume_max);
memcpy(*data, &temp_vol, *len);
return 0;
} else if (setup->bRequest == USB_AUDIO_GET_RES) {
*len = sizeof(audio_dev_data->volumes.volume_res);
temp_vol = sys_cpu_to_le16(audio_dev_data->volumes.volume_res);
memcpy(*data, &temp_vol, *len);
return 0;
}
}

return -EINVAL;
}

/**
* @brief Handler for feature unit requests.
*
Expand Down Expand Up @@ -590,6 +666,10 @@ static int handle_feature_unit_req(struct usb_audio_dev_data *audio_dev_data,
ret = handle_fu_mute_req(audio_dev_data, pSetup,
len, data, &evt, device);
break;
case USB_AUDIO_FU_VOLUME_CONTROL:
ret = handle_fu_volume_req(audio_dev_data, pSetup,
len, data, &evt, device);
break;
default:
return -ENOTSUP;
}
Expand Down
5 changes: 5 additions & 0 deletions subsys/usb/device/class/audio/usb_audio_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@
#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 GET_VOLUME_HS(i, prop) DT_PROP_OR(DT_INST(i, COMPAT_HS), prop, 0)
#define GET_VOLUME_HP(i, prop) DT_PROP_OR(DT_INST(i, COMPAT_HP), prop, 0)
#define GET_VOLUME_MIC(i, prop) DT_PROP_OR(DT_INST(i, COMPAT_MIC), prop, 0)
#define GET_VOLUME(dev, i, prop) GET_VOLUME_##dev(i, prop)

#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
Expand Down