summaryrefslogtreecommitdiff
path: root/Kernel/Devices/Audio/IntelHDA/OutputPath.h
blob: 7d6e8b57b437155aecaf1dfcef4b37bf0b69937c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
 * Copyright (c) 2023, Jelle Raaijmakers <jelle@gmta.nl>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/NonnullOwnPtr.h>
#include <AK/NonnullRefPtr.h>
#include <AK/StringBuilder.h>
#include <AK/Vector.h>
#include <Kernel/Devices/Audio/Channel.h>
#include <Kernel/Devices/Audio/IntelHDA/Stream.h>
#include <Kernel/Library/KString.h>

namespace Kernel::Audio::IntelHDA {

class WidgetNode;

class OutputPath {
public:
    static constexpr u8 fixed_pcm_bits = 16;
    static constexpr u8 fixed_channel_count = 2;

    static ErrorOr<NonnullOwnPtr<OutputPath>> create(Vector<NonnullRefPtr<WidgetNode>> widget_path, NonnullOwnPtr<OutputStream> output_stream)
    {
        return adopt_nonnull_own_or_enomem(new (nothrow) OutputPath(move(widget_path), move(output_stream)));
    }

    OutputStream& output_stream() { return *m_output_stream; }

    ErrorOr<void> activate()
    {
        // Power on the function group and all widgets that support it
        auto output_widget = get<WidgetNode::WidgetType::AudioOutput>();
        auto group = output_widget->parent_node();
        TRY(group->set_power_state(Node::PowerState::D0));
        for (auto& widget : m_widget_path) {
            if (widget->power_control_supported())
                TRY(widget->set_power_state(Node::PowerState::D0));
        }

        // Link the audio output widget to the output stream number and first channel
        TRY(output_widget->set_converter_stream_and_channel(m_output_stream->stream_number(), OutputStream::fixed_channel));

        // Set full volume for all output amplifiers in the path
        for (auto& widget : m_widget_path) {
            if (!widget->output_amp_present())
                continue;

            // NOTE: setting gain to the offset means 0dB attenuation / 100% volume
            TRY(widget->set_amplifier_gain_mute({
                .mute = false,
                .gain = widget->output_amp_capabilities().offset,
            }));
        }

        // Walk through pairs of widgets and connect them to each other
        for (size_t i = 0; i < m_widget_path.size() - 1; ++i) {
            auto left_widget = m_widget_path[i];
            auto right_widget = m_widget_path[i + 1];

            VERIFY(left_widget->connection_list_present());
            if (left_widget->connection_list().size() == 1) {
                // If there is only one possible connection, it is fixed and we cannot change it.
                VERIFY(left_widget->connection_selected_node_id() == right_widget->node_id());
            } else {
                // Find the index of the right widget node id in the connection list
                size_t connection_index = 0;
                for (auto connection_node_id : left_widget->connection_list()) {
                    if (connection_node_id == right_widget->node_id())
                        break;
                    ++connection_index;
                }
                VERIFY(connection_index < left_widget->connection_list().size());

                // Select this index
                TRY(left_widget->set_connection_select(connection_index));
            }
        }

        // Enable pin complex output
        auto pin_widget = get<WidgetNode::WidgetType::PinComplex>();
        TRY(pin_widget->set_pin_control({ .output_enabled = true }));

        // Finally, retrieve the active converter format for the output widget and set the same for our output stream
        auto converter_format = TRY(output_widget->get_converter_format());
        TRY(set_format(converter_format));
        return {};
    }

    ErrorOr<void> set_format(FormatParameters format)
    {
        // FIXME: support other PCM bit sizes and channel counts
        format.pcm_bits = fixed_pcm_bits;
        format.number_of_channels = fixed_channel_count;

        // 7.3.3.8: Converter Format
        // "The Converter Format control determines the format the converter will use. This must match the
        // format programmed into the Stream Descriptor on the controller so that the data format being
        // transmitted on the link matches what is expected by the consumer of the data."
        auto output_widget = get<WidgetNode::WidgetType::AudioOutput>();
        if (!output_widget->supported_pcm_rates().contains_slow(format.sample_rate)
            || !output_widget->supported_pcm_sizes().contains_slow(format.pcm_bits)
            || format.number_of_channels > output_widget->channel_count())
            return ENOTSUP;

        TRY(m_output_stream->set_format(format));
        TRY(output_widget->set_converter_format(format));
        return {};
    }

    ErrorOr<NonnullOwnPtr<KString>> to_string()
    {
        StringBuilder builder;
        TRY(builder.try_append("OutputPath: ["sv));
        for (size_t i = 0; i < m_widget_path.size(); ++i) {
            auto widget = m_widget_path[i];
            TRY(builder.try_append(TRY(widget->to_string())->view()));
            if (i < m_widget_path.size() - 1)
                TRY(builder.try_append(" → "sv));
        }
        TRY(builder.try_append(']'));
        return KString::try_create(builder.string_view());
    }

private:
    OutputPath(Vector<NonnullRefPtr<WidgetNode>> widget_path, NonnullOwnPtr<OutputStream> output_stream)
        : m_widget_path(move(widget_path))
        , m_output_stream(move(output_stream))
    {
    }

    template<WidgetNode::WidgetType T>
    NonnullRefPtr<WidgetNode> get()
    {
        for (auto& widget : m_widget_path) {
            if (widget->widget_type() == T)
                return widget;
        }
        VERIFY_NOT_REACHED();
    }

    Vector<NonnullRefPtr<WidgetNode>> m_widget_path;
    NonnullOwnPtr<OutputStream> m_output_stream;
};

}