/* * Copyright (c) 2023, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #include "Codec.h" #include #include namespace Kernel::Audio::IntelHDA { // 7.3.4.7: Supported PCM Size, Rates struct BitRateEncoding { u8 flag; u8 bit_rate; }; static constexpr Array 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 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 Codec::register_node(NonnullRefPtr node) { auto set_result = TRY(m_nodes_by_node_id.try_set(node->node_id(), node)); VERIFY(set_result == HashSetResult::InsertedNewEntry); return {}; } ErrorOr Node::initialize() { return codec().register_node(*this); } ErrorOr 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 Node::parameter(GetParameterId get_parameter_id) { return command(CodecControlVerb::GetParameter, to_underlying(get_parameter_id)); } ErrorOr Node::set_power_state(PowerState power_state) { // 7.3.3.10: Power State TRY(command(CodecControlVerb::SetPowerState, to_underlying(power_state))); return {}; } template void NodeWithChildren::for_each_child_node(Function 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 ErrorOr NodeWithChildren::initialize() { TRY(Node::initialize()); return populate_child_nodes(); } template ErrorOr NodeWithChildren::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(*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 WidgetNode::initialize() { TRY(Node::initialize()); // 7.3.4.6: Audio Widget Capabilities auto widget_capabilities = TRY(parameter(GetParameterId::AudioWidgetCapabilities)); m_widget_type = static_cast((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 { auto capabilities = TRY(node.parameter(type)); return AmplifierCapabilities { .muting_supported = ((capabilities >> 31) & 0x1) > 0, .step_size = static_cast((capabilities >> 16) & 0x7f), .number_of_steps = static_cast(((capabilities >> 8) & 0x7f) + 1), .offset = static_cast(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> 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(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 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 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 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 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 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 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 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 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 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(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 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((configuration_default >> 8) & 0xf); m_pin_configuration_default.color = static_cast((configuration_default >> 12) & 0xf); m_pin_configuration_default.connection_type = static_cast((configuration_default >> 16) & 0xf); m_pin_configuration_default.default_device = static_cast((configuration_default >> 20) & 0xf); m_pin_configuration_default.geometric_location = static_cast((configuration_default >> 24) & 0xf); m_pin_configuration_default.gross_location = static_cast((configuration_default >> 28) & 0x3); m_pin_configuration_default.port_connectivity = static_cast(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 FunctionGroupNode::initialize() { TRY(NodeWithChildren::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> 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 RootNode::initialize() { TRY(NodeWithChildren::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> 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); }); } }