mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 20:27:45 +00:00
Kernel: Add support for Intel HDA
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.
This commit is contained in:
parent
c530f74e2f
commit
dd8fa73da1
17 changed files with 2627 additions and 20 deletions
721
Kernel/Devices/Audio/IntelHDA/Codec.cpp
Normal file
721
Kernel/Devices/Audio/IntelHDA/Codec.cpp
Normal file
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue