|
@@ -0,0 +1,721 @@
|
|
|
+/*
|
|
|
+ * 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);
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+}
|