blob: 44bc5e368a2ab7de97e24e581d58f64a4cabcced [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <driver/usb.h>
#include <stdlib.h>
#include <stdio.h>
#include "usb-audio.h"
uint32_t* usb_audio_parse_sample_rates(usb_audio_ac_format_type_i_desc* format_desc,
int* out_count) {
*out_count = 0;
// sanity check the descriptor
int count = format_desc->bSamFreqType;
if (count == 0 || format_desc->bLength < sizeof(*format_desc) +
(sizeof(format_desc->tSamFreq[0]) * count)) {
printf("malformed format_desc in usb_audio_parse_sample_rates\n");
return NULL;
}
uint32_t* result = (uint32_t *)malloc(count * sizeof(uint32_t));
if (!result) return NULL;
usb_audio_ac_samp_freq* ptr = format_desc->tSamFreq;
for (int i = 0; i < count; i++) {
uint32_t freq = ptr->freq[0] | (ptr->freq[1] << 8) | (ptr->freq[2] << 16);
result[i] = freq;
ptr++;
}
*out_count = count;
return result;
}
zx_status_t usb_audio_set_sample_rate(usb_protocol_t* usb, uint8_t ep_addr, uint32_t sample_rate) {
uint8_t buffer[3];
buffer[0] = sample_rate;
buffer[1] = sample_rate >> 8;
buffer[2] = sample_rate >> 16;
zx_status_t result = usb_control(usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT,
USB_AUDIO_SET_CUR,
USB_AUDIO_SAMPLING_FREQ_CONTROL << 8,
ep_addr, &buffer, sizeof(buffer), ZX_TIME_INFINITE, NULL);
return result;
}
zx_status_t get_feature_unit_ch_count(const usb_audio_ac_feature_unit_desc* fu_desc,
uint8_t* ch_count_out) {
if (!fu_desc->bControlSize) {
printf("[USB_AUD] Invalid ControlSize (%u) while computing feature unit channel count\n",
fu_desc->bControlSize);
return ZX_ERR_INVALID_ARGS;
}
// In addition to the fields listed in the feature_unit_desc struct, there
// is and additional single byte field (iFeature) which comes after the
// variable length control bitmaps field. Account for this when we sanity
// check our length.
const uint8_t overhead = sizeof(*fu_desc) + 1;
if (((fu_desc->bLength < (overhead + fu_desc->bControlSize)) ||
((fu_desc->bLength - overhead) % fu_desc->bControlSize))) {
printf("[USB_AUD] Invalid Length (%u) while computing feature unit channel count\n",
fu_desc->bLength);
return ZX_ERR_INVALID_ARGS;
}
*ch_count_out = (fu_desc->bLength - overhead) / fu_desc->bControlSize;
return ZX_OK;
}
void usb_audio_dump_feature_unit_caps(usb_protocol_t* usb,
uint8_t interface_number,
const usb_audio_ac_feature_unit_desc* fu_desc) {
printf("Feature unit dump for interface number %u\n", interface_number);
printf("Length : 0x%02x (%u)\n", fu_desc->bLength, fu_desc->bLength);
printf("DType : 0x%02x (%u)\n", fu_desc->bDescriptorType, fu_desc->bDescriptorType);
printf("DSubtype : 0x%02x (%u)\n", fu_desc->bDescriptorSubtype, fu_desc->bDescriptorSubtype);
printf("UnitID : 0x%02x (%u)\n", fu_desc->bUnitID, fu_desc->bUnitID);
printf("SrcID : 0x%02x (%u)\n", fu_desc->bSourceID, fu_desc->bSourceID);
printf("CtrlSz : 0x%02x (%u)\n", fu_desc->bControlSize, fu_desc->bControlSize);
uint8_t ch_count;
if (get_feature_unit_ch_count(fu_desc, &ch_count) != ZX_OK) {
return;
}
const uint8_t* bma = fu_desc->bmaControls;
for (uint8_t i = 0; i < ch_count; ++i) {
printf("CBma[%3u] : 0x", i);
for (uint8_t j = 0; j < fu_desc->bControlSize; ++j) {
printf("%02x", bma[fu_desc->bControlSize - j - 1]);
}
printf("\n");
bma += fu_desc->bControlSize;
}
}
// volume is in 0 - 100 range
zx_status_t usb_audio_set_volume(usb_protocol_t* usb,
uint8_t interface_number,
const usb_audio_ac_feature_unit_desc* fu_desc,
int volume) {
zx_status_t status;
size_t out_length;
uint8_t ch_count;
if ((volume < 0) || (volume > 100)) {
printf("[USB_AUD] Bad volume (%d)\n", volume);
status = ZX_ERR_INVALID_ARGS;
goto out;
}
status = get_feature_unit_ch_count(fu_desc, &ch_count);
if (status != ZX_OK) {
printf("[USB_AUD] Failed to parse feature unit descriptor\n");
goto out;
}
const uint16_t unit_addr = (((uint16_t)fu_desc->bUnitID) << 8) | interface_number;
for (uint8_t ch = 0; ch < ch_count; ++ch) {
uint8_t caps_bma = fu_desc->bmaControls[ch * fu_desc->bControlSize];
if (caps_bma & USB_AUDIO_FU_BMA_MUTE) {
uint8_t val = (volume == 0) ? 1 : 0;
const uint16_t ctrl_addr = (USB_AUDIO_MUTE_CONTROL << 8) | ch;
status = usb_control(usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
USB_AUDIO_SET_CUR, ctrl_addr, unit_addr,
&val, sizeof(val), ZX_TIME_INFINITE,
&out_length);
if ((status != ZX_OK) || (out_length != sizeof(val))) {
printf("[USB_AUD] Failed to set mute; IID %u FeatUnitID %u Ch %u\n",
interface_number, fu_desc->bUnitID, ch);
goto out;
}
}
if (caps_bma & USB_AUDIO_FU_BMA_VOLUME) {
int16_t min, max, val;
const uint16_t ctrl_addr = (USB_AUDIO_VOLUME_CONTROL << 8) | ch;
status = usb_control(usb, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
USB_AUDIO_GET_MIN, ctrl_addr, unit_addr,
&min, sizeof(min), ZX_TIME_INFINITE,
&out_length);
if ((status != ZX_OK) || (out_length != sizeof(val))) {
printf("[USB_AUD] Failed to to fetch min vol; IID %u FeatUnitID %u Ch %u\n",
interface_number, fu_desc->bUnitID, ch);
goto out;
}
status = usb_control(usb, USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
USB_AUDIO_GET_MAX, ctrl_addr, unit_addr,
&max, sizeof(max), ZX_TIME_INFINITE,
&out_length);
if ((status != ZX_OK) || (out_length != sizeof(val))) {
printf("[USB_AUD] Failed to to fetch max vol; IID %u FeatUnitID %u Ch %u\n",
interface_number, fu_desc->bUnitID, ch);
goto out;
}
val = (int16_t)(((((int32_t)(max - min)) * volume) / 100) + min);
status = usb_control(usb, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
USB_AUDIO_SET_CUR, ctrl_addr, unit_addr,
&val, sizeof(val), ZX_TIME_INFINITE,
&out_length);
if ((status != ZX_OK) || (out_length != sizeof(val))) {
printf("[USB_AUD] Failed to to set vol; IID %u FeatUnitID %u Ch %u\n",
interface_number, fu_desc->bUnitID, ch);
goto out;
}
}
}
out:
if (status == ZX_ERR_IO_REFUSED) {
// clear the stall
usb_reset_endpoint(usb, 0);
}
return status;
}