blob: 8b0b082dc78ba1367714324ff640c4118380eea5 [file] [log] [blame]
// Copyright 2017 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 "intel_hda_codec.h"
#include <iterator>
#include <memory>
#include <utility>
#include <fbl/algorithm.h>
namespace audio {
namespace intel_hda {
IntelHDACodec::CodecTree IntelHDACodec::codecs_;
extern void print_codec_state(const CodecState& codec);
////////////////////////////////////////////////////////////////////////////////
//
// Parser and CommandList for fetching the currently configured unsolicited
// response state (present in both function groups and widgets)
//
////////////////////////////////////////////////////////////////////////////////
static zx_status_t ParseUnsolicitedResponseState(UnsolicitedResponseState& state,
const CodecResponse& resp) {
// Section 7.3.3.14.
state.raw_data_ = static_cast<uint8_t>(resp.data & 0xFF);
return ZX_OK;
}
static const IntelHDACodec::CommandListEntry<UnsolicitedResponseState>
FETCH_UNSOLICITED_RESPONSE_STATE[] = {
{GET_UNSOLICITED_RESP_CTRL, ParseUnsolicitedResponseState},
};
////////////////////////////////////////////////////////////////////////////////
//
// Parsers and CommandLists for fetching info about supported and current power
// state.
//
////////////////////////////////////////////////////////////////////////////////
static zx_status_t ParseSupportedPowerStates(PowerState& ps, const CodecResponse& resp) {
ps.supported_states_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseCurrentPowerState(PowerState& ps, const CodecResponse& resp) {
// Section 7.3.3.10
ps.set_ = static_cast<uint8_t>(resp.data & 0xF);
ps.active_ = static_cast<uint8_t>((resp.data >> 4) & 0xF);
ps.error_ = (resp.data & (1u << 8)) != 0;
ps.clock_stop_ok_ = (resp.data & (1u << 9)) != 0;
ps.settings_reset_ = (resp.data & (1u << 10)) != 0;
return ZX_OK;
}
static const IntelHDACodec::CommandListEntry<PowerState> FETCH_POWER_STATE[] = {
{GET_PARAM(CodecParam::SUPPORTED_PWR_STATES), ParseSupportedPowerStates},
{GET_POWER_STATE, ParseCurrentPowerState},
};
////////////////////////////////////////////////////////////////////////////////
//
// Parsers and CommandLists for fetching info about audio widgets
//
////////////////////////////////////////////////////////////////////////////////
static zx_status_t ParseAWPcmSizeRate(AudioWidgetState& widget, const CodecResponse& resp) {
auto& afg = *widget.afg_;
const auto& caps = widget.caps_;
widget.pcm_size_rate_ = caps.format_override() ? resp.data : afg.default_pcm_size_rate_;
return ZX_OK;
}
static zx_status_t ParseAWPcmFormats(AudioWidgetState& widget, const CodecResponse& resp) {
auto& afg = *widget.afg_;
const auto& caps = widget.caps_;
widget.pcm_formats_ = caps.format_override() ? resp.data : afg.default_pcm_formats_;
return ZX_OK;
}
static zx_status_t ParseAWInputAmpCaps(AudioWidgetState& widget, const CodecResponse& resp) {
auto& afg = *widget.afg_;
const auto& caps = widget.caps_;
if (caps.input_amp_present()) {
widget.input_amp_caps_ =
caps.amp_param_override() ? AmpCaps(resp.data) : afg.default_input_amp_caps_;
}
return ZX_OK;
}
static zx_status_t ParseAWOutputAmpCaps(AudioWidgetState& widget, const CodecResponse& resp) {
auto& afg = *widget.afg_;
const auto& caps = widget.caps_;
if (caps.output_amp_present()) {
widget.output_amp_caps_ =
caps.amp_param_override() ? AmpCaps(resp.data) : afg.default_output_amp_caps_;
}
return ZX_OK;
}
static zx_status_t ParseAWConnectionListLen(AudioWidgetState& widget, const CodecResponse& resp) {
const auto& caps = widget.caps_;
if (caps.has_conn_list()) {
widget.long_form_conn_list_ = ((resp.data & 0x80) != 0);
widget.conn_list_len_ = resp.data & 0x7f;
if (widget.conn_list_len_) {
fbl::AllocChecker ac;
widget.conn_list_.reset(new (&ac) AudioWidgetState::ConnListEntry[widget.conn_list_len_]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
} else {
widget.long_form_conn_list_ = false;
widget.conn_list_len_ = 0;
}
return ZX_OK;
}
static zx_status_t ParseAWProcessingCaps(AudioWidgetState& widget, const CodecResponse& resp) {
const auto& caps = widget.caps_;
if (caps.proc_widget()) {
widget.can_bypass_processing_ = (resp.data & 0x1) != 0;
widget.processing_coefficient_count_ = ((resp.data >> 8) & 0xFF);
}
return ZX_OK;
}
static zx_status_t ParseAWPinCaps(AudioWidgetState& widget, const CodecResponse& resp) {
widget.pin_caps_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAWVolumeKnobCaps(AudioWidgetState& widget, const CodecResponse& resp) {
widget.vol_knob_is_delta_ = (resp.data & 0x80) != 0;
widget.vol_knob_steps_ = (resp.data & 0x7f);
return ZX_OK;
}
static zx_status_t ParseAWStreamChan(AudioWidgetState& widget, const CodecResponse& resp) {
// Section 7.3.3.11 and Table 85
widget.stream_tag_ = static_cast<uint8_t>((resp.data >> 4) & 0xF);
widget.stream_chan_ = static_cast<uint8_t>(resp.data & 0xF);
return ZX_OK;
}
static zx_status_t ParseAWConfigDefaults(AudioWidgetState& widget, const CodecResponse& resp) {
widget.cfg_defaults_.raw_data_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAWPinWidgetCtrl(AudioWidgetState& widget, const CodecResponse& resp) {
widget.pin_widget_ctrl_.raw_data_ = static_cast<uint8_t>(resp.data & 0xFF);
return ZX_OK;
}
static zx_status_t ParseAudioWidgetType(AudioWidgetStatePtr& ptr, const CodecResponse& resp) {
AudioWidgetCaps caps(resp.data);
switch (caps.type()) {
case AudioWidgetCaps::Type::OUTPUT:
case AudioWidgetCaps::Type::INPUT:
case AudioWidgetCaps::Type::MIXER:
case AudioWidgetCaps::Type::SELECTOR:
case AudioWidgetCaps::Type::PIN_COMPLEX:
case AudioWidgetCaps::Type::POWER:
case AudioWidgetCaps::Type::VOLUME_KNOB:
case AudioWidgetCaps::Type::BEEP_GEN:
case AudioWidgetCaps::Type::VENDOR:
break;
default:
return ZX_ERR_INVALID_ARGS;
}
fbl::AllocChecker ac;
ptr.reset(new (&ac) AudioWidgetState(caps));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
// clang-format off
static const IntelHDACodec::CommandListEntry<AudioWidgetState> FETCH_AUDIO_INPUT_CAPS[] = {
{ GET_PARAM(CodecParam::SUPPORTED_PCM_SIZE_RATE), ParseAWPcmSizeRate },
{ GET_PARAM(CodecParam::SUPPORTED_STREAM_FORMATS), ParseAWPcmFormats },
{ GET_PARAM(CodecParam::INPUT_AMP_CAPS), ParseAWInputAmpCaps },
{ GET_PARAM(CodecParam::CONNECTION_LIST_LEN), ParseAWConnectionListLen },
{ GET_PARAM(CodecParam::PROCESSING_CAPS), ParseAWProcessingCaps },
{ GET_CONVERTER_STREAM_CHAN, ParseAWStreamChan },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetState> FETCH_AUDIO_OUTPUT_CAPS[] = {
{ GET_PARAM(CodecParam::SUPPORTED_PCM_SIZE_RATE), ParseAWPcmSizeRate },
{ GET_PARAM(CodecParam::SUPPORTED_STREAM_FORMATS), ParseAWPcmFormats },
{ GET_PARAM(CodecParam::OUTPUT_AMP_CAPS), ParseAWOutputAmpCaps },
{ GET_PARAM(CodecParam::PROCESSING_CAPS), ParseAWProcessingCaps },
{ GET_CONVERTER_STREAM_CHAN, ParseAWStreamChan },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetState> FETCH_DIGITAL_PIN_COMPLEX_CAPS[] = {
{ GET_PARAM(CodecParam::PIN_CAPS), ParseAWPinCaps },
{ GET_PARAM(CodecParam::OUTPUT_AMP_CAPS), ParseAWOutputAmpCaps },
{ GET_PARAM(CodecParam::CONNECTION_LIST_LEN), ParseAWConnectionListLen },
{ GET_PARAM(CodecParam::PROCESSING_CAPS), ParseAWProcessingCaps },
{ GET_CONFIG_DEFAULT, ParseAWConfigDefaults },
{ GET_PIN_WIDGET_CTRL, ParseAWPinWidgetCtrl },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetState>
FETCH_NON_DIGITAL_PIN_COMPLEX_CAPS[] = {
{ GET_PARAM(CodecParam::PIN_CAPS), ParseAWPinCaps },
{ GET_PARAM(CodecParam::INPUT_AMP_CAPS), ParseAWInputAmpCaps },
{ GET_PARAM(CodecParam::OUTPUT_AMP_CAPS), ParseAWOutputAmpCaps },
{ GET_PARAM(CodecParam::CONNECTION_LIST_LEN), ParseAWConnectionListLen },
{ GET_PARAM(CodecParam::PROCESSING_CAPS), ParseAWProcessingCaps },
{ GET_CONFIG_DEFAULT, ParseAWConfigDefaults },
{ GET_PIN_WIDGET_CTRL, ParseAWPinWidgetCtrl },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetState> FETCH_MIXER_CAPS[] = {
{ GET_PARAM(CodecParam::INPUT_AMP_CAPS), ParseAWInputAmpCaps },
{ GET_PARAM(CodecParam::OUTPUT_AMP_CAPS), ParseAWOutputAmpCaps },
{ GET_PARAM(CodecParam::CONNECTION_LIST_LEN), ParseAWConnectionListLen },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetState> FETCH_SELECTOR_CAPS[] = {
{ GET_PARAM(CodecParam::INPUT_AMP_CAPS), ParseAWInputAmpCaps },
{ GET_PARAM(CodecParam::OUTPUT_AMP_CAPS), ParseAWOutputAmpCaps },
{ GET_PARAM(CodecParam::CONNECTION_LIST_LEN), ParseAWConnectionListLen },
{ GET_PARAM(CodecParam::PROCESSING_CAPS), ParseAWProcessingCaps },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetState> FETCH_POWER_CAPS[] = {
{ GET_PARAM(CodecParam::CONNECTION_LIST_LEN), ParseAWConnectionListLen },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetState> FETCH_VOLUME_KNOB_CAPS[] = {
{ GET_PARAM(CodecParam::CONNECTION_LIST_LEN), ParseAWConnectionListLen },
{ GET_PARAM(CodecParam::VOLUME_KNOB_CAPS), ParseAWVolumeKnobCaps },
};
static const IntelHDACodec::CommandListEntry<AudioWidgetStatePtr> FETCH_WIDGET_TYPE[] = {
{ GET_PARAM(CodecParam::AW_CAPS), ParseAudioWidgetType },
};
// clang-format on
////////////////////////////////////////////////////////////////////////////////
//
// Parsers and CommandLists for fetching info about function groups.
//
////////////////////////////////////////////////////////////////////////////////
static zx_status_t ParseAFGCaps(AudioFunctionGroupState& afg, const CodecResponse& resp) {
afg.caps_.raw_data_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAFGPcmSizeRate(AudioFunctionGroupState& afg, const CodecResponse& resp) {
// Section 7.3.4.7 : Supported PCM sizes and rates
afg.default_pcm_size_rate_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAFGPcmFormats(AudioFunctionGroupState& afg, const CodecResponse& resp) {
afg.default_pcm_formats_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAFGInputAmpCaps(AudioFunctionGroupState& afg, const CodecResponse& resp) {
afg.default_input_amp_caps_.raw_data_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAFGOutputAmpCaps(AudioFunctionGroupState& afg, const CodecResponse& resp) {
afg.default_output_amp_caps_.raw_data_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAFGGPIOCount(AudioFunctionGroupState& afg, const CodecResponse& resp) {
// Section 7.3.4.14 : GPIO Counts
afg.gpio_can_wake_ = (resp.data & 0x80000000) != 0;
afg.gpio_can_send_unsolicited_ = (resp.data & 0x40000000) != 0;
afg.gpi_count_ = (resp.data >> 16) & 0xFF;
afg.gpo_count_ = (resp.data >> 8) & 0xFF;
afg.gpio_count_ = (resp.data >> 0) & 0xFF;
return ZX_OK;
}
static zx_status_t ParseAFGImplId(AudioFunctionGroupState& afg, const CodecResponse& resp) {
afg.impl_id_.raw_data_ = resp.data;
return ZX_OK;
}
static zx_status_t ParseAFGWidgetCount(AudioFunctionGroupState& afg, const CodecResponse& resp) {
/* Response format documented in section 7.3.4.1 */
afg.widget_count_ = resp.data & 0xFF;
afg.widget_starting_id_ = (resp.data >> 16) & 0xFF;
uint32_t last_widget_nid = static_cast<uint32_t>(afg.widget_starting_id_) + afg.widget_count_ - 1;
if (last_widget_nid > HDA_MAX_NID)
return ZX_ERR_INTERNAL;
if (afg.widget_count_) {
fbl::AllocChecker ac;
afg.widgets_.reset(new (&ac) AudioWidgetStatePtr[afg.widget_count_]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
}
return ZX_OK;
}
static const IntelHDACodec::CommandListEntry<AudioFunctionGroupState> FETCH_AFG_PROPERTIES[]{
// clang-format off
{ GET_PARAM(CodecParam::AFG_CAPS), ParseAFGCaps },
{ GET_PARAM(CodecParam::SUPPORTED_PCM_SIZE_RATE), ParseAFGPcmSizeRate },
{ GET_PARAM(CodecParam::SUPPORTED_STREAM_FORMATS), ParseAFGPcmFormats },
{ GET_PARAM(CodecParam::INPUT_AMP_CAPS), ParseAFGInputAmpCaps },
{ GET_PARAM(CodecParam::OUTPUT_AMP_CAPS), ParseAFGOutputAmpCaps },
{ GET_PARAM(CodecParam::GPIO_COUNT), ParseAFGGPIOCount },
{ GET_IMPLEMENTATION_ID, ParseAFGImplId },
{ GET_PARAM(CodecParam::SUBORDINATE_NODE_COUNT), ParseAFGWidgetCount },
// clang-format on
};
static zx_status_t ParseFnGroupType(FunctionGroupStatePtr& ptr, const CodecResponse& resp) {
/* Response format documented in section 7.3.4.1 */
auto type = static_cast<FunctionGroupState::Type>(resp.data & 0xFF);
fbl::AllocChecker ac;
switch (type) {
case FunctionGroupState::Type::AUDIO:
ptr.reset(new (&ac) AudioFunctionGroupState());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
break;
case FunctionGroupState::Type::MODEM:
ptr.reset(new (&ac) ModemFunctionGroupState());
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
break;
default:
if ((type >= FunctionGroupState::Type::VENDOR_START) &&
(type <= FunctionGroupState::Type::VENDOR_END)) {
ptr.reset(new (&ac) VendorFunctionGroupState(type));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
} else {
return ZX_ERR_INTERNAL;
}
break;
}
ptr->can_send_unsolicited_ = ((resp.data & 0x100) != 0);
return ZX_OK;
}
static const IntelHDACodec::CommandListEntry<FunctionGroupStatePtr> FETCH_FUNCTION_GROUP_TYPE[] = {
{GET_PARAM(CodecParam::FUNCTION_GROUP_TYPE), ParseFnGroupType},
};
////////////////////////////////////////////////////////////////////////////////
//
// Parsers and command list for fetching info about core codec capabilities.
//
////////////////////////////////////////////////////////////////////////////////
static zx_status_t ParseVendorID(CodecState& codec, const CodecResponse& resp) {
/* Response format documented in section 7.3.4.1 */
codec.vendor_id_ = static_cast<uint16_t>((resp.data >> 16) & 0xFFFF);
codec.device_id_ = static_cast<uint16_t>(resp.data & 0xFFFF);
return (codec.vendor_id_ != 0) ? ZX_OK : ZX_ERR_INTERNAL;
}
static zx_status_t ParseRevisionID(CodecState& codec, const CodecResponse& resp) {
/* Response format documented in section 7.3.4.2 */
codec.major_rev_ = (resp.data >> 20) & 0xF;
codec.minor_rev_ = (resp.data >> 16) & 0xF;
codec.vendor_rev_id_ = (resp.data >> 8) & 0xFF;
codec.vendor_stepping_id_ = resp.data & 0xFF;
return ZX_OK;
}
static zx_status_t ParseFnGroupCount(CodecState& codec, const CodecResponse& resp) {
/* Response format documented in section 7.3.4.3 */
codec.fn_group_count_ = resp.data & 0xFF;
codec.fn_group_starting_id_ = (resp.data >> 16) & 0xFF;
uint32_t last_fn_group_nid =
static_cast<uint32_t>(codec.fn_group_starting_id_) + codec.fn_group_count_ - 1;
if (last_fn_group_nid > HDA_MAX_NID)
return ZX_ERR_INTERNAL;
// Allocate the storage for the function group state pointers, then
// start the process of enumerating their properties and widgets.
fbl::AllocChecker ac;
codec.fn_groups_.reset(new (&ac) FunctionGroupStatePtr[codec.fn_group_count_]);
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
static const IntelHDACodec::CommandListEntry<CodecState> FETCH_CODEC_ROOT_COMMANDS[] = {
// clang-format off
{ GET_PARAM(CodecParam::VENDOR_ID), ParseVendorID },
{ GET_PARAM(CodecParam::REVISION_ID), ParseRevisionID },
{ GET_PARAM(CodecParam::SUBORDINATE_NODE_COUNT), ParseFnGroupCount },
// clang-format on
};
zx_status_t IntelHDACodec::Enumerate() {
static const char* const DEV_PATH = "/dev/class/intel-hda-codec";
zx_status_t res = ZirconDevice::Enumerate(
nullptr, DEV_PATH, [](void*, uint32_t id, const char* const dev_name) -> zx_status_t {
fbl::AllocChecker ac;
auto codec = std::unique_ptr<IntelHDACodec>(new (&ac) IntelHDACodec(id, dev_name));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
if (codec == nullptr)
return ZX_ERR_NO_MEMORY;
if (!codecs_.insert_or_find(std::move(codec)))
return ZX_ERR_INTERNAL;
return ZX_OK;
});
return res;
}
zx_status_t IntelHDACodec::DumpCodec(int argc, const char** argv) {
zx_status_t res = ReadCodecState();
if (res != ZX_OK)
return res;
printf("Codec ID %u :: %s\n", codec_id_, dev_name_.c_str());
print_codec_state(codec_state_);
return ZX_OK;
}
zx_status_t IntelHDACodec::Probe(IntelHDADevice* result) {
return ProbeIntelHdaDevice(&device_, result);
}
#define RUN_COMMAND_LIST(_tgt, _nid, _list, _fail_msg, ...) \
{ \
res = RunCommandList(_tgt, _nid, _list, std::size(_list)); \
if (res != ZX_OK) { \
printf(_fail_msg " (res %d)\n", ##__VA_ARGS__, res); \
return res; \
} \
}
zx_status_t IntelHDACodec::ReadCodecState() {
zx_status_t res = device_.Connect();
if (res != ZX_OK) {
return res;
}
codec_state_.reset();
RUN_COMMAND_LIST(codec_state_, 0u, FETCH_CODEC_ROOT_COMMANDS,
"Failed while fetching codec root info");
for (uint16_t group_ndx = 0; group_ndx < codec_state_.fn_group_count_; ++group_ndx) {
auto& fn_group_ptr = codec_state_.fn_groups_[group_ndx];
auto nid = static_cast<uint16_t>(group_ndx + codec_state_.fn_group_starting_id_);
res = ReadFunctionGroupState(fn_group_ptr, nid);
if (res != ZX_OK)
return res;
}
return ZX_OK;
}
zx_status_t IntelHDACodec::ReadFunctionGroupState(FunctionGroupStatePtr& ptr, uint16_t nid) {
zx_status_t res;
RUN_COMMAND_LIST(ptr, nid, FETCH_FUNCTION_GROUP_TYPE,
"Failed to fetch function group type (nid %hu)", nid);
if (ptr->can_send_unsolicited_) {
RUN_COMMAND_LIST(ptr->unsol_resp_ctrl_, nid, FETCH_UNSOLICITED_RESPONSE_STATE,
"Failed to fetch unsolicited response control state (nid %hu)", nid);
}
ptr->nid_ = nid;
switch (ptr->type_) {
case FunctionGroupState::Type::AUDIO: {
auto& afg = *(static_cast<AudioFunctionGroupState*>(ptr.get()));
return ReadAudioFunctionGroupState(afg);
}
case FunctionGroupState::Type::MODEM: {
// We do not support probing the state of modem function groups right now.
printf("Warning: MODEM function group (nid %hd) state details not fetched.\n", nid);
break;
}
default:
// ParseFnGroupType should have aborted at this point if the function
// group type was not valid.
ZX_DEBUG_ASSERT((ptr->type_ >= FunctionGroupState::Type::VENDOR_START) &&
(ptr->type_ <= FunctionGroupState::Type::VENDOR_END));
break;
}
return ZX_OK;
}
zx_status_t IntelHDACodec::ReadAudioFunctionGroupState(AudioFunctionGroupState& afg) {
zx_status_t res;
RUN_COMMAND_LIST(afg, afg.nid_, FETCH_AFG_PROPERTIES,
"Failed to audio fn group properties (nid %hu)", afg.nid_);
RUN_COMMAND_LIST(afg.power_, afg.nid_, FETCH_POWER_STATE,
"Failed to fetch Power caps/state for audio function group (nid %hu)", afg.nid_);
for (uint32_t i = 0; i < afg.widget_count_; ++i) {
auto& widget_ptr = afg.widgets_[i];
uint16_t nid = static_cast<uint16_t>(afg.widget_starting_id_ + i);
RUN_COMMAND_LIST(widget_ptr, nid, FETCH_WIDGET_TYPE,
"Failed to audio widget type (nid %hu) for function "
"group located at nid %hu",
nid, afg.nid_);
widget_ptr->nid_ = nid;
widget_ptr->afg_ = &afg;
res = ReadAudioWidgetState(*widget_ptr);
if (res != ZX_OK)
return res;
}
return ZX_OK;
}
zx_status_t IntelHDACodec::ReadAudioWidgetState(AudioWidgetState& widget) {
zx_status_t res;
switch (widget.caps_.type()) {
case AudioWidgetCaps::Type::INPUT:
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_AUDIO_INPUT_CAPS,
"Failed to fetch INPUT_CAPS for audio widget (nid %hu)", widget.nid_);
break;
case AudioWidgetCaps::Type::OUTPUT:
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_AUDIO_OUTPUT_CAPS,
"Failed to fetch OUTPUT_CAPS for audio widget (nid %hu)", widget.nid_);
break;
case AudioWidgetCaps::Type::PIN_COMPLEX:
if (widget.caps_.digital()) {
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_DIGITAL_PIN_COMPLEX_CAPS,
"Failed to fetch DIGITAL_PIN_COMPLEX_CAPS for audio widget "
"(nid %hu)",
widget.nid_);
} else {
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_NON_DIGITAL_PIN_COMPLEX_CAPS,
"Failed to fetch NON_DIGITAL_PIN_COMPLEX_CAPS for audio widget "
"(nid %hu)",
widget.nid_);
}
break;
case AudioWidgetCaps::Type::MIXER:
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_MIXER_CAPS,
"Failed to fetch MIXER_CAPS for audio widget (nid %hu)", widget.nid_);
break;
case AudioWidgetCaps::Type::SELECTOR:
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_SELECTOR_CAPS,
"Failed to fetch SELECTOR_CAPS for audio widget (nid %hu)", widget.nid_);
break;
case AudioWidgetCaps::Type::POWER:
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_POWER_CAPS,
"Failed to fetch POWER_CAPS for audio widget (nid %hu)", widget.nid_);
break;
case AudioWidgetCaps::Type::VOLUME_KNOB:
RUN_COMMAND_LIST(widget, widget.nid_, FETCH_VOLUME_KNOB_CAPS,
"Failed to fetch VOLUME_KNOB_CAPS for audio widget (nid %hu)", widget.nid_);
break;
// We don't currently fetch any state for beep generators or vendor widgets.
case AudioWidgetCaps::Type::BEEP_GEN:
case AudioWidgetCaps::Type::VENDOR:
break;
default:
printf("Unrecognized audio widget type (%u) at nid %hu\n",
static_cast<uint32_t>(widget.caps_.type()), widget.nid_);
return ZX_ERR_BAD_STATE;
}
// If this widget has a connection list, read it now.
if (widget.caps_.has_conn_list()) {
res = ReadConnList(widget);
if (res != ZX_OK)
return res;
}
// If this widget has power management capabilities, read the caps and the
// current state now.
if (widget.caps_.has_power_ctl()) {
RUN_COMMAND_LIST(widget.power_, widget.nid_, FETCH_POWER_STATE,
"Failed to fetch Power caps/state for audio widget (nid %hu)", widget.nid_);
// From section 7.3.4.12.
//
// "If this is not implemented (returns 0's) or just returns 0 as
// response to reading this parameter for a node that supports a Power
// State Control (see section 7.3.3.10) then the supported power states
// for that node will be the same as reported for the Function Group."
if (widget.power_.supported_states_ == 0) {
ZX_DEBUG_ASSERT(widget.afg_ != nullptr);
widget.power_.supported_states_ = widget.afg_->power_.supported_states_;
}
}
// If this is an input or output converter widget, read the currently configured format.
if ((widget.caps_.type() == AudioWidgetCaps::Type::INPUT) ||
(widget.caps_.type() == AudioWidgetCaps::Type::OUTPUT)) {
CodecResponse resp;
res = DoCodecCmd(widget.nid_, GET_CONVERTER_FORMAT, &resp);
if (res != ZX_OK) {
printf("Failed to get stream converter format for for nid %hu (res %d)\n", widget.nid_, res);
return res;
}
widget.cur_format_.raw_data_ = static_cast<uint16_t>(resp.data & 0xFFFF);
}
// If this is a pin complex, and it supports presence detection, and the
// JackOverride bit has not been set in the config defaults, query the pin
// sense.
if ((widget.caps_.type() == AudioWidgetCaps::Type::PIN_COMPLEX) &&
(widget.pin_caps_ & AW_PIN_CAPS_FLAG_CAN_PRESENCE_DETECT) &&
(!widget.cfg_defaults_.jack_detect_override())) {
// TODO(johngro): Add support for SW triggering a pin detection. Timing
// requirements are unclear and may be codec specific. Also, triggering
// the presence detection is a "set" operation, which is not currently
// permitted by the driver.
if (widget.pin_caps_ & AW_PIN_CAPS_FLAG_TRIGGER_REQUIRED) {
printf("WARNING: SW triggered presence sensing not supported (nid %hu)\n", widget.nid_);
} else {
// TODO(johngro): do we need to bring the pin complex to a
// particular power state in order for presence detect to work, or
// should it run at all power states?
CodecResponse resp;
res = DoCodecCmd(widget.nid_, GET_PIN_SENSE, &resp);
if (res != ZX_OK) {
printf("Failed to get pin sense status for pin complex nid %hu (res %d)\n", widget.nid_,
res);
return res;
}
widget.pin_sense_.raw_data_ = resp.data;
widget.pin_sense_valid_ = true;
}
}
// Read the current state of the EAPD/BTL register if this is...
//
// 1) A pin complex with external amplifier control.
// 2) A pin complex capable of balanced output.
// 3) Any widget capable of swapping L/R channels
if (widget.caps_.can_lr_swap() || (widget.pin_caps_ & AW_PIN_CAPS_FLAG_BALANCED_IO) ||
(widget.pin_caps_ & AW_PIN_CAPS_FLAG_CAN_EAPD)) {
CodecResponse resp;
res = DoCodecCmd(widget.nid_, GET_EAPD_BTL_ENABLE, &resp);
if (res != ZX_OK) {
printf("Failed to get EAPD/BTL state for nid %hu (res %d)\n", widget.nid_, res);
return res;
}
widget.eapd_state_.raw_data_ = resp.data;
}
// If this widget has an input or output amplifier, read its current state.
//
// Todo(johngro) : add support for reading gain settings for mixers and
// summing widgets which have more than just a single amplifier gain/mute
// setting.
if (widget.caps_.input_amp_present()) {
// If this a mixer, read the individual input amp state for each of the mixer inputs.
// Otherwise, just read the common input amp state.
if (widget.caps_.type() == AudioWidgetCaps::Type::MIXER) {
for (uint8_t i = 0; i < widget.conn_list_len_; ++i) {
res = ReadAmpState(widget.nid_, true, i, widget.input_amp_caps_,
&widget.conn_list_[i].amp_state_);
if (res != ZX_OK)
return res;
}
} else {
res = ReadAmpState(widget.nid_, true, 0, widget.input_amp_caps_, &widget.input_amp_state_);
if (res != ZX_OK)
return res;
}
}
if (widget.caps_.output_amp_present()) {
res = ReadAmpState(widget.nid_, false, 0, widget.output_amp_caps_, &widget.output_amp_state_);
if (res != ZX_OK)
return res;
}
// If this widget can send unsolicited responses, query the current state of
// the unsolicted response controls.
if (widget.caps_.can_send_unsol()) {
RUN_COMMAND_LIST(widget.unsol_resp_ctrl_, widget.nid_, FETCH_UNSOLICITED_RESPONSE_STATE,
"Failed to fetch unsolicited response control state (nid %hu)", widget.nid_);
}
// Finished.
return ZX_OK;
}
#undef RUN_COMMAND_LIST
zx_status_t IntelHDACodec::ReadConnList(AudioWidgetState& widget) {
CodecResponse resp;
zx_status_t res;
ZX_DEBUG_ASSERT(widget.conn_list_len_ > 0);
ZX_DEBUG_ASSERT(widget.conn_list_ != nullptr);
size_t i = 0;
while (i < widget.conn_list_len_) {
res = DoCodecCmd(widget.nid_, GET_CONNECTION_LIST_ENTRY(static_cast<uint8_t>(i)), &resp);
if (res != ZX_OK) {
printf("Failed to get connection list entry at ndx %zu for nid %hu (res %d)\n", i,
widget.nid_, res);
return res;
}
// See section 7.1.2 and figure 51 for the format of long and short form
// connection widget.conn_list_ entries.
if (widget.long_form_conn_list_) {
for (size_t j = 0; (j < 2) && (i < widget.conn_list_len_); j++, i++) {
uint16_t raw = static_cast<uint16_t>(resp.data & 0xFFFF);
widget.conn_list_[i].range_ = (raw & 0x8000u) != 0;
widget.conn_list_[i].nid_ = (raw & 0x7FFFu);
resp.data >>= 16;
}
} else {
for (size_t j = 0; (j < 4) && (i < widget.conn_list_len_); j++, i++) {
uint8_t raw = static_cast<uint8_t>(resp.data & 0xFF);
widget.conn_list_[i].range_ = (raw & 0x80u) != 0;
widget.conn_list_[i].nid_ = (raw & 0x7Fu);
resp.data >>= 8;
}
}
}
// Sanity check the widget.conn_list_.
for (i = 0; i < widget.conn_list_len_; ++i) {
if (widget.conn_list_[i].range_ && (!i || widget.conn_list_[i - 1].range_)) {
printf(
"Invalid connection widget.conn_list_ entry [nid, ndx] = [%hu, %zu]. "
"Range end may not be the first entry in the connection widget.conn_list_, "
"or proceeded by a range end entry.\n",
widget.nid_, i);
return ZX_ERR_BAD_STATE;
}
}
// If the connection list length is greater than 1, and this is not a mixer
// widger, then there exists a selection control. Read it's current setting
// so we can report it. Otherwise, the currently connected NID must be the
// same as the first entry in the list, or this is a mixer widget in which
// case it is always connected to all of the entries in the connection list.
if (widget.caps_.type() != AudioWidgetCaps::Type::MIXER) {
if (widget.conn_list_len_ == 1) {
widget.connected_nid_ = widget.conn_list_[0].nid_;
widget.connected_nid_ndx_ = 0;
} else {
// Select control response format documented in section 7.3.3.2 Table 73
res = DoCodecCmd(widget.nid_, GET_CONNECTION_SELECT_CONTROL, &resp);
if (res != ZX_OK) {
printf("Failed to get connection selection for nid %hu (res %d)\n", widget.nid_, res);
return res;
}
widget.connected_nid_ndx_ = static_cast<uint8_t>(resp.data & 0xFF);
widget.connected_nid_ = (widget.connected_nid_ndx_ < widget.conn_list_len_)
? widget.conn_list_[widget.connected_nid_ndx_].nid_
: 0;
}
} else {
widget.connected_nid_ = 0;
widget.connected_nid_ndx_ = 0;
}
return ZX_OK;
}
zx_status_t IntelHDACodec::ReadAmpState(uint16_t nid, bool is_input, uint8_t ndx,
const AmpCaps& caps,
AudioWidgetState::AmpState* state_out) {
ZX_DEBUG_ASSERT(state_out);
CodecResponse resp;
zx_status_t res;
for (size_t i = 0; i < std::size(state_out->gain); ++i) {
res = DoCodecCmd(nid, GET_AMPLIFIER_GAIN_MUTE(is_input, (i > 0), ndx), &resp);
if (res != ZX_OK) {
printf("Failed to get amp settings for nid %hu's %s %s amplifier #%u (res %d)\n", nid,
(i > 0) ? "right" : "left", is_input ? "input" : "output", ndx, res);
return res;
}
// Section 7.3.3.7 and Figure 62
state_out->gain[i] = static_cast<uint8_t>(resp.data & 0x7f);
state_out->mute[i] = (resp.data & 0x80) != 0;
}
return ZX_OK;
}
zx_status_t IntelHDACodec::DoCodecCmd(uint16_t nid, const CodecVerb& verb,
CodecResponse* resp_out) {
ZX_DEBUG_ASSERT(resp_out != nullptr);
ihda_codec_send_corb_cmd_req_t req;
ihda_codec_send_corb_cmd_resp_t resp;
device_.InitRequest(&req, IHDA_CODEC_SEND_CORB_CMD);
req.nid = nid;
req.verb = verb.val;
zx_status_t res = device_.CallDevice(req, &resp);
if (res != ZX_OK) {
printf("Codec command failed; [nid, verb] = [%2u, 0x%05x] (res %d)\n", nid, verb.val, res);
return res;
}
resp_out->data = resp.data;
resp_out->data_ex = resp.data_ex;
return ZX_OK;
}
template <typename T>
zx_status_t IntelHDACodec::RunCommandList(T& target, uint16_t nid, const CommandListEntry<T>* cmds,
size_t cmd_count) {
ZX_DEBUG_ASSERT(cmds);
for (size_t i = 0; i < cmd_count; ++i) {
const auto& cmd = cmds[i];
zx_status_t res;
CodecResponse resp;
res = DoCodecCmd(nid, cmd.verb, &resp);
if (res != ZX_OK)
return res;
res = cmd.parser(target, resp);
if (res != ZX_OK) {
printf("Cmd parse; [nid, verb] = [%2u, 0x%05x] --> resp [0x%08x, 0x%08x] (res %d)\n", nid,
cmd.verb.val, resp.data, resp.data_ex, res);
return res;
}
}
return ZX_OK;
}
} // namespace intel_hda
} // namespace audio