Newer
Older
minerva / Kernel / Devices / Audio / IntelHDA / Codec.cpp
@minerva minerva on 13 Jul 28 KB Initial commit
/*
 * 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);
    });
}

}