Kernel: Implement volume control to audio drivers
This commit is contained in:
parent
85f61aded5
commit
5647cf24d2
|
|
@ -6,7 +6,7 @@
|
|||
namespace Kernel
|
||||
{
|
||||
|
||||
class AC97AudioController : public AudioController, public Interruptable
|
||||
class AC97AudioController final : public AudioController, public Interruptable
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<void> create(PCI::Device& pci_device);
|
||||
|
|
@ -23,11 +23,15 @@ namespace Kernel
|
|||
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_volume_mdB(int32_t) override;
|
||||
|
||||
private:
|
||||
AC97AudioController(PCI::Device& pci_device)
|
||||
: m_pci_device(pci_device)
|
||||
{ }
|
||||
|
||||
uint32_t get_volume_data() const;
|
||||
|
||||
BAN::ErrorOr<void> initialize();
|
||||
BAN::ErrorOr<void> initialize_bld();
|
||||
BAN::ErrorOr<void> initialize_interrupts();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#include <kernel/Memory/ByteRingBuffer.h>
|
||||
#include <kernel/PCI.h>
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
|
|
@ -28,6 +30,8 @@ namespace Kernel
|
|||
virtual uint32_t get_current_pin() const = 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_write_impl() const override { SpinLockGuard _(m_spinlock); return !m_sample_data->full(); }
|
||||
bool has_error_impl() const override { return false; }
|
||||
|
|
@ -44,6 +48,8 @@ namespace Kernel
|
|||
static constexpr size_t m_sample_data_capacity = 1 << 20;
|
||||
BAN::UniqPtr<ByteRingBuffer> m_sample_data;
|
||||
|
||||
snd_volume_info m_volume_info {};
|
||||
|
||||
private:
|
||||
const dev_t m_rdev;
|
||||
char m_name[10] {};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ namespace Kernel
|
|||
|
||||
class HDAudioController;
|
||||
|
||||
class HDAudioFunctionGroup : public AudioController
|
||||
class HDAudioFunctionGroup final : public AudioController
|
||||
{
|
||||
public:
|
||||
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;
|
||||
BAN::ErrorOr<void> set_current_pin(uint32_t) override;
|
||||
|
||||
BAN::ErrorOr<void> set_volume_mdB(int32_t) override;
|
||||
|
||||
void handle_new_data() override;
|
||||
|
||||
private:
|
||||
|
|
@ -46,7 +48,6 @@ namespace Kernel
|
|||
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_volume_data() const;
|
||||
|
||||
size_t bdl_offset() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,16 @@ namespace Kernel::HDAudio
|
|||
} 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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -137,8 +137,27 @@ namespace Kernel
|
|||
// Reset mixer to default values
|
||||
m_mixer->write16(AudioMixerRegister::Reset, 0);
|
||||
|
||||
// Master volume 100%, no mute
|
||||
m_mixer->write16(AudioMixerRegister::MasterVolume, 0x0000);
|
||||
// Master volumes
|
||||
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
|
||||
m_mixer->write16(AudioMixerRegister::PCMOutVolume, 0x0808);
|
||||
|
|
@ -187,6 +206,19 @@ namespace Kernel
|
|||
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()
|
||||
{
|
||||
ASSERT(m_spinlock.current_processor_has_lock());
|
||||
|
|
|
|||
|
|
@ -105,6 +105,12 @@ namespace Kernel
|
|||
case SND_SET_PIN:
|
||||
TRY(set_current_pin(*static_cast<uint32_t*>(arg)));
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -136,6 +136,37 @@ namespace Kernel
|
|||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ASSERT(index < m_output_paths.size());
|
||||
|
|
@ -318,7 +342,6 @@ namespace Kernel
|
|||
}
|
||||
|
||||
const auto format = get_format_data();
|
||||
const auto volume = get_volume_data();
|
||||
|
||||
for (size_t i = 0; i < path.size(); i++)
|
||||
{
|
||||
|
|
@ -347,13 +370,17 @@ namespace Kernel
|
|||
}));
|
||||
}
|
||||
|
||||
// set volume
|
||||
// set volume to 0 dB, no mute
|
||||
if (path[i]->output_amplifier.has_value())
|
||||
{
|
||||
const uint32_t volume = 0b1'0'1'1'0000'0'0000000 | path[i]->output_amplifier->offset;
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 conn_width = (connection_info & 0x80) ? 2 : 1;
|
||||
const uint8_t conn_count = connection_info & 0x3F;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
__BEGIN_DECLS
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define I_ATMARK 1
|
||||
#define I_CANPUT 2
|
||||
#define I_CKBAND 3
|
||||
|
|
@ -50,6 +52,13 @@ struct winsize
|
|||
#define TIOCGWINSZ 50
|
||||
#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_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 */
|
||||
|
|
@ -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_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_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_SET_LEDS 81 /* set controller leds to uint8_t bitmap */
|
||||
|
|
|
|||
Loading…
Reference in New Issue