mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 12:54:58 +00:00

This is an implementation that tries to follow the spec as closely as possible, and works with Qemu's Intel HDA and some bare metal HDA controllers out there. Compiling with `INTEL_HDA_DEBUG=on` will provide a lot of detailed information that could help us getting this to work on more bare metal controllers as well :^) Output format is limited to `i16` samples for now.
721 lines
28 KiB
C++
721 lines
28 KiB
C++
/*
|
|
* Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "Codec.h"
|
|
#include <AK/Array.h>
|
|
#include <Kernel/Devices/Audio/IntelHDA/Controller.h>
|
|
|
|
namespace Kernel::Audio::IntelHDA {
|
|
|
|
// 7.3.4.7: Supported PCM Size, Rates
|
|
struct BitRateEncoding {
|
|
u8 flag;
|
|
u8 bit_rate;
|
|
};
|
|
static constexpr Array<BitRateEncoding, 5> bit_rate_encodings { {
|
|
// clang-format off
|
|
{ 0x1, 8 },
|
|
{ 0x2, 16 },
|
|
{ 0x4, 20 },
|
|
{ 0x8, 24 },
|
|
{ 0x10, 32 },
|
|
// clang-format on
|
|
} };
|
|
|
|
struct SampleRateEncoding {
|
|
u16 flag;
|
|
u32 sample_rate;
|
|
};
|
|
static constexpr Array<SampleRateEncoding, 12> sample_rate_encodings { {
|
|
// clang-format off
|
|
{ 0x1, 8'000 },
|
|
{ 0x2, 11'025 },
|
|
{ 0x4, 16'000 },
|
|
{ 0x8, 22'050 },
|
|
{ 0x10, 32'000 },
|
|
{ 0x20, 44'100 },
|
|
{ 0x40, 48'000 },
|
|
{ 0x80, 88'200 },
|
|
{ 0x100, 96'000 },
|
|
{ 0x200, 176'400 },
|
|
{ 0x400, 192'000 },
|
|
{ 0x800, 384'000 },
|
|
// clang-format on
|
|
} };
|
|
|
|
ErrorOr<void> Codec::register_node(NonnullRefPtr<Node> node)
|
|
{
|
|
auto set_result = TRY(m_nodes_by_node_id.try_set(node->node_id(), node));
|
|
VERIFY(set_result == HashSetResult::InsertedNewEntry);
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> Node::initialize()
|
|
{
|
|
return codec().register_node(*this);
|
|
}
|
|
|
|
ErrorOr<u32> Node::command(CodecControlVerb verb, u16 payload)
|
|
{
|
|
auto& node_codec = codec();
|
|
return node_codec.controller().send_command(node_codec.codec_address(), m_node_id, verb, payload);
|
|
}
|
|
|
|
ErrorOr<u32> Node::parameter(GetParameterId get_parameter_id)
|
|
{
|
|
return command(CodecControlVerb::GetParameter, to_underlying(get_parameter_id));
|
|
}
|
|
|
|
ErrorOr<void> Node::set_power_state(PowerState power_state)
|
|
{
|
|
// 7.3.3.10: Power State
|
|
TRY(command(CodecControlVerb::SetPowerState, to_underlying(power_state)));
|
|
return {};
|
|
}
|
|
|
|
template<typename T>
|
|
void NodeWithChildren<T>::for_each_child_node(Function<void(T const&, bool)> callback) const
|
|
{
|
|
auto number_of_child_nodes = m_child_nodes.size();
|
|
for (size_t child_index = 0; child_index < number_of_child_nodes; ++child_index)
|
|
callback(m_child_nodes[child_index], child_index == number_of_child_nodes - 1);
|
|
}
|
|
|
|
template<typename T>
|
|
ErrorOr<void> NodeWithChildren<T>::initialize()
|
|
{
|
|
TRY(Node::initialize());
|
|
return populate_child_nodes();
|
|
}
|
|
|
|
template<typename T>
|
|
ErrorOr<void> NodeWithChildren<T>::populate_child_nodes()
|
|
{
|
|
VERIFY(m_child_nodes.is_empty());
|
|
|
|
// 7.3.4.3: Subordinate Node Count
|
|
auto subordinate_node_count = TRY(parameter(GetParameterId::SubordinateNodeCount));
|
|
u8 starting_node_number = (subordinate_node_count >> 16) & 0xff;
|
|
u8 total_number_of_nodes = subordinate_node_count & 0xff;
|
|
TRY(m_child_nodes.try_ensure_capacity(total_number_of_nodes));
|
|
for (int subnode_index = 0; subnode_index < total_number_of_nodes; ++subnode_index)
|
|
m_child_nodes.unchecked_append(TRY(Node::create<T>(*this, starting_node_number + subnode_index)));
|
|
return {};
|
|
}
|
|
|
|
StringView WidgetNode::widget_type_name() const
|
|
{
|
|
switch (m_widget_type) {
|
|
case WidgetType::AudioInput:
|
|
return "Audio Input"sv;
|
|
case WidgetType::AudioMixer:
|
|
return "Audio Mixer"sv;
|
|
case WidgetType::AudioOutput:
|
|
return "Audio Output"sv;
|
|
case WidgetType::AudioSelector:
|
|
return "Audio Selector"sv;
|
|
case WidgetType::BeepGenerator:
|
|
return "Beep Generator"sv;
|
|
case WidgetType::PinComplex:
|
|
return "Pin Complex"sv;
|
|
case WidgetType::Power:
|
|
return "Power"sv;
|
|
case WidgetType::VendorDefined:
|
|
return "Vendor Defined"sv;
|
|
case WidgetType::VolumeKnob:
|
|
return "Volume Knob"sv;
|
|
}
|
|
return "Reserved"sv;
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::initialize()
|
|
{
|
|
TRY(Node::initialize());
|
|
|
|
// 7.3.4.6: Audio Widget Capabilities
|
|
auto widget_capabilities = TRY(parameter(GetParameterId::AudioWidgetCapabilities));
|
|
m_widget_type = static_cast<WidgetType>((widget_capabilities >> 20) & 0xf);
|
|
m_channel_count = (((widget_capabilities >> 15) & 0xe) | (widget_capabilities & 0x1)) + 1;
|
|
m_power_control_supported = (widget_capabilities & WidgetCapabilityFlag::PowerControlSupported) > 0;
|
|
m_connection_list_present = (widget_capabilities & WidgetCapabilityFlag::ConnectionListPresent) > 0;
|
|
m_format_override = (widget_capabilities & WidgetCapabilityFlag::FormatOverride) > 0;
|
|
m_amp_param_override = (widget_capabilities & WidgetCapabilityFlag::AmpParamOverride) > 0;
|
|
m_output_amp_present = (widget_capabilities & WidgetCapabilityFlag::OutputAmpPresent) > 0;
|
|
m_input_amp_present = (widget_capabilities & WidgetCapabilityFlag::InputAmpPresent) > 0;
|
|
|
|
if (supports_stream()) {
|
|
// 7.3.3.11: Converter Stream, Channel
|
|
auto stream_channel = TRY(command(CodecControlVerb::GetConverterStreamChannel, 0));
|
|
m_selected_stream = (stream_channel >> 4) & 0xf;
|
|
m_selected_channel = stream_channel & 0xf;
|
|
|
|
TRY(populate_supported_pcm_size_rates());
|
|
TRY(populate_supported_stream_formats());
|
|
}
|
|
|
|
// 7.3.4.10: Amplifier Capabilities
|
|
auto read_amp_capabilities = [](Node& node, GetParameterId type) -> ErrorOr<AmplifierCapabilities> {
|
|
auto capabilities = TRY(node.parameter(type));
|
|
return AmplifierCapabilities {
|
|
.muting_supported = ((capabilities >> 31) & 0x1) > 0,
|
|
.step_size = static_cast<u8>((capabilities >> 16) & 0x7f),
|
|
.number_of_steps = static_cast<u8>(((capabilities >> 8) & 0x7f) + 1),
|
|
.offset = static_cast<u8>(capabilities & 0x7f),
|
|
};
|
|
};
|
|
Node& amp_params_node = amp_param_override() ? *this : *parent_node();
|
|
if (output_amp_present())
|
|
m_output_amp_capabilities = TRY(read_amp_capabilities(amp_params_node, GetParameterId::OutputAmplifierCapabilities));
|
|
if (input_amp_present())
|
|
m_input_amp_capabilities = TRY(read_amp_capabilities(amp_params_node, GetParameterId::InputAmplifierCapabilities));
|
|
|
|
if (widget_type() == WidgetType::PinComplex) {
|
|
// 7.3.4.9: Pin Capabilities
|
|
auto pin_capabilities = TRY(parameter(GetParameterId::PinCapabilities));
|
|
m_pin_complex_input_supported = (pin_capabilities & PinCapabilityFlag::InputCapable) > 0;
|
|
m_pin_complex_output_supported = (pin_capabilities & PinCapabilityFlag::OutputCapable) > 0;
|
|
|
|
TRY(populate_pin_configuration_default());
|
|
}
|
|
|
|
// Connection list
|
|
if (connection_list_present())
|
|
TRY(populate_connection_list());
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<NonnullOwnPtr<KString>> WidgetNode::to_string()
|
|
{
|
|
StringBuilder builder;
|
|
TRY(builder.try_appendff("WidgetNode(node_id={}, type={})", node_id(), widget_type_name()));
|
|
return KString::try_create(builder.string_view());
|
|
}
|
|
|
|
void WidgetNode::debug_dump(StringView group_spine, bool is_last) const
|
|
{
|
|
dbgln("{} {} Widget (node #{}):", group_spine, is_last ? "└"sv : "├"sv, node_id());
|
|
auto spine = is_last ? " "sv : "│"sv;
|
|
dbgln("{} {} ├ Type: {} ({:#x})", group_spine, spine, widget_type_name(), to_underlying(widget_type()));
|
|
dbgln("{} {} ├ Channel count: {}", group_spine, spine, channel_count());
|
|
dbgln("{} {} ├ Power control supported: {}", group_spine, spine, m_power_control_supported ? "yes"sv : "no"sv);
|
|
|
|
if (supports_stream()) {
|
|
dbgln("{} {} ├ Selected stream: {}", group_spine, spine, selected_stream());
|
|
if (channel_count() == 1)
|
|
dbgln("{} {} ├ Selected channel: {}", group_spine, spine, selected_channel());
|
|
else
|
|
dbgln("{} {} ├ Selected channels: {}-{}", group_spine, spine, selected_channel(), selected_channel() + channel_count() - 1);
|
|
|
|
dbgln("{} {} ├ Format override: {}", group_spine, spine, format_override() ? "yes"sv : "no"sv);
|
|
dbgln("{} {} ├ Supported PCM bit sizes:", group_spine, spine);
|
|
for (auto supported_size : supported_pcm_sizes())
|
|
dbgln("{} {} │ • {}", group_spine, spine, supported_size);
|
|
|
|
dbgln("{} {} ├ Supported PCM rates:", group_spine, spine);
|
|
for (auto supported_rate : supported_pcm_rates())
|
|
dbgln("{} {} │ • {}Hz", group_spine, spine, supported_rate);
|
|
|
|
dbgln("{} {} ├ Supported stream formats:", group_spine, spine);
|
|
if (has_flag(supported_stream_formats(), StreamFormatFlag::PCM))
|
|
dbgln("{} {} │ • PCM", group_spine, spine);
|
|
if (has_flag(supported_stream_formats(), StreamFormatFlag::Float32))
|
|
dbgln("{} {} │ • Float32", group_spine, spine);
|
|
if (has_flag(supported_stream_formats(), StreamFormatFlag::AC3))
|
|
dbgln("{} {} │ • AC3", group_spine, spine);
|
|
}
|
|
|
|
dbgln("{} {} ├ Amplifier parameters override: {}", group_spine, spine, amp_param_override() ? "yes"sv : "no"sv);
|
|
dbgln("{} {} ├ Output amplifier present: {}", group_spine, spine, output_amp_present() ? "yes"sv : "no"sv);
|
|
if (output_amp_present()) {
|
|
auto amp_capabilities = output_amp_capabilities();
|
|
dbgln("{} {} │ ├ Muting supported: {}", group_spine, spine, amp_capabilities.muting_supported ? "yes"sv : "no"sv);
|
|
dbgln("{} {} │ ├ Step size: {}", group_spine, spine, amp_capabilities.step_size);
|
|
dbgln("{} {} │ ├ Number of steps: {}", group_spine, spine, amp_capabilities.number_of_steps);
|
|
dbgln("{} {} │ └ Offset: {}", group_spine, spine, amp_capabilities.offset);
|
|
}
|
|
|
|
dbgln("{} {} ├ Input amplifier present: {}", group_spine, spine, input_amp_present() ? "yes"sv : "no"sv);
|
|
if (input_amp_present()) {
|
|
auto amp_capabilities = input_amp_capabilities();
|
|
dbgln("{} {} │ ├ Muting supported: {}", group_spine, spine, amp_capabilities.muting_supported ? "yes"sv : "no"sv);
|
|
dbgln("{} {} │ ├ Step size: {}", group_spine, spine, amp_capabilities.step_size);
|
|
dbgln("{} {} │ ├ Number of steps: {}", group_spine, spine, amp_capabilities.number_of_steps);
|
|
dbgln("{} {} │ └ Offset: {}", group_spine, spine, amp_capabilities.offset);
|
|
}
|
|
|
|
if (widget_type() == WidgetType::PinComplex) {
|
|
dbgln("{} {} ├ Pin complex input supported: {}", group_spine, spine, pin_complex_input_supported());
|
|
dbgln("{} {} ├ Pin complex output supported: {}", group_spine, spine, pin_complex_output_supported());
|
|
dbgln("{} {} ├ Pin configuration default:", group_spine, spine);
|
|
dbgln("{} {} │ ├ Sequence: {}", group_spine, spine, m_pin_configuration_default.sequence);
|
|
dbgln("{} {} │ ├ Default association: {}", group_spine, spine, m_pin_configuration_default.default_association);
|
|
dbgln("{} {} │ ├ Jack detect override: {}", group_spine, spine,
|
|
((static_cast<u8>(m_pin_configuration_default.misc) & to_underlying(PinMiscFlag::JackDetectOverride)) > 0) ? "yes"sv : "no"sv);
|
|
dbgln("{} {} │ ├ Color: {}", group_spine, spine, pin_color_name());
|
|
dbgln("{} {} │ ├ Connection type: {}", group_spine, spine, pin_connection_type_name());
|
|
dbgln("{} {} │ ├ Default device: {}", group_spine, spine, pin_default_device_name());
|
|
dbgln("{} {} │ ├ Location: {}, {}", group_spine, spine, pin_gross_location_name(), pin_geometric_location_name());
|
|
dbgln("{} {} │ └ Port connectivity: {}", group_spine, spine, pin_port_connectivity_name());
|
|
}
|
|
|
|
dbgln("{} {} └ Connection list:{}", group_spine, spine, connection_list_present() ? ""sv : " absent"sv);
|
|
if (connection_list_present()) {
|
|
auto selected_node_id = connection_selected_node_id();
|
|
auto all_active = !supports_connection_select_control();
|
|
for (auto connection_entry : connection_list()) {
|
|
dbgln("{} {} • Node #{}{}", group_spine, spine, connection_entry,
|
|
all_active || connection_entry == selected_node_id ? " (active)"sv : ""sv);
|
|
}
|
|
}
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::set_amplifier_gain_mute(SetAmplifierGainMute settings)
|
|
{
|
|
// 7.3.3.7: Amplifier Gain/Mute
|
|
VERIFY(input_amp_present() || output_amp_present());
|
|
u16 set_amp_gain_payload = ((output_amp_present() ? 1 : 0) << 15)
|
|
| ((input_amp_present() ? 1 : 0) << 15)
|
|
| ((settings.set_left ? 1 : 0) << 13)
|
|
| ((settings.set_right ? 1 : 0) << 12)
|
|
| ((settings.connection_index & 0xf) << 8)
|
|
| ((settings.mute ? 1 : 0) << 7)
|
|
| (settings.gain & 0x7f);
|
|
TRY(command(CodecControlVerb::SetAmplifierGainMute, set_amp_gain_payload));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::set_connection_select(u8 connection_index)
|
|
{
|
|
// 7.3.3.2: Connection Select Control
|
|
VERIFY(connection_list_present());
|
|
VERIFY(connection_index < connection_list().size());
|
|
TRY(command(CodecControlVerb::SetConnectionSelectControl, connection_index));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::set_converter_stream_and_channel(u8 stream_index, u8 channel_index)
|
|
{
|
|
// 7.3.3.11: Converter Stream, Channel
|
|
VERIFY(widget_type() == WidgetType::AudioInput || widget_type() == WidgetType::AudioOutput);
|
|
u16 stream_channel_payload = ((stream_index & 0xf) << 4) | (channel_index & 0xf);
|
|
TRY(command(CodecControlVerb::SetConverterStreamChannel, stream_channel_payload));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::set_pin_control(PinControl pin_control)
|
|
{
|
|
// 7.3.3.13: Pin Widget Control
|
|
VERIFY(widget_type() == WidgetType::PinComplex);
|
|
VERIFY(!pin_control.output_enabled || pin_complex_output_supported());
|
|
VERIFY(!pin_control.input_enabled || pin_complex_input_supported());
|
|
|
|
u8 payload = ((pin_control.low_impedance_amplifier_enabled ? 1 : 0) << 7)
|
|
| ((pin_control.output_enabled ? 1 : 0) << 6)
|
|
| ((pin_control.input_enabled ? 1 : 0) << 5)
|
|
| (pin_control.voltage_reference_enable & 0x7);
|
|
TRY(command(CodecControlVerb::SetPinWidgetControl, payload));
|
|
return {};
|
|
}
|
|
|
|
bool WidgetNode::supports_stream() const
|
|
{
|
|
return widget_type() == WidgetType::AudioInput
|
|
|| widget_type() == WidgetType::AudioOutput;
|
|
}
|
|
|
|
bool WidgetNode::supports_connection_select_control() const
|
|
{
|
|
return widget_type() == WidgetType::AudioInput
|
|
|| widget_type() == WidgetType::AudioSelector
|
|
|| widget_type() == WidgetType::PinComplex;
|
|
}
|
|
|
|
ErrorOr<FormatParameters> WidgetNode::get_converter_format()
|
|
{
|
|
// 7.3.3.8: Converter Format
|
|
VERIFY(widget_type() == WidgetType::AudioInput || widget_type() == WidgetType::AudioOutput);
|
|
u16 format = TRY(command(CodecControlVerb::GetConverterFormat, 0)) & 0xffffu;
|
|
return decode_format(format);
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::set_converter_format(FormatParameters format)
|
|
{
|
|
// 7.3.3.8: Converter Format
|
|
VERIFY(widget_type() == WidgetType::AudioInput || widget_type() == WidgetType::AudioOutput);
|
|
u16 format_payload = TRY(encode_format(format));
|
|
TRY(command(CodecControlVerb::SetConverterFormat, format_payload));
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::populate_supported_pcm_size_rates()
|
|
{
|
|
VERIFY(m_supported_pcm_sizes.is_empty() && m_supported_pcm_rates.is_empty());
|
|
|
|
// 7.3.4.7: Supported PCM Size, Rates
|
|
Node& stream_support_node = format_override() ? *this : *parent_node();
|
|
auto supported_pcm_size_and_rates = TRY(stream_support_node.parameter(GetParameterId::SupportedPCMSizeRates));
|
|
|
|
auto pcm_sizes = (supported_pcm_size_and_rates >> 16) & 0x1f;
|
|
TRY(m_supported_pcm_sizes.try_ensure_capacity(popcount(pcm_sizes)));
|
|
for (auto bit_rate_encoding : bit_rate_encodings) {
|
|
if ((pcm_sizes & bit_rate_encoding.flag) > 0)
|
|
m_supported_pcm_sizes.unchecked_append(bit_rate_encoding.bit_rate);
|
|
}
|
|
|
|
auto pcm_rates = supported_pcm_size_and_rates & 0x7ff;
|
|
TRY(m_supported_pcm_rates.try_ensure_capacity(popcount(pcm_rates)));
|
|
for (auto sample_rate_encoding : sample_rate_encodings) {
|
|
if ((pcm_rates & sample_rate_encoding.flag) > 0)
|
|
m_supported_pcm_rates.unchecked_append(sample_rate_encoding.sample_rate);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::populate_supported_stream_formats()
|
|
{
|
|
VERIFY(m_supported_stream_formats == 0);
|
|
|
|
// 7.3.4.8: Supported Stream Formats
|
|
Node& stream_support_node = format_override() ? *this : *parent_node();
|
|
auto supported_stream_formats = TRY(stream_support_node.parameter(GetParameterId::SupportedStreamFormats));
|
|
|
|
if ((supported_stream_formats & 0x1) > 0)
|
|
m_supported_stream_formats |= StreamFormatFlag::PCM;
|
|
if ((supported_stream_formats & 0x2) > 0)
|
|
m_supported_stream_formats |= StreamFormatFlag::Float32;
|
|
if ((supported_stream_formats & 0x4) > 0)
|
|
m_supported_stream_formats |= StreamFormatFlag::AC3;
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::populate_connection_list()
|
|
{
|
|
VERIFY(connection_list_present());
|
|
VERIFY(m_connection_list.is_empty());
|
|
|
|
// 7.3.4.11: Connection List Length
|
|
auto connection_list_length_info = TRY(parameter(GetParameterId::ConnectionListLength));
|
|
bool long_form = (connection_list_length_info >> 7) & 0x1;
|
|
u8 connection_list_length = connection_list_length_info & 0x7f;
|
|
u8 entries_per_request = long_form ? 2 : 4;
|
|
|
|
// 7.3.3.3: Get Connection List Entry
|
|
for (u8 entry_offset = 0; entry_offset < connection_list_length; entry_offset += entries_per_request) {
|
|
auto entries = TRY(command(CodecControlVerb::GetConnectionListEntry, entry_offset));
|
|
for (u8 entry_index = 0; entry_index < min(entries_per_request, static_cast<int>(connection_list_length) - entry_offset); ++entry_index) {
|
|
u16 entry = entries & (long_form ? 0xffff : 0xff);
|
|
TRY(m_connection_list.try_append(entry));
|
|
entries >>= (32 / entries_per_request);
|
|
}
|
|
}
|
|
|
|
// 7.1.3: Widget Interconnection Rules
|
|
// "Connection_List_Length = 1 means there is only one (hard-wired) input possible and,
|
|
// therefore, there is no Connection_Selector field. The actual connection is read
|
|
// from the Connection List as usual."
|
|
if (connection_list_length == 1) {
|
|
m_connection_index = 0;
|
|
} else {
|
|
// 7.3.3.2: Connection Select Control
|
|
auto connection_selection_control = TRY(command(CodecControlVerb::GetConnectionSelectControl, 0));
|
|
m_connection_index = connection_selection_control & 0xff;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<void> WidgetNode::populate_pin_configuration_default()
|
|
{
|
|
VERIFY(widget_type() == WidgetType::PinComplex);
|
|
|
|
u32 configuration_default = TRY(command(CodecControlVerb::GetConfigurationDefault, 0));
|
|
m_pin_configuration_default.sequence = configuration_default & 0xf;
|
|
m_pin_configuration_default.default_association = (configuration_default >> 4) & 0xf;
|
|
m_pin_configuration_default.misc = static_cast<PinMiscFlag>((configuration_default >> 8) & 0xf);
|
|
m_pin_configuration_default.color = static_cast<PinColor>((configuration_default >> 12) & 0xf);
|
|
m_pin_configuration_default.connection_type = static_cast<PinConnectionType>((configuration_default >> 16) & 0xf);
|
|
m_pin_configuration_default.default_device = static_cast<PinDefaultDevice>((configuration_default >> 20) & 0xf);
|
|
m_pin_configuration_default.geometric_location = static_cast<PinGeometricLocation>((configuration_default >> 24) & 0xf);
|
|
m_pin_configuration_default.gross_location = static_cast<PinGrossLocation>((configuration_default >> 28) & 0x3);
|
|
m_pin_configuration_default.port_connectivity = static_cast<PinPortConnectivity>(configuration_default >> 30);
|
|
|
|
return {};
|
|
}
|
|
|
|
StringView WidgetNode::pin_color_name() const
|
|
{
|
|
auto underlying_color = to_underlying(m_pin_configuration_default.color);
|
|
if (underlying_color >= 0xa && underlying_color <= 0xd)
|
|
return "Reserved"sv;
|
|
|
|
switch (m_pin_configuration_default.color) {
|
|
case PinColor::Unknown:
|
|
return "Unknown"sv;
|
|
case PinColor::Black:
|
|
return "Black"sv;
|
|
case PinColor::Grey:
|
|
return "Grey"sv;
|
|
case PinColor::Blue:
|
|
return "Blue"sv;
|
|
case PinColor::Green:
|
|
return "Green"sv;
|
|
case PinColor::Red:
|
|
return "Red"sv;
|
|
case PinColor::Orange:
|
|
return "Orange"sv;
|
|
case PinColor::Yellow:
|
|
return "Yellow"sv;
|
|
case PinColor::Purple:
|
|
return "Purple"sv;
|
|
case PinColor::Pink:
|
|
return "Pink"sv;
|
|
case PinColor::White:
|
|
return "White"sv;
|
|
case PinColor::Other:
|
|
return "Other"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
StringView WidgetNode::pin_connection_type_name() const
|
|
{
|
|
switch (m_pin_configuration_default.connection_type) {
|
|
case PinConnectionType::Unknown:
|
|
return "Unknown"sv;
|
|
case PinConnectionType::EighthStereoMono:
|
|
return "1/8\" Stereo/Mono"sv;
|
|
case PinConnectionType::FourthStereoMono:
|
|
return "1/4\" Stereo/Mono"sv;
|
|
case PinConnectionType::ATAPIInternal:
|
|
return "ATAPI Internal"sv;
|
|
case PinConnectionType::RCA:
|
|
return "RCA"sv;
|
|
case PinConnectionType::Optical:
|
|
return "Optical"sv;
|
|
case PinConnectionType::OtherDigital:
|
|
return "Other Digital"sv;
|
|
case PinConnectionType::OtherAnalog:
|
|
return "Other Analog"sv;
|
|
case PinConnectionType::MultichannelAnalog:
|
|
return "Multichannel Analog"sv;
|
|
case PinConnectionType::XLRProfessional:
|
|
return "XLR / Professional"sv;
|
|
case PinConnectionType::RJ11:
|
|
return "RJ-11 (Modem)"sv;
|
|
case PinConnectionType::Combination:
|
|
return "Combination"sv;
|
|
case PinConnectionType::Other:
|
|
return "Other"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
StringView WidgetNode::pin_default_device_name() const
|
|
{
|
|
switch (m_pin_configuration_default.default_device) {
|
|
case PinDefaultDevice::LineOut:
|
|
return "Line Out"sv;
|
|
case PinDefaultDevice::Speaker:
|
|
return "Speaker"sv;
|
|
case PinDefaultDevice::HPOut:
|
|
return "Headphones"sv;
|
|
case PinDefaultDevice::CD:
|
|
return "CD"sv;
|
|
case PinDefaultDevice::SPDIFOut:
|
|
return "S/PDIF Out"sv;
|
|
case PinDefaultDevice::DigitalOtherOut:
|
|
return "Digital Other Out"sv;
|
|
case PinDefaultDevice::ModemLineSide:
|
|
return "Modem Line Side"sv;
|
|
case PinDefaultDevice::ModemHandsetSide:
|
|
return "Modem Handset Side"sv;
|
|
case PinDefaultDevice::LineIn:
|
|
return "Line In"sv;
|
|
case PinDefaultDevice::AUX:
|
|
return "AUX"sv;
|
|
case PinDefaultDevice::MicIn:
|
|
return "Mic In"sv;
|
|
case PinDefaultDevice::Telephony:
|
|
return "Telephony"sv;
|
|
case PinDefaultDevice::SPDIFIn:
|
|
return "S/PDIF In"sv;
|
|
case PinDefaultDevice::DigitalOtherIn:
|
|
return "Digital Other In"sv;
|
|
case PinDefaultDevice::Reserved:
|
|
return "Reserved"sv;
|
|
case PinDefaultDevice::Other:
|
|
return "Other"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
StringView WidgetNode::pin_gross_location_name() const
|
|
{
|
|
switch (m_pin_configuration_default.gross_location) {
|
|
case PinGrossLocation::ExternalOnPrimaryChassis:
|
|
return "External on Primary Chassis"sv;
|
|
case PinGrossLocation::Internal:
|
|
return "Internal"sv;
|
|
case PinGrossLocation::SeparateChassis:
|
|
return "Separate Chassis"sv;
|
|
case PinGrossLocation::Other:
|
|
return "Other"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
StringView WidgetNode::pin_geometric_location_name() const
|
|
{
|
|
// 7.3.3.31: Configuration Default - special cases
|
|
if (m_pin_configuration_default.geometric_location == PinGeometricLocation::Special1) {
|
|
if (m_pin_configuration_default.gross_location == PinGrossLocation::ExternalOnPrimaryChassis)
|
|
return "Rear Panel"sv;
|
|
if (m_pin_configuration_default.gross_location == PinGrossLocation::Internal)
|
|
return "Riser"sv;
|
|
if (m_pin_configuration_default.gross_location == PinGrossLocation::Other)
|
|
return "Mobile Lid (Inside)"sv;
|
|
} else if (m_pin_configuration_default.geometric_location == PinGeometricLocation::Special2) {
|
|
if (m_pin_configuration_default.gross_location == PinGrossLocation::ExternalOnPrimaryChassis)
|
|
return "Drive Bay"sv;
|
|
if (m_pin_configuration_default.gross_location == PinGrossLocation::Internal)
|
|
return "Digital Display"sv;
|
|
if (m_pin_configuration_default.gross_location == PinGrossLocation::Other)
|
|
return "Mobile Lid (Outside)"sv;
|
|
} else if (m_pin_configuration_default.geometric_location == PinGeometricLocation::Special3) {
|
|
if (m_pin_configuration_default.gross_location == PinGrossLocation::Internal)
|
|
return "ATAPI"sv;
|
|
}
|
|
|
|
switch (m_pin_configuration_default.geometric_location) {
|
|
case PinGeometricLocation::NotApplicable:
|
|
return "N/A"sv;
|
|
case PinGeometricLocation::Rear:
|
|
return "Rear"sv;
|
|
case PinGeometricLocation::Front:
|
|
return "Front"sv;
|
|
case PinGeometricLocation::Left:
|
|
return "Left"sv;
|
|
case PinGeometricLocation::Right:
|
|
return "Right"sv;
|
|
case PinGeometricLocation::Top:
|
|
return "Top"sv;
|
|
case PinGeometricLocation::Bottom:
|
|
return "Bottom"sv;
|
|
case PinGeometricLocation::Special1:
|
|
case PinGeometricLocation::Special2:
|
|
case PinGeometricLocation::Special3:
|
|
return "Special"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
StringView WidgetNode::pin_port_connectivity_name() const
|
|
{
|
|
switch (m_pin_configuration_default.port_connectivity) {
|
|
case PinPortConnectivity::Jack:
|
|
return "Jack"sv;
|
|
case PinPortConnectivity::NoConnection:
|
|
return "No Physical Connection"sv;
|
|
case PinPortConnectivity::FixedFunction:
|
|
return "Fixed Function Device"sv;
|
|
case PinPortConnectivity::JackAndFixedFunction:
|
|
return "Jack and Fixed Function Device"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
StringView FunctionGroupNode::function_group_type_name() const
|
|
{
|
|
switch (m_function_group_type) {
|
|
case FunctionGroupType::AudioFunctionGroup:
|
|
return "Audio Function Group"sv;
|
|
case FunctionGroupType::ModemFunctionGroup:
|
|
return "Modem Function Group"sv;
|
|
case FunctionGroupType::VendorFunctionGroup:
|
|
return "Vendor Function Group"sv;
|
|
case FunctionGroupType::Reserved:
|
|
return "Reserved"sv;
|
|
}
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
ErrorOr<void> FunctionGroupNode::initialize()
|
|
{
|
|
TRY(NodeWithChildren<WidgetNode>::initialize());
|
|
|
|
// 7.3.4.4: Function Group Type
|
|
auto function_group_type = TRY(parameter(GetParameterId::FunctionGroupType));
|
|
if (function_group_type == 0x1)
|
|
m_function_group_type = FunctionGroupType::AudioFunctionGroup;
|
|
else if (function_group_type == 0x2)
|
|
m_function_group_type = FunctionGroupType::ModemFunctionGroup;
|
|
else if (function_group_type >= 0x80)
|
|
m_function_group_type = FunctionGroupType::VendorFunctionGroup;
|
|
else
|
|
m_function_group_type = FunctionGroupType::Reserved;
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<NonnullOwnPtr<KString>> FunctionGroupNode::to_string()
|
|
{
|
|
StringBuilder builder;
|
|
TRY(builder.try_appendff("FunctionGroupNode(node_id={})", node_id()));
|
|
return KString::try_create(builder.string_view());
|
|
}
|
|
|
|
void FunctionGroupNode::debug_dump(bool is_last) const
|
|
{
|
|
dbgln("{} Function group (node #{}):", is_last ? "└"sv : "├"sv, node_id());
|
|
auto spine = is_last ? " "sv : "│"sv;
|
|
dbgln("{} ├ Function group type: {} ({:#x})", spine, function_group_type_name(), to_underlying(function_group_type()));
|
|
|
|
for_each_child_node([&spine](WidgetNode const& widget_node, bool is_last) -> void {
|
|
widget_node.debug_dump(spine, is_last);
|
|
});
|
|
}
|
|
|
|
ErrorOr<void> RootNode::initialize()
|
|
{
|
|
TRY(NodeWithChildren<FunctionGroupNode>::initialize());
|
|
|
|
// 7.3.4.1: Vendor ID
|
|
auto vendor_id_response = TRY(parameter(GetParameterId::VendorID));
|
|
m_vendor_id = (vendor_id_response >> 16) & 0xffff;
|
|
m_device_id = vendor_id_response & 0xffff;
|
|
|
|
// 7.3.4.2: Revision ID
|
|
auto revision_id_response = TRY(parameter(GetParameterId::RevisionID));
|
|
m_major_revision = (revision_id_response >> 20) & 0xf;
|
|
m_minor_revision = (revision_id_response >> 16) & 0xf;
|
|
if (m_major_revision != 1 || m_minor_revision != 0)
|
|
return ENOTSUP;
|
|
|
|
return {};
|
|
}
|
|
|
|
ErrorOr<NonnullOwnPtr<KString>> RootNode::to_string()
|
|
{
|
|
StringBuilder builder;
|
|
TRY(builder.try_appendff("RootNode(node_id={})", node_id()));
|
|
return KString::try_create(builder.string_view());
|
|
}
|
|
|
|
void RootNode::debug_dump() const
|
|
{
|
|
dbgln("Root (node #{}):", node_id());
|
|
dbgln("├ Codec vendor: {:#04x}, device: {:#04x}", vendor_id(), device_id());
|
|
dbgln("├ Codec HDA compatibility: {}.{}", major_revision(), minor_revision());
|
|
|
|
for_each_child_node([](FunctionGroupNode const& fg_node, bool is_last) -> void {
|
|
fg_node.debug_dump(is_last);
|
|
});
|
|
}
|
|
|
|
}
|