blob: 1db889c43b06e0e64b21cc19dfa01afdeb15e502 [file] [log] [blame]
/**
* \file
*
* \brief USB Device Audio 1.0 interface.
*
* Copyright (C) 2011 Atmel Corporation. All rights reserved.
*
* \page License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel AVR product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
#include "conf_usb.h"
#include "usb_protocol.h"
#include "usb_protocol_audio.h"
#include "udd.h"
#include "udc.h"
#include "udi_audio.h"
#include <string.h>
#define UDI_AUDIO_NUM_BUFFERS 4
/**
* \addtogroup udi_audio_group
*
* @{
*/
/**
* \name Interface for UDC
*/
//@{
bool udi_audio_control_enable(void);
void udi_audio_control_disable(void);
bool udi_audio_control_setup(void);
bool udi_audio_stream_enable(void);
void udi_audio_stream_disable(void);
bool udi_audio_stream_setup(void);
uint8_t udi_audio_control_getsetting(void);
uint8_t udi_audio_stream_getsetting(void);
static void udi_audio_stream_feature_changed(void);
#ifdef UDI_AUDIO_ENABLE_AUDIO_OUT
static bool udi_audio_stream_out_start(void);
static void udi_audio_stream_out_stop(void);
static void udi_audio_stream_samples_received(udd_ep_status_t status, iram_size_t n);
#endif
#ifdef UDI_AUDIO_ENABLE_AUDIO_IN
static bool udi_audio_stream_in_start(void);
static void udi_audio_stream_in_stop(void);
static void udi_audio_stream_samples_sent(udd_ep_status_t status, iram_size_t n);
#endif
static uint8_t udi_audio_stream_buf_state[UDI_AUDIO_NUM_BUFFERS];
static uint8_t udi_audio_stream_buf[UDI_AUDIO_NUM_BUFFERS][UDI_AUDIO_EPS_SIZE];
static uint16_t udi_audio_stream_buf_len[UDI_AUDIO_NUM_BUFFERS];
static uint8_t udi_audio_buf_write_index = 0;
static uint8_t udi_audio_buf_read_index = 0;
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
static int16_t udi_audio_cur_volume = 0x0000;
static int16_t udi_audio_min_volume = 0x0000;
static int16_t udi_audio_max_volume = 0x7FFF;
static int16_t udi_audio_res_volume = 0x0001;
#endif
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_MUTE
static uint8_t udi_audio_cur_mute = false;
#endif
//! Global structure which contains standard UDI API for UDC
UDC_DESC_STORAGE udi_api_t udi_api_audio_control = {
.enable = udi_audio_control_enable,
.disable = udi_audio_control_disable,
.setup = udi_audio_control_setup,
.getsetting = udi_audio_control_getsetting,
};
UDC_DESC_STORAGE udi_api_t udi_api_audio_data = {
.enable = udi_audio_stream_enable,
.disable = udi_audio_stream_disable,
.setup = udi_audio_stream_setup,
.getsetting = udi_audio_stream_getsetting,
};
//@}
static uint8_t udi_audio_data_alt_setting = 0;
/**
* \name Internal routines
*/
//@{
bool udi_audio_control_enable(void)
{
return true;
}
void udi_audio_control_disable(void)
{
// Do nothing - control interface has no endpoints to manage
}
bool udi_audio_control_setup(void)
{
if (Udd_setup_is_in()) {
// GET Interface Requests
if (Udd_setup_type() == USB_REQ_TYPE_CLASS) {
// Requests Class Interface Get
switch (udd_g_ctrlreq.req.bRequest) {
case USB_REQ_AUDIO_GET_MIN:
switch (udd_g_ctrlreq.req.wValue >> 8) {
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_audio_min_volume;
udd_g_ctrlreq.payload_size =
sizeof(udi_audio_min_volume);
return true;
#endif
}
break;
case USB_REQ_AUDIO_GET_MAX:
switch (udd_g_ctrlreq.req.wValue >> 8) {
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_audio_max_volume;
udd_g_ctrlreq.payload_size =
sizeof(udi_audio_max_volume);
return true;
#endif
}
break;
case USB_REQ_AUDIO_GET_CUR:
switch (udd_g_ctrlreq.req.wValue >> 8) {
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_audio_cur_volume;
udd_g_ctrlreq.payload_size =
sizeof(udi_audio_cur_volume);
return true;
#endif
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_MUTE
case AUDIO_CONTROL_MUTE:
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_audio_cur_mute;
udd_g_ctrlreq.payload_size =
sizeof(udi_audio_cur_mute);
return true;
#endif
}
break;
case USB_REQ_AUDIO_GET_RES:
switch (udd_g_ctrlreq.req.wValue >> 8) {
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_audio_res_volume;
udd_g_ctrlreq.payload_size =
sizeof(udi_audio_res_volume);
return true;
#endif
}
break;
}
}
}
if (Udd_setup_is_out()) {
// SET Interface Requests
if (Udd_setup_type() == USB_REQ_TYPE_CLASS) {
// Requests Class Interface Set
switch (udd_g_ctrlreq.req.bRequest) {
case USB_REQ_AUDIO_SET_CUR:
switch (udd_g_ctrlreq.req.wValue >> 8) {
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_audio_cur_volume;
udd_g_ctrlreq.payload_size =
sizeof(udi_audio_cur_volume);
udd_g_ctrlreq.callback = udi_audio_stream_feature_changed;
return true;
#endif
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_MUTE
case AUDIO_CONTROL_MUTE:
udd_g_ctrlreq.payload =
(uint8_t *) &
udi_audio_cur_mute;
udd_g_ctrlreq.payload_size =
sizeof(udi_audio_cur_mute);
udd_g_ctrlreq.callback = udi_audio_stream_feature_changed;
return true;
#endif
}
break;
}
}
}
return false;
}
bool udi_audio_stream_enable(void)
{
usb_iface_desc_t *current_interface_selected;
current_interface_selected = udc_get_interface_desc();
udi_audio_data_alt_setting = current_interface_selected->bAlternateSetting;
udi_audio_buf_read_index = 0;
udi_audio_buf_write_index = 0;
#ifdef UDI_AUDIO_ENABLE_AUDIO_IN
if (udi_audio_data_alt_setting) {
if (!udi_audio_stream_in_start())
return false;
UDI_AUDIO_ENABLE_EXT();
}
else {
udi_audio_stream_in_stop();
UDI_AUDIO_DISABLE_EXT();
}
#endif
#ifdef UDI_AUDIO_ENABLE_AUDIO_OUT
if (udi_audio_data_alt_setting) {
if (!udi_audio_stream_out_start())
return false;
UDI_AUDIO_ENABLE_EXT();
}
else {
udi_audio_stream_out_stop();
UDI_AUDIO_DISABLE_EXT();
}
#endif
return true;
}
void udi_audio_stream_disable(void)
{
udi_audio_data_alt_setting = 0;
UDI_AUDIO_DISABLE_EXT();
}
bool udi_audio_stream_setup(void)
{
return false;
}
uint8_t udi_audio_control_getsetting(void)
{
// No alternative setting for the Audio control interface
return 0;
}
uint8_t udi_audio_stream_getsetting(void)
{
// Return if the audio stream is enabled or not (enabled when alt setting != 0)
return udi_audio_data_alt_setting;
}
void udi_audio_process(void)
{
#ifdef UDI_AUDIO_ENABLE_AUDIO_OUT
if (udi_audio_stream_buf_state[udi_audio_buf_write_index] == AUDIO_BUFFER_IDLE) {
udi_audio_stream_out_start();
}
if (udi_audio_stream_buf_state[udi_audio_buf_read_index] == AUDIO_BUFFER_FULL) {
UDI_AUDIO_PROCESS_EXT(udi_audio_stream_buf[udi_audio_buf_read_index],
udi_audio_stream_buf_len[udi_audio_buf_read_index]);
udi_audio_stream_buf_state[udi_audio_buf_read_index] = AUDIO_BUFFER_IDLE;
udi_audio_stream_buf_len[udi_audio_buf_read_index] = 0;
udi_audio_buf_read_index = (udi_audio_buf_read_index + 1) % UDI_AUDIO_NUM_BUFFERS;
}
#endif
#ifdef UDI_AUDIO_ENABLE_AUDIO_IN
if (udi_audio_stream_buf_state[udi_audio_buf_read_index] == AUDIO_BUFFER_FULL) {
udi_audio_stream_in_start();
}
#endif
}
void udi_audio_stream_set_feature_min(uint16_t feature, uint16_t value)
{
switch (feature)
{
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udi_audio_min_volume = value;
break;
#endif
}
udi_audio_stream_feature_changed();
}
void udi_audio_stream_set_feature_max(uint16_t feature, uint16_t value)
{
switch (feature)
{
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udi_audio_max_volume = value;
break;
#endif
}
udi_audio_stream_feature_changed();
}
void udi_audio_stream_set_feature_res(uint16_t feature, uint16_t value)
{
switch (feature)
{
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udi_audio_res_volume = value;
break;
#endif
}
udi_audio_stream_feature_changed();
}
void udi_audio_stream_set_feature_cur(uint16_t feature, uint16_t value)
{
switch (feature)
{
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
udi_audio_cur_volume = value;
break;
#endif
}
udi_audio_stream_feature_changed();
}
uint16_t udi_audio_stream_get_feature_cur(uint16_t feature)
{
switch (feature)
{
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
case AUDIO_CONTROL_VOLUME:
return udi_audio_cur_volume;
#endif
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_MUTE
case AUDIO_CONTROL_MUTE:
return udi_audio_cur_mute;
#endif
}
return 0;
}
#ifdef UDI_AUDIO_ENABLE_AUDIO_IN
bool udi_audio_stream_samples_send(uint8_t* buffer, uint8_t length)
{
while (!(udi_audio_stream_buf_state[udi_audio_buf_write_index] == AUDIO_BUFFER_IDLE)) {};
memcpy(udi_audio_stream_buf[udi_audio_buf_write_index], buffer, length);
udi_audio_stream_buf_len[udi_audio_buf_write_index] = length;
udi_audio_stream_buf_state[udi_audio_buf_write_index] = AUDIO_BUFFER_FULL;
udi_audio_buf_write_index = (udi_audio_buf_write_index + 1) % UDI_AUDIO_NUM_BUFFERS;
if (udi_audio_stream_buf_state[udi_audio_buf_read_index] == AUDIO_BUFFER_FULL) {
udi_audio_stream_in_start();
}
return true;
}
#endif
//-------------------------------------------------
//------- Internal routines to process data transfer
static void udi_audio_stream_feature_changed(void)
{
#if UDI_AUDIO_FEATURES & AUDIO_FEATURE_VOLUME
if (udi_audio_curr_volume < udi_audio_min_volume) {
udi_audio_curr_volume = udi_audio_min_volume;
}
else if (udi_audio_curr_volume > udi_audio_max_volume) {
udi_audio_curr_volume = udi_audio_max_volume;
}
#endif
}
#ifdef UDI_AUDIO_ENABLE_AUDIO_IN
static void udi_audio_stream_samples_sent(udd_ep_status_t status, iram_size_t n)
{
if (UDD_EP_TRANSFER_OK != status) {
// Abort reception
udi_audio_stream_buf_state[udi_audio_buf_read_index] = AUDIO_BUFFER_IDLE;
return;
}
udi_audio_stream_buf_state[udi_audio_buf_read_index] = AUDIO_BUFFER_IDLE;
udi_audio_stream_buf_len[udi_audio_buf_read_index] = n;
udi_audio_buf_read_index = (udi_audio_buf_read_index + 1) % UDI_AUDIO_NUM_BUFFERS;
if (udi_audio_stream_buf_state[udi_audio_buf_read_index] == AUDIO_BUFFER_FULL) {
udi_audio_stream_in_start();
}
}
static bool udi_audio_stream_in_start(void)
{
if (!udd_ep_run(UDI_AUDIO_EP_IN,
true,
udi_audio_stream_buf[udi_audio_buf_read_index],
udi_audio_stream_buf_len[udi_audio_buf_read_index],
udi_audio_stream_samples_sent)) {
return false;
}
udi_audio_stream_buf_state[udi_audio_buf_read_index] = AUDIO_BUFFER_BUSY;
return true;
}
static void udi_audio_stream_in_stop(void)
{
udd_ep_abort(UDI_AUDIO_EP_IN);
}
#endif
#ifdef UDI_AUDIO_ENABLE_AUDIO_OUT
static void udi_audio_stream_samples_received(udd_ep_status_t status, iram_size_t n)
{
if (UDD_EP_TRANSFER_OK != status) {
// Abort reception
udi_audio_stream_buf_state[udi_audio_buf_write_index] = AUDIO_BUFFER_IDLE;
return;
}
udi_audio_stream_buf_state[udi_audio_buf_write_index] = AUDIO_BUFFER_FULL;
udi_audio_stream_buf_len[udi_audio_buf_write_index] = n;
udi_audio_buf_write_index = (udi_audio_buf_write_index + 1) % UDI_AUDIO_NUM_BUFFERS;
if (udi_audio_stream_buf_state[udi_audio_buf_write_index] == AUDIO_BUFFER_IDLE) {
udi_audio_stream_out_start();
}
}
static bool udi_audio_stream_out_start(void)
{
if (!udd_ep_run(UDI_AUDIO_EP_OUT,
true,
udi_audio_stream_buf[udi_audio_buf_write_index],
UDI_AUDIO_EPS_SIZE,
udi_audio_stream_samples_received)) {
return false;
}
udi_audio_stream_buf_state[udi_audio_buf_write_index] = AUDIO_BUFFER_BUSY;
return true;
}
static void udi_audio_stream_out_stop(void)
{
udd_ep_abort(UDI_AUDIO_EP_OUT);
}
#endif
//@}