Kernel: Implement volume control to audio drivers

This commit is contained in:
Bananymous 2026-04-02 15:14:27 +03:00
parent 85f61aded5
commit 5647cf24d2
9 changed files with 166 additions and 20 deletions

View File

@ -6,7 +6,7 @@
namespace Kernel namespace Kernel
{ {
class AC97AudioController : public AudioController, public Interruptable class AC97AudioController final : public AudioController, public Interruptable
{ {
public: public:
static BAN::ErrorOr<void> create(PCI::Device& pci_device); static BAN::ErrorOr<void> create(PCI::Device& pci_device);
@ -23,11 +23,15 @@ namespace Kernel
uint32_t get_current_pin() const override { return 0; } uint32_t get_current_pin() const override { return 0; }
BAN::ErrorOr<void> set_current_pin(uint32_t pin) override { if (pin != 0) return BAN::Error::from_errno(EINVAL); return {}; } BAN::ErrorOr<void> set_current_pin(uint32_t pin) override { if (pin != 0) return BAN::Error::from_errno(EINVAL); return {}; }
BAN::ErrorOr<void> set_volume_mdB(int32_t) override;
private: private:
AC97AudioController(PCI::Device& pci_device) AC97AudioController(PCI::Device& pci_device)
: m_pci_device(pci_device) : m_pci_device(pci_device)
{ } { }
uint32_t get_volume_data() const;
BAN::ErrorOr<void> initialize(); BAN::ErrorOr<void> initialize();
BAN::ErrorOr<void> initialize_bld(); BAN::ErrorOr<void> initialize_bld();
BAN::ErrorOr<void> initialize_interrupts(); BAN::ErrorOr<void> initialize_interrupts();

View File

@ -4,6 +4,8 @@
#include <kernel/Memory/ByteRingBuffer.h> #include <kernel/Memory/ByteRingBuffer.h>
#include <kernel/PCI.h> #include <kernel/PCI.h>
#include <sys/ioctl.h>
namespace Kernel namespace Kernel
{ {
@ -28,6 +30,8 @@ namespace Kernel
virtual uint32_t get_current_pin() const = 0; virtual uint32_t get_current_pin() const = 0;
virtual BAN::ErrorOr<void> set_current_pin(uint32_t) = 0; virtual BAN::ErrorOr<void> set_current_pin(uint32_t) = 0;
virtual BAN::ErrorOr<void> set_volume_mdB(int32_t) = 0;
bool can_read_impl() const override { return false; } bool can_read_impl() const override { return false; }
bool can_write_impl() const override { SpinLockGuard _(m_spinlock); return !m_sample_data->full(); } bool can_write_impl() const override { SpinLockGuard _(m_spinlock); return !m_sample_data->full(); }
bool has_error_impl() const override { return false; } bool has_error_impl() const override { return false; }
@ -44,6 +48,8 @@ namespace Kernel
static constexpr size_t m_sample_data_capacity = 1 << 20; static constexpr size_t m_sample_data_capacity = 1 << 20;
BAN::UniqPtr<ByteRingBuffer> m_sample_data; BAN::UniqPtr<ByteRingBuffer> m_sample_data;
snd_volume_info m_volume_info {};
private: private:
const dev_t m_rdev; const dev_t m_rdev;
char m_name[10] {}; char m_name[10] {};

View File

@ -8,7 +8,7 @@ namespace Kernel
class HDAudioController; class HDAudioController;
class HDAudioFunctionGroup : public AudioController class HDAudioFunctionGroup final : public AudioController
{ {
public: public:
static BAN::ErrorOr<BAN::RefPtr<HDAudioFunctionGroup>> create(BAN::RefPtr<HDAudioController>, uint8_t cid, HDAudio::AFGNode&&); static BAN::ErrorOr<BAN::RefPtr<HDAudioFunctionGroup>> create(BAN::RefPtr<HDAudioController>, uint8_t cid, HDAudio::AFGNode&&);
@ -24,6 +24,8 @@ namespace Kernel
uint32_t get_current_pin() const override; uint32_t get_current_pin() const override;
BAN::ErrorOr<void> set_current_pin(uint32_t) override; BAN::ErrorOr<void> set_current_pin(uint32_t) override;
BAN::ErrorOr<void> set_volume_mdB(int32_t) override;
void handle_new_data() override; void handle_new_data() override;
private: private:
@ -46,7 +48,6 @@ namespace Kernel
BAN::ErrorOr<void> recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector<const HDAudio::AFGWidget*>& path); BAN::ErrorOr<void> recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector<const HDAudio::AFGWidget*>& path);
uint16_t get_format_data() const; uint16_t get_format_data() const;
uint16_t get_volume_data() const;
size_t bdl_offset() const; size_t bdl_offset() const;

View File

@ -55,6 +55,16 @@ namespace Kernel::HDAudio
} pin_complex; } pin_complex;
}; };
struct Amplifier
{
uint8_t offset;
uint8_t num_steps;
uint8_t step_size;
bool mute;
};
BAN::Optional<Amplifier> output_amplifier;
BAN::Vector<uint16_t> connections; BAN::Vector<uint16_t> connections;
}; };

View File

@ -137,8 +137,27 @@ namespace Kernel
// Reset mixer to default values // Reset mixer to default values
m_mixer->write16(AudioMixerRegister::Reset, 0); m_mixer->write16(AudioMixerRegister::Reset, 0);
// Master volume 100%, no mute // Master volumes
m_mixer->write16(AudioMixerRegister::MasterVolume, 0x0000); m_mixer->write16(AudioMixerRegister::MasterVolume, 0x2020);
if (m_mixer->read16(AudioMixerRegister::MasterVolume) == 0x2020)
{
m_volume_info = {
.min_mdB = -94500,
.max_mdB = 0,
.step_mdB = 1500,
.mdB = 0,
};
}
else
{
m_volume_info = {
.min_mdB = -46500,
.max_mdB = 0,
.step_mdB = 1500,
.mdB = 0,
};
}
m_mixer->write16(AudioMixerRegister::MasterVolume, get_volume_data());
// PCM output volume left/right +0 db, no mute // PCM output volume left/right +0 db, no mute
m_mixer->write16(AudioMixerRegister::PCMOutVolume, 0x0808); m_mixer->write16(AudioMixerRegister::PCMOutVolume, 0x0808);
@ -187,6 +206,19 @@ namespace Kernel
return {}; return {};
} }
uint32_t AC97AudioController::get_volume_data() const
{
const uint32_t steps = (-m_volume_info.mdB + m_volume_info.step_mdB / 2) / m_volume_info.step_mdB;
return (steps << 8) | steps;
}
BAN::ErrorOr<void> AC97AudioController::set_volume_mdB(int32_t mdB)
{
m_volume_info.mdB = BAN::Math::clamp(mdB, m_volume_info.min_mdB, m_volume_info.max_mdB);
m_mixer->write16(AudioMixerRegister::MasterVolume, get_volume_data());
return {};
}
void AC97AudioController::handle_new_data() void AC97AudioController::handle_new_data()
{ {
ASSERT(m_spinlock.current_processor_has_lock()); ASSERT(m_spinlock.current_processor_has_lock());

View File

@ -105,6 +105,12 @@ namespace Kernel
case SND_SET_PIN: case SND_SET_PIN:
TRY(set_current_pin(*static_cast<uint32_t*>(arg))); TRY(set_current_pin(*static_cast<uint32_t*>(arg)));
return 0; return 0;
case SND_GET_VOLUME_INFO:
*static_cast<snd_volume_info*>(arg) = m_volume_info;
return 0;
case SND_SET_VOLUME_MDB:
TRY(set_volume_mdB(*static_cast<int32_t*>(arg)));
return 0;
} }
return CharacterDevice::ioctl_impl(cmd, arg); return CharacterDevice::ioctl_impl(cmd, arg);

View File

@ -136,6 +136,37 @@ namespace Kernel
return BAN::Error::from_errno(ENOTSUP); return BAN::Error::from_errno(ENOTSUP);
} }
BAN::ErrorOr<void> HDAudioFunctionGroup::set_volume_mdB(int32_t mdB)
{
mdB = BAN::Math::clamp(mdB, m_volume_info.min_mdB, m_volume_info.max_mdB);
const auto& path = m_output_paths[m_output_path_index];
for (size_t i = 0; i < path.size(); i++)
{
if (!path[i]->output_amplifier.has_value())
continue;
const int32_t step_round = (mdB >= 0)
? +m_volume_info.step_mdB / 2
: -m_volume_info.step_mdB / 2;
const uint32_t step = (mdB + step_round) / m_volume_info.step_mdB + path[i]->output_amplifier->offset;
const uint32_t volume = 0b1'0'1'1'0000'0'0000000 | step;
TRY(m_controller->send_command({
.data = static_cast<uint8_t>(volume & 0xFF),
.command = static_cast<uint16_t>(0x300 | (volume >> 8)),
.node_index = path[i]->id,
.codec_address = m_cid,
}));
break;
}
m_volume_info.mdB = mdB;
return {};
}
size_t HDAudioFunctionGroup::bdl_offset() const size_t HDAudioFunctionGroup::bdl_offset() const
{ {
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t); const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
@ -291,13 +322,6 @@ namespace Kernel
return 0b0'0'000'000'0'001'0001; return 0b0'0'000'000'0'001'0001;
} }
uint16_t HDAudioFunctionGroup::get_volume_data() const
{
// TODO: don't hardcode this
// left and right output, no mute, max gain
return 0b1'0'1'1'0000'0'1111111;
}
BAN::ErrorOr<void> HDAudioFunctionGroup::enable_output_path(uint8_t index) BAN::ErrorOr<void> HDAudioFunctionGroup::enable_output_path(uint8_t index)
{ {
ASSERT(index < m_output_paths.size()); ASSERT(index < m_output_paths.size());
@ -318,7 +342,6 @@ namespace Kernel
} }
const auto format = get_format_data(); const auto format = get_format_data();
const auto volume = get_volume_data();
for (size_t i = 0; i < path.size(); i++) for (size_t i = 0; i < path.size(); i++)
{ {
@ -347,13 +370,17 @@ namespace Kernel
})); }));
} }
// set volume // set volume to 0 dB, no mute
TRY(m_controller->send_command({ if (path[i]->output_amplifier.has_value())
.data = static_cast<uint8_t>(volume & 0xFF), {
.command = static_cast<uint16_t>(0x300 | (volume >> 8)), const uint32_t volume = 0b1'0'1'1'0000'0'0000000 | path[i]->output_amplifier->offset;
.node_index = path[i]->id, TRY(m_controller->send_command({
.codec_address = m_cid, .data = static_cast<uint8_t>(volume & 0xFF),
})); .command = static_cast<uint16_t>(0x300 | (volume >> 8)),
.node_index = path[i]->id,
.codec_address = m_cid,
}));
}
switch (path[i]->type) switch (path[i]->type)
{ {
@ -398,6 +425,41 @@ namespace Kernel
} }
} }
// update volume info to this path
m_volume_info.min_mdB = 0;
m_volume_info.max_mdB = 0;
m_volume_info.step_mdB = 0;
for (size_t i = 0; i < path.size(); i++)
{
if (!path[i]->output_amplifier.has_value())
continue;
const auto& amp = path[i]->output_amplifier.value();
const int32_t step_mdB = amp.step_size * 250;
m_volume_info.step_mdB = step_mdB;
m_volume_info.min_mdB = -amp.offset * step_mdB;
m_volume_info.max_mdB = (amp.num_steps - amp.offset) * step_mdB;
m_volume_info.mdB = BAN::Math::clamp(m_volume_info.mdB, m_volume_info.min_mdB, m_volume_info.max_mdB);
const int32_t step_round = (m_volume_info.mdB >= 0)
? +step_mdB / 2
: -step_mdB / 2;
const uint32_t step = (m_volume_info.mdB + step_round) / step_mdB + amp.offset;
const uint32_t volume = 0b1'0'1'1'0000'0'0000000 | step;
TRY(m_controller->send_command({
.data = static_cast<uint8_t>(volume & 0xFF),
.command = static_cast<uint16_t>(0x300 | (volume >> 8)),
.node_index = path[i]->id,
.codec_address = m_cid,
}));
break;
}
if (m_volume_info.min_mdB == 0 && m_volume_info.max_mdB == 0)
m_volume_info.mdB = 0;
return {}; return {};
} }

View File

@ -319,6 +319,20 @@ namespace Kernel
}; };
} }
if (const uint32_t out_amp_cap = send_command_or_zero(0xF00, 0x12))
{
const uint8_t offset = (out_amp_cap >> 0) & 0x7F;
const uint8_t num_steps = (out_amp_cap >> 8) & 0x7F;
const uint8_t step_size = (out_amp_cap >> 16) & 0x7F;
const bool mute = (out_amp_cap >> 31);
result.output_amplifier = HDAudio::AFGWidget::Amplifier {
.offset = offset,
.num_steps = num_steps,
.step_size = step_size,
.mute = mute,
};
}
const uint8_t connection_info = send_command_or_zero(0xF00, 0x0E); const uint8_t connection_info = send_command_or_zero(0xF00, 0x0E);
const uint8_t conn_width = (connection_info & 0x80) ? 2 : 1; const uint8_t conn_width = (connection_info & 0x80) ? 2 : 1;
const uint8_t conn_count = connection_info & 0x3F; const uint8_t conn_count = connection_info & 0x3F;

View File

@ -5,6 +5,8 @@
__BEGIN_DECLS __BEGIN_DECLS
#include <stdint.h>
#define I_ATMARK 1 #define I_ATMARK 1
#define I_CANPUT 2 #define I_CANPUT 2
#define I_CKBAND 3 #define I_CKBAND 3
@ -50,6 +52,13 @@ struct winsize
#define TIOCGWINSZ 50 #define TIOCGWINSZ 50
#define TIOCSWINSZ 51 #define TIOCSWINSZ 51
struct snd_volume_info
{
int32_t min_mdB;
int32_t max_mdB;
int32_t step_mdB;
int32_t mdB;
};
#define SND_GET_CHANNELS 60 /* stores number of channels to uint32_t argument */ #define SND_GET_CHANNELS 60 /* stores number of channels to uint32_t argument */
#define SND_GET_SAMPLE_RATE 61 /* stores sample rate to uint32_t argument */ #define SND_GET_SAMPLE_RATE 61 /* stores sample rate to uint32_t argument */
#define SND_RESET_BUFFER 62 /* stores the size of internal buffer to uint32_t argument and clears the buffer */ #define SND_RESET_BUFFER 62 /* stores the size of internal buffer to uint32_t argument and clears the buffer */
@ -57,6 +66,8 @@ struct winsize
#define SND_GET_TOTAL_PINS 64 /* gets the number of pins on the current device as uint32_t */ #define SND_GET_TOTAL_PINS 64 /* gets the number of pins on the current device as uint32_t */
#define SND_GET_PIN 65 /* gets the currently active pin as uint32_t */ #define SND_GET_PIN 65 /* gets the currently active pin as uint32_t */
#define SND_SET_PIN 66 /* sets the currently active pin to uint32_t */ #define SND_SET_PIN 66 /* sets the currently active pin to uint32_t */
#define SND_GET_VOLUME_INFO 67 /* gets the current volume as snd_volume_info */
#define SND_SET_VOLUME_MDB 68 /* sets the current volume to int32_t dB */
#define JOYSTICK_GET_LEDS 80 /* get controller led bitmap to uint8_t argument */ #define JOYSTICK_GET_LEDS 80 /* get controller led bitmap to uint8_t argument */
#define JOYSTICK_SET_LEDS 81 /* set controller leds to uint8_t bitmap */ #define JOYSTICK_SET_LEDS 81 /* set controller leds to uint8_t bitmap */