haiku/src/add-ons/kernel/drivers/audio/hda/hda_multi_audio.cpp
Jérôme Duval c71a6614e2 * try to enable EAPD on capable pin widgets
* added a pin capabilities attribute instead of input and output pin attributes
* added ATI and nVidia vendor ids definitions
* uses "mic in" and "line in" when pin colors are undefined


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@28839 a95241bf-73f2-0310-859d-f6bbb57e9c96
2009-01-01 17:13:43 +00:00

951 lines
28 KiB
C++

/*
* Copyright 2007-2008, Haiku, Inc. All Rights Reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ithamar Adema, ithamar AT unet DOT nl
* Axel Dörfler, axeld@pinc-software.de
*/
#include "hmulti_audio.h"
#include "driver.h"
#ifdef TRACE
# undef TRACE
#endif
#define TRACE_MULTI_AUDIO
#ifdef TRACE_MULTI_AUDIO
# define TRACE(a...) dprintf("\33[34mhda:\33[0m " a)
#else
# define TRACE(a...) ;
#endif
typedef enum {
B_MIX_GAIN = 1 << 0,
B_MIX_MUTE = 1 << 1
} mixer_type;
static multi_channel_info sChannels[] = {
{ 0, B_MULTI_OUTPUT_CHANNEL, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
{ 1, B_MULTI_OUTPUT_CHANNEL, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
{ 2, B_MULTI_INPUT_CHANNEL, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
{ 3, B_MULTI_INPUT_CHANNEL, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
{ 4, B_MULTI_OUTPUT_BUS, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS,
B_CHANNEL_MINI_JACK_STEREO },
{ 5, B_MULTI_OUTPUT_BUS, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS,
B_CHANNEL_MINI_JACK_STEREO },
{ 6, B_MULTI_INPUT_BUS, B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS,
B_CHANNEL_MINI_JACK_STEREO },
{ 7, B_MULTI_INPUT_BUS, B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS,
B_CHANNEL_MINI_JACK_STEREO },
};
static int32
format2size(uint32 format)
{
switch (format) {
case B_FMT_8BIT_S:
case B_FMT_16BIT:
return 2;
case B_FMT_18BIT:
case B_FMT_20BIT:
case B_FMT_24BIT:
case B_FMT_32BIT:
case B_FMT_FLOAT:
return 4;
default:
return -1;
}
}
static status_t
get_description(hda_audio_group* audioGroup, multi_description* data)
{
data->interface_version = B_CURRENT_INTERFACE_VERSION;
data->interface_minimum = B_CURRENT_INTERFACE_VERSION;
strcpy(data->friendly_name, "HD Audio");
strcpy(data->vendor_info, "Haiku");
int32 inChannels = 0;
if (audioGroup->record_stream != NULL)
inChannels = 2;
int32 outChannels = 0;
if (audioGroup->playback_stream != NULL)
outChannels = 2;
data->output_channel_count = outChannels;
data->output_bus_channel_count = outChannels;
data->input_channel_count = inChannels;
data->input_bus_channel_count = inChannels;
data->aux_bus_channel_count = 0;
dprintf("%s: request_channel_count: %ld\n", __func__,
data->request_channel_count);
if (data->request_channel_count >= (int)(sizeof(sChannels)
/ sizeof(sChannels[0]))) {
memcpy(data->channels, &sChannels, sizeof(sChannels));
}
/* determine output/input rates */
data->output_rates = audioGroup->widget.d.io.rates;
data->input_rates = audioGroup->widget.d.io.rates;
/* force existance of 48kHz if variable rates are not supported */
if (data->output_rates == 0)
data->output_rates = B_SR_48000;
if (data->input_rates == 0)
data->input_rates = B_SR_48000;
data->max_cvsr_rate = 0;
data->min_cvsr_rate = 0;
data->output_formats = audioGroup->widget.d.io.formats;
data->input_formats = audioGroup->widget.d.io.formats;
data->lock_sources = B_MULTI_LOCK_INTERNAL;
data->timecode_sources = 0;
data->interface_flags = B_MULTI_INTERFACE_PLAYBACK | B_MULTI_INTERFACE_RECORD;
data->start_latency = 30000;
strcpy(data->control_panel, "");
return B_OK;
}
static status_t
get_enabled_channels(hda_audio_group* audioGroup, multi_channel_enable* data)
{
B_SET_CHANNEL(data->enable_bits, 0, true);
B_SET_CHANNEL(data->enable_bits, 1, true);
B_SET_CHANNEL(data->enable_bits, 2, true);
B_SET_CHANNEL(data->enable_bits, 3, true);
data->lock_source = B_MULTI_LOCK_INTERNAL;
return B_OK;
}
static status_t
get_global_format(hda_audio_group* audioGroup, multi_format_info* data)
{
data->output_latency = 0;
data->input_latency = 0;
data->timecode_kind = 0;
if (audioGroup->playback_stream != NULL) {
data->output.format = audioGroup->playback_stream->sample_format;
data->output.rate = audioGroup->playback_stream->sample_rate;
} else {
data->output.format = 0;
data->output.rate = 0;
}
if (audioGroup->record_stream != NULL) {
data->input.format = audioGroup->record_stream->sample_format;
data->input.rate = audioGroup->record_stream->sample_format;
} else {
data->input.format = 0;
data->input.rate = 0;
}
return B_OK;
}
static status_t
set_global_format(hda_audio_group* audioGroup, multi_format_info* data)
{
// TODO: it looks like we're not supposed to fail; fix this!
#if 0
if ((data->output.format & audioGroup->supported_formats) == 0)
|| (data->output.rate & audioGroup->supported_rates) == 0)
return B_BAD_VALUE;
#endif
if (audioGroup->playback_stream != NULL) {
audioGroup->playback_stream->sample_format = data->output.format;
audioGroup->playback_stream->sample_rate = data->output.rate;
audioGroup->playback_stream->sample_size = format2size(
audioGroup->playback_stream->sample_format);
}
if (audioGroup->record_stream != NULL) {
audioGroup->record_stream->sample_rate = data->input.rate;
audioGroup->record_stream->sample_format = data->input.format;
audioGroup->record_stream->sample_size = format2size(
audioGroup->record_stream->sample_format);
}
return B_OK;
}
static int32
hda_create_group_control(hda_multi *multi, uint32 *index, int32 parent,
enum strind_id string, const char* name) {
uint32 i = *index;
(*index)++;
multi->controls[i].mix_control.id = MULTI_CONTROL_FIRSTID + i;
multi->controls[i].mix_control.parent = parent;
multi->controls[i].mix_control.flags = B_MULTI_MIX_GROUP;
multi->controls[i].mix_control.master = MULTI_CONTROL_MASTERID;
multi->controls[i].mix_control.string = string;
if (name)
strcpy(multi->controls[i].mix_control.name, name);
return multi->controls[i].mix_control.id;
}
static void
hda_create_channel_control(hda_multi *multi, uint32 *index, int32 parent, int32 string,
hda_widget& widget, bool input, uint32 capabilities, int32 inputIndex, bool &gain, bool& mute) {
uint32 i = *index, id;
hda_multi_mixer_control control;
control.nid = widget.node_id;
control.input = input;
control.mute = 0;
control.gain = 0;
control.capabilities = capabilities;
control.index = inputIndex;
control.mix_control.master = MULTI_CONTROL_MASTERID;
control.mix_control.parent = parent;
if (mute && capabilities & AMP_CAP_MUTE) {
control.mix_control.id = MULTI_CONTROL_FIRSTID + i;
control.mix_control.flags = B_MULTI_MIX_ENABLE;
control.mix_control.string = S_MUTE;
control.type = B_MIX_MUTE;
multi->controls[i++] = control;
TRACE("control nid %ld mute\n", control.nid);
mute = false;
}
if (gain && AMP_CAP_NUM_STEPS(capabilities) >= 1) {
control.mix_control.gain.granularity = AMP_CAP_STEP_SIZE(capabilities);
control.mix_control.gain.min_gain = (0.0 - AMP_CAP_OFFSET(capabilities))
* control.mix_control.gain.granularity;
control.mix_control.gain.max_gain = (AMP_CAP_NUM_STEPS(capabilities) - AMP_CAP_OFFSET(capabilities))
* control.mix_control.gain.granularity;
control.mix_control.id = MULTI_CONTROL_FIRSTID + i;
control.mix_control.flags = B_MULTI_MIX_GAIN;
control.mix_control.string = S_null;
control.type = B_MIX_GAIN;
strcpy(control.mix_control.name, "Gain");
multi->controls[i++] = control;
id = control.mix_control.id;
// second channel
control.mix_control.id = MULTI_CONTROL_FIRSTID + i;
control.mix_control.master = id;
multi->controls[i++] = control;
TRACE("control nid %ld %f min %f max %f\n", control.nid, control.mix_control.gain.granularity,
control.mix_control.gain.min_gain, control.mix_control.gain.max_gain);
gain = false;
}
*index = i;
}
static enum strind_id
hda_find_multi_string(hda_widget& widget)
{
switch (CONF_DEFAULT_DEVICE(widget.d.pin.config)) {
case PIN_DEV_CD:
return S_CD;
case PIN_DEV_LINE_IN:
case PIN_DEV_LINE_OUT:
return S_LINE;
case PIN_DEV_MIC_IN:
return S_MIC;
case PIN_DEV_AUX:
return S_AUX;
case PIN_DEV_SPDIF_IN:
case PIN_DEV_SPDIF_OUT:
return S_SPDIF;
case PIN_DEV_HEAD_PHONE_OUT:
return S_HEADPHONE;
}
TRACE("couln't find a string for widget %ld in hda_find_multi_string()\n", widget.node_id);
return S_null;
}
static const char *
hda_find_multi_custom_string(hda_widget& widget)
{
switch (CONF_DEFAULT_DEVICE(widget.d.pin.config)) {
case PIN_DEV_LINE_IN:
case PIN_DEV_LINE_OUT:
case PIN_DEV_MIC_IN:
switch (CONF_DEFAULT_COLOR(widget.d.pin.config)) {
case 1:
return "Rear";
case 2:
return "Side";
case 3:
return "Line In";
case 4:
return "Front";
case 6:
return "Center/Sub";
case 9:
return "Mic in";
}
if (CONF_DEFAULT_DEVICE(widget.d.pin.config) == PIN_DEV_LINE_IN)
return "Line In";
if (CONF_DEFAULT_DEVICE(widget.d.pin.config) == PIN_DEV_MIC_IN)
return "Mic In";
return "Line Out";
break;
case PIN_DEV_SPDIF_IN:
return "SPDIF In";
case PIN_DEV_SPDIF_OUT:
return "SPDIF Out";
case PIN_DEV_CD:
return "CD";
case PIN_DEV_HEAD_PHONE_OUT:
return "Headphones";
case PIN_DEV_SPEAKER:
return "Speaker";
}
TRACE("couldn't find a string for widget %ld in hda_find_multi_custom_string()\n", widget.node_id);
return NULL;
}
static void
hda_create_control_for_complex(hda_multi *multi, uint32 *index, uint32 parent,
hda_widget& widget, bool& gain, bool& mute)
{
hda_audio_group *audioGroup = multi->group;
switch (widget.type) {
case WT_AUDIO_OUTPUT:
case WT_AUDIO_MIXER:
case WT_AUDIO_SELECTOR:
case WT_PIN_COMPLEX:
break;
default:
return;
}
if (widget.flags & WIDGET_FLAG_WIDGET_PATH)
return;
TRACE(" create widget nid %lu\n", widget.node_id);
hda_create_channel_control(multi, index, parent, 0,
widget, false, widget.capabilities.output_amplifier, 0, gain, mute);
if (!gain && !mute) {
widget.flags |= WIDGET_FLAG_WIDGET_PATH;
return;
}
if (widget.type & WT_AUDIO_MIXER) {
hda_create_channel_control(multi, index, parent, 0,
widget, true, widget.capabilities.input_amplifier, 0, gain, mute);
if (!gain && !mute) {
widget.flags |= WIDGET_FLAG_WIDGET_PATH;
return;
}
}
if ((widget.type & WT_AUDIO_OUTPUT) == 0
&& widget.num_inputs > 0) {
hda_widget &child = * hda_audio_group_get_widget(audioGroup, widget.inputs[widget.active_input]);
hda_create_control_for_complex(multi, index, parent, child, gain, mute);
}
widget.flags |= WIDGET_FLAG_WIDGET_PATH;
}
static status_t
hda_create_controls_list(hda_multi *multi)
{
uint32 index = 0, parent, parent2;
hda_audio_group *audioGroup = multi->group;
parent = hda_create_group_control(multi, &index, 0, S_OUTPUT, NULL);
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& complex = audioGroup->widgets[i];
if (complex.type != WT_PIN_COMPLEX)
continue;
if (!PIN_CAP_IS_OUTPUT(complex.d.pin.capabilities))
continue;
if ((complex.flags & WIDGET_FLAG_OUTPUT_PATH) == 0)
continue;
TRACE("create complex nid %lu\n", complex.node_id);
parent2 = hda_create_group_control(multi, &index,
parent, S_null, hda_find_multi_custom_string(complex));
bool gain = true, mute = true;
hda_create_control_for_complex(multi, &index, parent2, complex, gain, mute);
}
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.type != WT_AUDIO_MIXER)
continue;
if (widget.flags & WIDGET_FLAG_WIDGET_PATH)
continue;
TRACE("create widget nid %lu\n", widget.node_id);
if (AMP_CAP_NUM_STEPS(widget.capabilities.input_amplifier) >= 1) {
for (uint32 j = 0; j < widget.num_inputs; j++) {
hda_widget *complex = hda_audio_group_get_widget(audioGroup, widget.inputs[j]);
if (complex->type != WT_PIN_COMPLEX)
continue;
if (!PIN_CAP_IS_INPUT(complex->d.pin.capabilities))
continue;
if (complex->flags & WIDGET_FLAG_OUTPUT_PATH)
continue;
TRACE(" create widget input nid %lu\n", widget.inputs[j]);
parent2 = hda_create_group_control(multi, &index,
parent, S_null, hda_find_multi_custom_string(*complex));
bool gain = true, mute = true;
hda_create_channel_control(multi, &index, parent2, 0,
widget, true, widget.capabilities.input_amplifier, j, gain, mute);
}
}
widget.flags |= WIDGET_FLAG_WIDGET_PATH;
}
parent = hda_create_group_control(multi, &index, 0, S_INPUT, NULL);
for (uint32 i = 0; i < audioGroup->widget_count; i++) {
hda_widget& widget = audioGroup->widgets[i];
if (widget.type != WT_AUDIO_INPUT)
continue;
uint32 capabilities = widget.capabilities.input_amplifier;
if (AMP_CAP_NUM_STEPS(capabilities) < 1)
continue;
parent2 = hda_create_group_control(multi, &index,
parent, hda_find_multi_string(widget), "Input");
bool gain = true, mute = true;
hda_create_channel_control(multi, &index, parent2, 0,
widget, true, capabilities, 0, gain, mute);
}
multi->control_count = index;
TRACE("multi->control_count %lu\n", multi->control_count);
return B_OK;
}
static status_t
list_mix_controls(hda_audio_group* audioGroup, multi_mix_control_info* MMCI)
{
multi_mix_control *MMC;
uint32 i;
MMC = MMCI->controls;
if (MMCI->control_count < 24)
return B_ERROR;
if (hda_create_controls_list(audioGroup->multi) < B_OK)
return B_ERROR;
for (i=0; i<audioGroup->multi->control_count; i++) {
MMC[i] = audioGroup->multi->controls[i].mix_control;
}
MMCI->control_count = audioGroup->multi->control_count;
return B_OK;
}
static status_t
list_mix_connections(hda_audio_group* audioGroup,
multi_mix_connection_info* data)
{
data->actual_count = 0;
return B_OK;
}
static status_t
list_mix_channels(hda_audio_group* audioGroup, multi_mix_channel_info *data)
{
return B_OK;
}
static void
get_control_gain_mute(hda_audio_group* audioGroup, hda_multi_mixer_control *control, uint32 *resp)
{
uint32 verb[2];
verb[0] = MAKE_VERB(audioGroup->codec->addr,
control->nid,
VID_GET_AMPLIFIER_GAIN_MUTE,
(control->input ? AMP_GET_INPUT : AMP_GET_OUTPUT)
| AMP_GET_LEFT_CHANNEL | AMP_GET_INPUT_INDEX(control->index));
verb[1] = MAKE_VERB(audioGroup->codec->addr,
control->nid,
VID_GET_AMPLIFIER_GAIN_MUTE,
(control->input ? AMP_GET_INPUT : AMP_GET_OUTPUT)
| AMP_GET_RIGHT_CHANNEL | AMP_GET_INPUT_INDEX(control->index));
hda_send_verbs(audioGroup->codec, verb, resp, 2);
}
static status_t
get_mix(hda_audio_group* audioGroup, multi_mix_value_info * MMVI)
{
int32 i;
uint32 id;
hda_multi_mixer_control *control = NULL;
for (i=0; i < MMVI->item_count; i++) {
id = MMVI->values[i].id - MULTI_CONTROL_FIRSTID;
if (id < 0 || id >= audioGroup->multi->control_count) {
dprintf("hda: get_mix : invalid control id requested : %li\n", id);
continue;
}
control = &audioGroup->multi->controls[id];
if (control->mix_control.flags & (B_MULTI_MIX_GAIN | B_MULTI_MIX_ENABLE)) {
uint32 resp[2];
get_control_gain_mute(audioGroup, control, resp);
if (control->mix_control.flags & B_MULTI_MIX_ENABLE) {
MMVI->values[i].enable = (resp[0] & AMP_MUTE) != 0;
TRACE("get_mix: %ld mute: %d\n", control->nid, MMVI->values[i].enable);
} else if (control->mix_control.flags & B_MULTI_MIX_GAIN) {
uint32 value;
if (control->mix_control.master == MULTI_CONTROL_MASTERID)
value = resp[0] & AMP_GAIN_MASK;
else
value = resp[1] & AMP_GAIN_MASK;
MMVI->values[i].gain = (value - AMP_CAP_OFFSET(control->capabilities))
* AMP_CAP_STEP_SIZE(control->capabilities);
TRACE("get_mix: %ld gain: %f (%ld)\n", control->nid, MMVI->values[i].gain, value);
}
}
/*if (control->mix_control.flags & B_MULTI_MIX_MUX && control->get) {
float values[1];
control->get(audioGroup, control, values);
MMVI->values[i].mux = (int32)values[0];
}*/
}
return B_OK;
}
static status_t
set_mix(hda_audio_group* audioGroup, multi_mix_value_info * MMVI)
{
int32 i;
uint32 id;
hda_multi_mixer_control *control = NULL;
for (i=0; i < MMVI->item_count; i++) {
id = MMVI->values[i].id - MULTI_CONTROL_FIRSTID;
if (id < 0 || id >= audioGroup->multi->control_count) {
dprintf("set_mix : invalid control id requested : %li\n", id);
continue;
}
control = &audioGroup->multi->controls[id];
if (control->mix_control.flags & B_MULTI_MIX_ENABLE) {
control->mute = (MMVI->values[i].enable ? AMP_MUTE : 0);
TRACE("set_mix: %ld mute: %lx\n", control->nid, control->mute);
uint32 resp[2];
get_control_gain_mute(audioGroup, control, resp);
uint32 verb[2];
verb[0] = MAKE_VERB(audioGroup->codec->addr,
control->nid,
VID_SET_AMPLIFIER_GAIN_MUTE,
(control->input ? AMP_SET_INPUT : AMP_SET_OUTPUT)
| AMP_SET_LEFT_CHANNEL
| AMP_SET_INPUT_INDEX(control->index)
| control->mute
| resp[0] & AMP_GAIN_MASK);
TRACE("set_mix: sending verb to %ld: %lx %lx %x %lx\n", control->nid,
control->mute, resp[0] & AMP_GAIN_MASK, control->input,
(control->input ? AMP_SET_INPUT : AMP_SET_OUTPUT)
| AMP_SET_LEFT_CHANNEL
| AMP_SET_INPUT_INDEX(control->index)
| control->mute
| resp[0] & AMP_GAIN_MASK);
verb[1] = MAKE_VERB(audioGroup->codec->addr,
control->nid,
VID_SET_AMPLIFIER_GAIN_MUTE,
(control->input ? AMP_SET_INPUT : AMP_SET_OUTPUT)
| AMP_SET_RIGHT_CHANNEL
| AMP_SET_INPUT_INDEX(control->index)
| control->mute
| resp[1] & AMP_GAIN_MASK);
TRACE("set_mix: ctrl2 sending verb to %ld: %lx %lx %x\n", control->nid,
control->mute, resp[1] & AMP_GAIN_MASK, control->input);
hda_send_verbs(audioGroup->codec, verb, NULL, 2);
} else if (control->mix_control.flags & B_MULTI_MIX_GAIN) {
hda_multi_mixer_control *control2 = NULL;
if (i+1<MMVI->item_count) {
id = MMVI->values[i + 1].id - MULTI_CONTROL_FIRSTID;
if (id < 0 || id >= audioGroup->multi->control_count) {
dprintf("set_mix : invalid control id requested : %li\n", id);
} else {
control2 = &audioGroup->multi->controls[id];
if (control2->mix_control.master != control->mix_control.id)
control2 = NULL;
}
}
if (control->mix_control.master == MULTI_CONTROL_MASTERID)
control->gain = (uint32)(MMVI->values[i].gain / AMP_CAP_STEP_SIZE(control->capabilities)
+ AMP_CAP_OFFSET(control->capabilities));
if (control2 && control2->mix_control.master != MULTI_CONTROL_MASTERID)
control2->gain = (uint32)(MMVI->values[i+1].gain / AMP_CAP_STEP_SIZE(control2->capabilities)
+ AMP_CAP_OFFSET(control2->capabilities));
TRACE("set_mix: %ld gain: %lx and %ld gain: %lx\n",
control->nid, control->gain, control2->nid, control2->gain);
uint32 resp[2];
get_control_gain_mute(audioGroup, control, resp);
control->mute = resp[0] & AMP_MUTE;
if (control2)
control2->mute = resp[1] & AMP_MUTE;
uint32 verb[2];
verb[0] = MAKE_VERB(audioGroup->codec->addr,
control->nid,
VID_SET_AMPLIFIER_GAIN_MUTE,
(control->input ? AMP_SET_INPUT : AMP_SET_OUTPUT)
| AMP_SET_LEFT_CHANNEL
| AMP_SET_INPUT_INDEX(control->index)
| (control->mute & AMP_MUTE)
| (control->gain & AMP_GAIN_MASK));
TRACE("set_mix: sending verb to %ld: %lx %lx %x %lx\n", control->nid,
control->mute, control->gain, control->input,
(control->input ? AMP_SET_INPUT : AMP_SET_OUTPUT)
| AMP_SET_LEFT_CHANNEL
| AMP_SET_INPUT_INDEX(control->index)
| (control->mute & AMP_MUTE)
| (control->gain & AMP_GAIN_MASK));
if (control2) {
verb[1] = MAKE_VERB(audioGroup->codec->addr,
control2->nid,
VID_SET_AMPLIFIER_GAIN_MUTE,
(control->input ? AMP_SET_INPUT : AMP_SET_OUTPUT)
| AMP_SET_RIGHT_CHANNEL
| AMP_SET_INPUT_INDEX(control->index)
| (control2->mute & AMP_MUTE)
| (control2->gain & AMP_GAIN_MASK));
TRACE("set_mix: ctrl2 sending verb to %ld: %lx %lx %x\n", control2->nid,
control2->mute, control2->gain, control2->input);
}
hda_send_verbs(audioGroup->codec, verb, NULL, control2 ? 2 : 1);
if (control2)
i++;
}
/*if (control->mix_control.flags & B_MULTI_MIX_MUX && control->set) {
float values[1];
values[0] = (float)MMVI->values[i].mux;
control->set(card, control->channel, control->type, values);
}*/
}
return B_OK;
}
static status_t
get_buffers(hda_audio_group* audioGroup, multi_buffer_list* data)
{
TRACE("playback: %ld buffers, %ld channels, %ld samples\n",
data->request_playback_buffers, data->request_playback_channels,
data->request_playback_buffer_size);
TRACE("record: %ld buffers, %ld channels, %ld samples\n",
data->request_record_buffers, data->request_record_channels,
data->request_record_buffer_size);
/* Determine what buffers we return given the request */
data->return_playback_buffers = data->request_playback_buffers;
data->return_playback_channels = data->request_playback_channels;
data->return_playback_buffer_size = data->request_playback_buffer_size;
data->return_record_buffers = data->request_record_buffers;
data->return_record_channels = data->request_record_channels;
data->return_record_buffer_size = data->request_record_buffer_size;
/* Workaround for Haiku multi_audio API, since it prefers to let the
driver pick values, while the BeOS multi_audio actually gives the
user's defaults. */
if (data->return_playback_buffers > STREAM_MAX_BUFFERS
|| data->return_playback_buffers < STREAM_MIN_BUFFERS)
data->return_playback_buffers = STREAM_MIN_BUFFERS;
if (data->return_record_buffers > STREAM_MAX_BUFFERS
|| data->return_record_buffers < STREAM_MIN_BUFFERS)
data->return_record_buffers = STREAM_MIN_BUFFERS;
if (data->return_playback_buffer_size == 0)
data->return_playback_buffer_size = DEFAULT_FRAMES_PER_BUFFER;
if (data->return_record_buffer_size == 0)
data->return_record_buffer_size = DEFAULT_FRAMES_PER_BUFFER;
/* ... from here on, we can assume again that a reasonable request is
being made */
data->flags = B_MULTI_BUFFER_PLAYBACK | B_MULTI_BUFFER_RECORD;
/* Copy the settings into the streams */
if (audioGroup->playback_stream != NULL) {
audioGroup->playback_stream->num_buffers = data->return_playback_buffers;
audioGroup->playback_stream->num_channels = data->return_playback_channels;
audioGroup->playback_stream->buffer_length
= data->return_playback_buffer_size;
status_t status = hda_stream_setup_buffers(audioGroup,
audioGroup->playback_stream, "Playback");
if (status != B_OK) {
dprintf("hda: Error setting up playback buffers: %s\n",
strerror(status));
return status;
}
}
if (audioGroup->record_stream != NULL) {
audioGroup->record_stream->num_buffers = data->return_record_buffers;
audioGroup->record_stream->num_channels = data->return_record_channels;
audioGroup->record_stream->buffer_length
= data->return_record_buffer_size;
status_t status = hda_stream_setup_buffers(audioGroup,
audioGroup->record_stream, "Recording");
if (status != B_OK) {
dprintf("hda: Error setting up recording buffers: %s\n",
strerror(status));
return status;
}
}
/* Setup data structure for multi_audio API... */
if (audioGroup->playback_stream != NULL) {
uint32 playbackSampleSize = audioGroup->playback_stream->sample_size;
for (int32 i = 0; i < data->return_playback_buffers; i++) {
for (int32 channelIndex = 0;
channelIndex < data->return_playback_channels; channelIndex++) {
data->playback_buffers[i][channelIndex].base
= (char*)audioGroup->playback_stream->buffers[i]
+ playbackSampleSize * channelIndex;
data->playback_buffers[i][channelIndex].stride
= playbackSampleSize * data->return_playback_channels;
}
}
}
if (audioGroup->record_stream != NULL) {
uint32 recordSampleSize = audioGroup->record_stream->sample_size;
for (int32 i = 0; i < data->return_record_buffers; i++) {
for (int32 channelIndex = 0;
channelIndex < data->return_record_channels; channelIndex++) {
data->record_buffers[i][channelIndex].base
= (char*)audioGroup->record_stream->buffers[i]
+ recordSampleSize * channelIndex;
data->record_buffers[i][channelIndex].stride
= recordSampleSize * data->return_record_channels;
}
}
}
return B_OK;
}
/*! playback_buffer_cycle is the buffer we want to have played */
static status_t
buffer_exchange(hda_audio_group* audioGroup, multi_buffer_info* data)
{
static int debug_buffers_exchanged = 0;
cpu_status status;
status_t err;
// TODO: support recording!
if (audioGroup->playback_stream == NULL)
return B_ERROR;
if (!audioGroup->playback_stream->running) {
hda_stream_start(audioGroup->codec->controller,
audioGroup->playback_stream);
}
if (audioGroup->record_stream && !audioGroup->record_stream->running) {
hda_stream_start(audioGroup->codec->controller,
audioGroup->record_stream);
}
/* do playback */
err = acquire_sem_etc(audioGroup->playback_stream->buffer_ready_sem,
1, B_CAN_INTERRUPT, 0);
if (err != B_OK) {
dprintf("%s: Error waiting for playback buffer to finish (%s)!\n", __func__,
strerror(err));
return err;
}
status = disable_interrupts();
acquire_spinlock(&audioGroup->playback_stream->lock);
data->playback_buffer_cycle = audioGroup->playback_stream->buffer_cycle;
data->played_real_time = audioGroup->playback_stream->real_time;
data->played_frames_count = audioGroup->playback_stream->frames_count;
release_spinlock(&audioGroup->playback_stream->lock);
if (audioGroup->record_stream) {
acquire_spinlock(&audioGroup->record_stream->lock);
data->record_buffer_cycle = audioGroup->record_stream->buffer_cycle;
data->recorded_real_time = audioGroup->record_stream->real_time;
data->recorded_frames_count = audioGroup->record_stream->frames_count;
release_spinlock(&audioGroup->record_stream->lock);
}
restore_interrupts(status);
debug_buffers_exchanged++;
if (((debug_buffers_exchanged % 100) == 1) && (debug_buffers_exchanged < 1111)) {
dprintf("%s: %d buffers processed\n", __func__, debug_buffers_exchanged);
}
return B_OK;
}
static status_t
buffer_force_stop(hda_audio_group* audioGroup)
{
if (audioGroup->playback_stream != NULL) {
hda_stream_stop(audioGroup->codec->controller,
audioGroup->playback_stream);
}
if (audioGroup->record_stream != NULL) {
hda_stream_stop(audioGroup->codec->controller,
audioGroup->record_stream);
}
//hda_stream_stop(audioGroup->codec->controller, audioGroup->record_stream);
return B_OK;
}
status_t
multi_audio_control(void* cookie, uint32 op, void* arg, size_t len)
{
hda_codec* codec = (hda_codec*)cookie;
hda_audio_group* audioGroup;
/* FIXME: We should simply pass the audioGroup into here... */
if (!codec || codec->num_audio_groups == 0)
return ENODEV;
audioGroup = codec->audio_groups[0];
// TODO: make userland-safe when built for Haiku!
switch (op) {
case B_MULTI_GET_DESCRIPTION:
{
#ifdef __HAIKU
multi_description description;
multi_channel_info channels[16];
multi_channel_info* originalChannels;
if (user_memcpy(&description, arg, sizeof(multi_description))
!= B_OK)
return B_BAD_ADDRESS;
originalChannels = description.channels;
description.channels = channels;
if (description.request_channel_count > 16)
description.request_channel_count = 16;
status_t status = get_description(audioGroup, &description);
if (status != B_OK)
return status;
description.channels = originalChannels;
return user_memcpy(arg, &description, sizeof(multi_description))
&& user_memcpy(originalChannels, channels,
sizeof(multi_channel_info)
* description.request_channel_count;
#else
return get_description(audioGroup, (multi_description*)arg);
#endif
}
case B_MULTI_GET_ENABLED_CHANNELS:
return get_enabled_channels(audioGroup, (multi_channel_enable*)arg);
case B_MULTI_SET_ENABLED_CHANNELS:
return B_OK;
case B_MULTI_GET_GLOBAL_FORMAT:
return get_global_format(audioGroup, (multi_format_info*)arg);
case B_MULTI_SET_GLOBAL_FORMAT:
return set_global_format(audioGroup, (multi_format_info*)arg);
case B_MULTI_LIST_MIX_CHANNELS:
return list_mix_channels(audioGroup, (multi_mix_channel_info*)arg);
case B_MULTI_LIST_MIX_CONTROLS:
return list_mix_controls(audioGroup, (multi_mix_control_info*)arg);
case B_MULTI_LIST_MIX_CONNECTIONS:
return list_mix_connections(audioGroup,
(multi_mix_connection_info*)arg);
case B_MULTI_GET_MIX:
return get_mix(audioGroup, (multi_mix_value_info *)arg);
case B_MULTI_SET_MIX:
return set_mix(audioGroup, (multi_mix_value_info *)arg);
case B_MULTI_GET_BUFFERS:
return get_buffers(audioGroup, (multi_buffer_list*)arg);
case B_MULTI_BUFFER_EXCHANGE:
return buffer_exchange(audioGroup, (multi_buffer_info*)arg);
case B_MULTI_BUFFER_FORCE_STOP:
return buffer_force_stop(audioGroup);
case B_MULTI_GET_EVENT_INFO:
case B_MULTI_SET_EVENT_INFO:
case B_MULTI_GET_EVENT:
case B_MULTI_GET_CHANNEL_FORMATS:
case B_MULTI_SET_CHANNEL_FORMATS:
case B_MULTI_SET_BUFFERS:
case B_MULTI_SET_START_TIME:
return B_ERROR;
}
return B_BAD_VALUE;
}