Kernel: Implement HD audio driver
This is very basic and does not support a lot of stuff (like changing the output pin :D)
This commit is contained in:
parent
e926beba5a
commit
da6794c8ce
|
|
@ -9,6 +9,8 @@ set(KERNEL_SOURCES
|
|||
kernel/APIC.cpp
|
||||
kernel/Audio/AC97/Controller.cpp
|
||||
kernel/Audio/Controller.cpp
|
||||
kernel/Audio/HDAudio/AudioFunctionGroup.cpp
|
||||
kernel/Audio/HDAudio/Controller.cpp
|
||||
kernel/BootInfo.cpp
|
||||
kernel/CPUID.cpp
|
||||
kernel/Credentials.cpp
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Kernel
|
|||
class AC97AudioController : public AudioController, public Interruptable
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<AC97AudioController>> create(PCI::Device& pci_device);
|
||||
static BAN::ErrorOr<void> create(PCI::Device& pci_device);
|
||||
|
||||
void handle_irq() override;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace Kernel
|
|||
class AudioController : public CharacterDevice
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<AudioController>> create(PCI::Device& pci_device);
|
||||
static BAN::ErrorOr<void> create(PCI::Device& pci_device);
|
||||
|
||||
dev_t rdev() const override { return m_rdev; }
|
||||
BAN::StringView name() const override { return m_name; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <kernel/Audio/Controller.h>
|
||||
#include <kernel/Audio/HDAudio/Controller.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
class HDAudioController;
|
||||
|
||||
class HDAudioFunctionGroup : public AudioController
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<BAN::RefPtr<HDAudioFunctionGroup>> create(BAN::RefPtr<HDAudioController>, uint8_t cid, HDAudio::AFGNode&&);
|
||||
|
||||
void on_stream_interrupt(uint8_t stream_index);
|
||||
|
||||
protected:
|
||||
// FIXME: allow setting these :D
|
||||
uint32_t get_channels() const override { return 2; }
|
||||
uint32_t get_sample_rate() const override { return 48000; }
|
||||
|
||||
void handle_new_data() override;
|
||||
|
||||
private:
|
||||
HDAudioFunctionGroup(BAN::RefPtr<HDAudioController> controller, uint8_t cid, HDAudio::AFGNode&& afg_node)
|
||||
: m_controller(controller)
|
||||
, m_afg_node(BAN::move(afg_node))
|
||||
, m_cid(cid)
|
||||
{ }
|
||||
~HDAudioFunctionGroup();
|
||||
|
||||
BAN::ErrorOr<void> initialize();
|
||||
BAN::ErrorOr<void> initialize_stream();
|
||||
BAN::ErrorOr<void> initialize_output();
|
||||
BAN::ErrorOr<void> enable_output_path(uint8_t index);
|
||||
|
||||
void reset_stream();
|
||||
|
||||
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;
|
||||
|
||||
void queue_bdl_data();
|
||||
|
||||
private:
|
||||
static constexpr size_t m_max_path_length = 16;
|
||||
|
||||
// use 6 512 sample BDL entries
|
||||
// each entry is ~10.7 ms at 48 kHz
|
||||
// -> total buffered audio is 64 ms
|
||||
static constexpr size_t m_bdl_entry_sample_frames = 512;
|
||||
static constexpr size_t m_bdl_entry_count = 6;
|
||||
|
||||
BAN::RefPtr<HDAudioController> m_controller;
|
||||
const HDAudio::AFGNode m_afg_node;
|
||||
const uint8_t m_cid;
|
||||
|
||||
BAN::Vector<BAN::Vector<const HDAudio::AFGWidget*>> m_output_paths;
|
||||
size_t m_output_path_index { SIZE_MAX };
|
||||
|
||||
uint8_t m_stream_id { 0xFF };
|
||||
uint8_t m_stream_index { 0xFF };
|
||||
BAN::UniqPtr<DMARegion> m_bdl_region;
|
||||
|
||||
size_t m_bdl_head { 0 };
|
||||
size_t m_bdl_tail { 0 };
|
||||
bool m_stream_running { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <kernel/Audio/Controller.h>
|
||||
#include <kernel/Audio/HDAudio/Definitions.h>
|
||||
#include <kernel/Memory/DMARegion.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
class HDAudioController : public Interruptable, public BAN::RefCounted<HDAudioController>
|
||||
{
|
||||
public:
|
||||
static BAN::ErrorOr<void> create(PCI::Device& pci_device);
|
||||
|
||||
BAN::ErrorOr<uint32_t> send_command(HDAudio::CORBEntry);
|
||||
|
||||
uint8_t get_stream_index(HDAudio::StreamType type, uint8_t index) const;
|
||||
|
||||
BAN::ErrorOr<uint8_t> allocate_stream_id();
|
||||
void deallocate_stream_id(uint8_t id);
|
||||
|
||||
BAN::ErrorOr<uint8_t> allocate_stream(HDAudio::StreamType type, void* afg);
|
||||
void deallocate_stream(uint8_t index);
|
||||
|
||||
PCI::BarRegion& bar0() { return *m_bar0; }
|
||||
|
||||
bool is_64bit() const { return m_is64bit; }
|
||||
|
||||
void handle_irq() override;
|
||||
|
||||
private:
|
||||
HDAudioController(PCI::Device& pci_device)
|
||||
: m_pci_device(pci_device)
|
||||
{ }
|
||||
|
||||
BAN::ErrorOr<void> initialize();
|
||||
BAN::ErrorOr<void> initialize_ring_buffers();
|
||||
|
||||
BAN::ErrorOr<void> reset_controller();
|
||||
|
||||
BAN::ErrorOr<HDAudio::Codec> initialize_codec(uint8_t codec);
|
||||
BAN::ErrorOr<HDAudio::AFGNode> initialize_node(uint8_t codec, uint8_t node);
|
||||
BAN::ErrorOr<HDAudio::AFGWidget> initialize_widget(uint8_t codec, uint8_t node);
|
||||
|
||||
private:
|
||||
struct RingBuffer
|
||||
{
|
||||
vaddr_t vaddr;
|
||||
uint32_t index;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
private:
|
||||
PCI::Device& m_pci_device;
|
||||
BAN::UniqPtr<PCI::BarRegion> m_bar0;
|
||||
bool m_is64bit { false };
|
||||
|
||||
bool m_use_immediate_command { false };
|
||||
|
||||
uint8_t m_output_streams { 0 };
|
||||
uint8_t m_input_streams { 0 };
|
||||
uint8_t m_bidir_streams { 0 };
|
||||
void* m_allocated_streams[30] {};
|
||||
|
||||
// NOTE: stream ids are from 1 to 15
|
||||
uint16_t m_allocated_stream_ids { 0 };
|
||||
|
||||
Mutex m_command_mutex;
|
||||
SpinLock m_rb_lock;
|
||||
ThreadBlocker m_rb_blocker;
|
||||
|
||||
RingBuffer m_corb;
|
||||
RingBuffer m_rirb;
|
||||
BAN::UniqPtr<DMARegion> m_ring_buffer_region;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
#include <BAN/Vector.h>
|
||||
|
||||
namespace Kernel::HDAudio
|
||||
{
|
||||
|
||||
struct CORBEntry
|
||||
{
|
||||
union {
|
||||
struct {
|
||||
uint32_t data : 8;
|
||||
uint32_t command : 12;
|
||||
uint32_t node_index : 8;
|
||||
uint32_t codec_address : 4;
|
||||
};
|
||||
uint32_t raw;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(CORBEntry) == sizeof(uint32_t));
|
||||
|
||||
struct BDLEntry
|
||||
{
|
||||
paddr_t address;
|
||||
uint32_t length;
|
||||
uint32_t ioc;
|
||||
};
|
||||
static_assert(sizeof(BDLEntry) == 16);
|
||||
|
||||
struct AFGWidget
|
||||
{
|
||||
enum class Type
|
||||
{
|
||||
OutputConverter,
|
||||
InputConverter,
|
||||
Mixer,
|
||||
Selector,
|
||||
PinComplex,
|
||||
Power,
|
||||
VolumeKnob,
|
||||
BeepGenerator,
|
||||
};
|
||||
|
||||
Type type;
|
||||
uint8_t id;
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
bool input;
|
||||
bool output;
|
||||
} pin_complex;
|
||||
};
|
||||
|
||||
BAN::Vector<uint16_t> connections;
|
||||
};
|
||||
|
||||
struct AFGNode
|
||||
{
|
||||
uint8_t id;
|
||||
BAN::Vector<AFGWidget> widgets;
|
||||
};
|
||||
|
||||
struct Codec
|
||||
{
|
||||
uint8_t id;
|
||||
BAN::Vector<AFGNode> nodes;
|
||||
};
|
||||
|
||||
enum class StreamType
|
||||
{
|
||||
Input,
|
||||
Output,
|
||||
Bidirectional,
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace Kernel::HDAudio
|
||||
{
|
||||
|
||||
enum Regs : uint8_t
|
||||
{
|
||||
GCAP = 0x00,
|
||||
VMIN = 0x02,
|
||||
VMAJ = 0x03,
|
||||
GCTL = 0x08,
|
||||
|
||||
INTCTL = 0x20,
|
||||
INTSTS = 0x24,
|
||||
|
||||
CORBLBASE = 0x40,
|
||||
CORBUBASE = 0x44,
|
||||
CORBWP = 0x48,
|
||||
CORBRP = 0x4A,
|
||||
CORBCTL = 0x4C,
|
||||
CORBSTS = 0x4D,
|
||||
CORBSIZE = 0x4E,
|
||||
|
||||
RIRBLBASE = 0x50,
|
||||
RIRBUBASE = 0x54,
|
||||
RIRBWP = 0x58,
|
||||
RINTCNT = 0x5A,
|
||||
RIRBCTL = 0x5C,
|
||||
RIRBSTS = 0x5D,
|
||||
RIRBSIZE = 0x5E,
|
||||
|
||||
ICOI = 0x60,
|
||||
ICII = 0x64,
|
||||
ICIS = 0x68,
|
||||
|
||||
SDCTL = 0x00,
|
||||
SDSTS = 0x03,
|
||||
SDLPIB = 0x04,
|
||||
SDCBL = 0x08,
|
||||
SDLVI = 0x0C,
|
||||
SDFIFOD = 0x10,
|
||||
SDFMT = 0x12,
|
||||
SDBDPL = 0x18,
|
||||
SDBDPU = 0x1C,
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -70,6 +70,8 @@
|
|||
#define DEBUG_USB_MOUSE 0
|
||||
#define DEBUG_USB_MASS_STORAGE 0
|
||||
|
||||
#define DEBUG_HDAUDIO 0
|
||||
|
||||
|
||||
namespace Debug
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <kernel/Audio/AC97/Controller.h>
|
||||
#include <kernel/Audio/AC97/Definitions.h>
|
||||
#include <kernel/FS/DevFS/FileSystem.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
|
@ -105,14 +106,14 @@ namespace Kernel
|
|||
IOCE = 1 << 4,
|
||||
};
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<AC97AudioController>> AC97AudioController::create(PCI::Device& pci_device)
|
||||
BAN::ErrorOr<void> AC97AudioController::create(PCI::Device& pci_device)
|
||||
{
|
||||
auto* ac97_ptr = new AC97AudioController(pci_device);
|
||||
if (ac97_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto ac97 = BAN::RefPtr<AC97AudioController>::adopt(ac97_ptr);
|
||||
TRY(ac97->initialize());
|
||||
return ac97;
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> AC97AudioController::initialize()
|
||||
|
|
@ -147,6 +148,8 @@ namespace Kernel
|
|||
// disable transfer, enable all interrupts
|
||||
m_bus_master->write8(BusMasterRegister::PO_CR, IOCE | FEIE | LVBIE);
|
||||
|
||||
DevFileSystem::get().add_device(this);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <kernel/Audio/AC97/Controller.h>
|
||||
#include <kernel/Audio/Controller.h>
|
||||
#include <kernel/Audio/HDAudio/Controller.h>
|
||||
#include <kernel/Device/DeviceNumbers.h>
|
||||
#include <kernel/FS/DevFS/FileSystem.h>
|
||||
#include <kernel/Lock/SpinLockAsMutex.h>
|
||||
|
|
@ -20,23 +21,26 @@ namespace Kernel
|
|||
BAN::Formatter::print([&ptr](char c) { *ptr++ = c; }, "audio{}", minor(m_rdev));
|
||||
}
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<AudioController>> AudioController::create(PCI::Device& pci_device)
|
||||
BAN::ErrorOr<void> AudioController::create(PCI::Device& pci_device)
|
||||
{
|
||||
switch (pci_device.subclass())
|
||||
{
|
||||
case 0x01:
|
||||
// We should confirm that the card is actually AC97 but I'm trusting osdev wiki on this one
|
||||
// > you can probably expect that every sound card with subclass 0x01 is sound card compatibile with AC97
|
||||
if (auto ret = AC97AudioController::create(pci_device); !ret.is_error())
|
||||
{
|
||||
DevFileSystem::get().add_device(ret.value());
|
||||
return BAN::RefPtr<AudioController>(ret.release_value());
|
||||
}
|
||||
else
|
||||
if (auto ret = AC97AudioController::create(pci_device); ret.is_error())
|
||||
{
|
||||
dwarnln("Failed to initialize AC97: {}", ret.error());
|
||||
return ret.release_error();
|
||||
}
|
||||
break;
|
||||
case 0x03:
|
||||
if (auto ret = HDAudioController::create(pci_device); ret.is_error())
|
||||
{
|
||||
dwarnln("Failed to initialize Intel HDA: {}", ret.error());
|
||||
return ret.release_error();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dprintln("Unsupported Sound card (PCI {2H}:{2H}:{2H})",
|
||||
pci_device.class_code(),
|
||||
|
|
@ -45,8 +49,9 @@ namespace Kernel
|
|||
);
|
||||
return BAN::Error::from_errno(ENOTSUP);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<size_t> AudioController::write_impl(off_t, BAN::ConstByteSpan buffer)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,456 @@
|
|||
#include <kernel/Audio/HDAudio/AudioFunctionGroup.h>
|
||||
#include <kernel/Audio/HDAudio/Registers.h>
|
||||
#include <kernel/FS/DevFS/FileSystem.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
BAN::ErrorOr<BAN::RefPtr<HDAudioFunctionGroup>> HDAudioFunctionGroup::create(BAN::RefPtr<HDAudioController> controller, uint8_t cid, HDAudio::AFGNode&& afg_node)
|
||||
{
|
||||
auto* audio_group_ptr = new HDAudioFunctionGroup(controller, cid, BAN::move(afg_node));
|
||||
if (audio_group_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto audio_group = BAN::RefPtr<HDAudioFunctionGroup>::adopt(audio_group_ptr);
|
||||
TRY(audio_group->initialize());
|
||||
return audio_group;
|
||||
}
|
||||
|
||||
HDAudioFunctionGroup::~HDAudioFunctionGroup()
|
||||
{
|
||||
if (m_stream_id != 0xFF)
|
||||
m_controller->deallocate_stream_id(m_stream_id);
|
||||
if (m_stream_index != 0xFF)
|
||||
m_controller->deallocate_stream(m_stream_index);
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> HDAudioFunctionGroup::initialize()
|
||||
{
|
||||
if constexpr(DEBUG_HDAUDIO)
|
||||
{
|
||||
const auto widget_to_string =
|
||||
[](HDAudio::AFGWidget::Type type) -> const char*
|
||||
{
|
||||
using HDAudio::AFGWidget;
|
||||
switch (type)
|
||||
{
|
||||
case AFGWidget::Type::OutputConverter: return "DAC";
|
||||
case AFGWidget::Type::InputConverter: return "ADC";
|
||||
case AFGWidget::Type::Mixer: return "Mixer";
|
||||
case AFGWidget::Type::Selector: return "Selector";
|
||||
case AFGWidget::Type::PinComplex: return "Pin";
|
||||
case AFGWidget::Type::Power: return "Power";
|
||||
case AFGWidget::Type::VolumeKnob: return "VolumeKnob";
|
||||
case AFGWidget::Type::BeepGenerator: return "BeepGenerator";
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
};
|
||||
|
||||
dprintln("AFG {}", m_afg_node.id);
|
||||
for (auto widget : m_afg_node.widgets)
|
||||
{
|
||||
if (widget.type == HDAudio::AFGWidget::Type::PinComplex)
|
||||
{
|
||||
const uint32_t config = TRY(m_controller->send_command({
|
||||
.data = 0x00,
|
||||
.command = 0xF1C,
|
||||
.node_index = widget.id,
|
||||
.codec_address = m_cid,
|
||||
}));
|
||||
|
||||
dprintln(" widget {}: {} ({}, {}), {32b}",
|
||||
widget.id,
|
||||
widget_to_string(widget.type),
|
||||
(int)widget.pin_complex.output,
|
||||
(int)widget.pin_complex.input,
|
||||
config
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
dprintln(" widget {}: {}",
|
||||
widget.id,
|
||||
widget_to_string(widget.type)
|
||||
);
|
||||
}
|
||||
|
||||
if (!widget.connections.empty())
|
||||
dprintln(" connections {}", widget.connections);
|
||||
}
|
||||
}
|
||||
|
||||
TRY(initialize_stream());
|
||||
TRY(initialize_output());
|
||||
DevFileSystem::get().add_device(this);
|
||||
return {};
|
||||
}
|
||||
|
||||
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_total_size = bdl_entry_bytes * m_bdl_entry_count;
|
||||
if (auto rem = bdl_total_size % 128)
|
||||
return bdl_total_size + (128 - rem);
|
||||
return bdl_total_size;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> HDAudioFunctionGroup::initialize_stream()
|
||||
{
|
||||
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
|
||||
const size_t bdl_list_size = m_bdl_entry_count * sizeof(HDAudio::BDLEntry);
|
||||
|
||||
m_bdl_region = TRY(DMARegion::create(bdl_offset() + bdl_list_size));
|
||||
if (!m_controller->is_64bit() && (m_bdl_region->paddr() >> 32))
|
||||
{
|
||||
dwarnln("no 64 bit support but allocated bdl has 64 bit address :(");
|
||||
return BAN::Error::from_errno(ENOTSUP);
|
||||
}
|
||||
|
||||
auto* bdl = reinterpret_cast<volatile HDAudio::BDLEntry*>(m_bdl_region->vaddr() + bdl_offset());
|
||||
for (size_t i = 0; i < m_bdl_entry_count; i++)
|
||||
{
|
||||
bdl[i].address = m_bdl_region->paddr() + i * bdl_entry_bytes;
|
||||
bdl[i].length = bdl_entry_bytes;
|
||||
bdl[i].ioc = 1;
|
||||
}
|
||||
|
||||
ASSERT(m_stream_id == 0xFF);
|
||||
m_stream_id = TRY(m_controller->allocate_stream_id());
|
||||
|
||||
ASSERT(m_stream_index == 0xFF);
|
||||
m_stream_index = TRY(m_controller->allocate_stream(HDAudio::StreamType::Output, this));
|
||||
|
||||
reset_stream();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void HDAudioFunctionGroup::reset_stream()
|
||||
{
|
||||
using Regs = HDAudio::Regs;
|
||||
|
||||
auto& bar = m_controller->bar0();
|
||||
const auto base = 0x80 + m_stream_index * 0x20;
|
||||
|
||||
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
|
||||
|
||||
// stop stream
|
||||
bar.write8(base + Regs::SDCTL, bar.read8(base + Regs::SDCTL) & 0xFD);
|
||||
|
||||
// reset stream
|
||||
bar.write8(base + Regs::SDCTL, (bar.read8(base + Regs::SDCTL) & 0xFE) | 1);
|
||||
while (!(bar.read8(base + Regs::SDCTL) & 1))
|
||||
Processor::pause();
|
||||
bar.write8(base + Regs::SDCTL, (bar.read8(base + Regs::SDCTL) & 0xFE));
|
||||
while ((bar.read8(base + Regs::SDCTL) & 1))
|
||||
Processor::pause();
|
||||
|
||||
// set bdl address, total size and lvi
|
||||
const paddr_t bdl_paddr = m_bdl_region->paddr() + bdl_offset();
|
||||
bar.write32(base + Regs::SDBDPL, bdl_paddr);
|
||||
if (m_controller->is_64bit())
|
||||
bar.write32(base + Regs::SDBDPU, bdl_paddr >> 32);
|
||||
bar.write32(base + Regs::SDCBL, bdl_entry_bytes * m_bdl_entry_count);
|
||||
bar.write16(base + Regs::SDLVI, (bar.read16(base + Regs::SDLVI) & 0xFF00) | (m_bdl_entry_count - 1));
|
||||
|
||||
// set stream format
|
||||
bar.write16(base + Regs::SDFMT, get_format_data());
|
||||
|
||||
// set stream id, not bidirectional
|
||||
bar.write8(base + Regs::SDCTL + 2, (bar.read8(base + Regs::SDCTL + 2) & 0x07) | (m_stream_id << 4));
|
||||
|
||||
m_bdl_head = 0;
|
||||
m_bdl_tail = 0;
|
||||
m_stream_running = false;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> HDAudioFunctionGroup::initialize_output()
|
||||
{
|
||||
BAN::Vector<const HDAudio::AFGWidget*> path;
|
||||
TRY(path.reserve(m_max_path_length));
|
||||
|
||||
for (const auto& widget : m_afg_node.widgets)
|
||||
{
|
||||
if (widget.type != HDAudio::AFGWidget::Type::PinComplex || !widget.pin_complex.output)
|
||||
continue;
|
||||
TRY(path.push_back(&widget));
|
||||
TRY(recurse_output_paths(widget, path));
|
||||
path.pop_back();
|
||||
}
|
||||
|
||||
dprintln_if(DEBUG_HDAUDIO, "found {} paths from output to DAC", m_output_paths.size());
|
||||
|
||||
// select first supported path
|
||||
// FIXME: prefer associations
|
||||
// FIXME: does this pin even have a device?
|
||||
auto result = BAN::Error::from_errno(ENODEV);
|
||||
for (size_t i = 0; i < m_output_paths.size(); i++)
|
||||
{
|
||||
if (auto ret = enable_output_path(i); ret.is_error())
|
||||
{
|
||||
if (ret.error().get_error_code() != ENOTSUP)
|
||||
return ret.release_error();
|
||||
dwarnln("path {} not supported", i);
|
||||
result = BAN::Error::from_errno(ENOTSUP);
|
||||
continue;
|
||||
}
|
||||
|
||||
m_output_path_index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_output_path_index >= m_output_paths.size())
|
||||
{
|
||||
dwarnln("could not find any usable output path");
|
||||
return result;
|
||||
}
|
||||
|
||||
dprintln_if(DEBUG_HDAUDIO, "routed output path");
|
||||
for (const auto* widget : m_output_paths[m_output_path_index])
|
||||
dprintln_if(DEBUG_HDAUDIO, " {}", widget->id);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
uint16_t HDAudioFunctionGroup::get_format_data() const
|
||||
{
|
||||
// TODO: don't hardcode this
|
||||
// format: PCM, 48 kHz, 16 bit, 2 channels
|
||||
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());
|
||||
const auto& path = m_output_paths[index];
|
||||
|
||||
for (const auto* widget : path)
|
||||
{
|
||||
switch (widget->type)
|
||||
{
|
||||
using HDAudio::AFGWidget;
|
||||
case AFGWidget::Type::OutputConverter:
|
||||
case AFGWidget::Type::PinComplex:
|
||||
break;
|
||||
default:
|
||||
dwarnln("FIXME: support enabling widget type {}", static_cast<int>(widget->type));
|
||||
return BAN::Error::from_errno(ENOTSUP);
|
||||
}
|
||||
}
|
||||
|
||||
const auto format = get_format_data();
|
||||
const auto volume = get_volume_data();
|
||||
|
||||
for (size_t i = 0; i < path.size(); i++)
|
||||
{
|
||||
// set power state D0
|
||||
TRY(m_controller->send_command({
|
||||
.data = 0x00,
|
||||
.command = 0x705,
|
||||
.node_index = path[i]->id,
|
||||
.codec_address = m_cid,
|
||||
}));
|
||||
|
||||
// set connection index
|
||||
if (i + 1 < path.size() && path[i]->connections.size() > 1)
|
||||
{
|
||||
uint8_t index = 0;
|
||||
for (; index < path[i]->connections.size(); index++)
|
||||
if (path[i]->connections[index] == path[i + 1]->id)
|
||||
break;
|
||||
ASSERT(index < path[i]->connections.size());
|
||||
|
||||
TRY(m_controller->send_command({
|
||||
.data = index,
|
||||
.command = 0x701,
|
||||
.node_index = path[i]->id,
|
||||
.codec_address = m_cid,
|
||||
}));
|
||||
}
|
||||
|
||||
// set volume
|
||||
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)
|
||||
{
|
||||
using HDAudio::AFGWidget;
|
||||
|
||||
case AFGWidget::Type::OutputConverter:
|
||||
// set stream and channel 0
|
||||
TRY(m_controller->send_command({
|
||||
.data = static_cast<uint8_t>(m_stream_id << 4),
|
||||
.command = 0x706,
|
||||
.node_index = path[i]->id,
|
||||
.codec_address = m_cid,
|
||||
}));
|
||||
// set format
|
||||
TRY(m_controller->send_command({
|
||||
.data = static_cast<uint8_t>(format & 0xFF),
|
||||
.command = static_cast<uint16_t>(0x200 | (format >> 8)),
|
||||
.node_index = path[i]->id,
|
||||
.codec_address = m_cid,
|
||||
}));
|
||||
break;
|
||||
|
||||
case AFGWidget::Type::PinComplex:
|
||||
// enable output and H-Phn
|
||||
TRY(m_controller->send_command({
|
||||
.data = 0x80 | 0x40,
|
||||
.command = 0x707,
|
||||
.node_index = path[i]->id,
|
||||
.codec_address = m_cid,
|
||||
}));
|
||||
// enable EAPD
|
||||
TRY(m_controller->send_command({
|
||||
.data = 0x02,
|
||||
.command = 0x70C,
|
||||
.node_index = path[i]->id,
|
||||
.codec_address = m_cid,
|
||||
}));
|
||||
break;
|
||||
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> HDAudioFunctionGroup::recurse_output_paths(const HDAudio::AFGWidget& widget, BAN::Vector<const HDAudio::AFGWidget*>& path)
|
||||
{
|
||||
// cycle "detection"
|
||||
if (path.size() >= m_max_path_length)
|
||||
return {};
|
||||
|
||||
// we've reached a DAC
|
||||
if (widget.type == HDAudio::AFGWidget::Type::OutputConverter)
|
||||
{
|
||||
BAN::Vector<const HDAudio::AFGWidget*> path_copy;
|
||||
TRY(path_copy.resize(path.size()));
|
||||
for (size_t i = 0; i < path.size(); i++)
|
||||
path_copy[i] = path[i];
|
||||
TRY(m_output_paths.push_back(BAN::move(path_copy)));
|
||||
return {};
|
||||
}
|
||||
|
||||
// check all connections
|
||||
for (const auto& connection : m_afg_node.widgets)
|
||||
{
|
||||
if (!widget.connections.contains(connection.id))
|
||||
continue;
|
||||
TRY(path.push_back(&connection));
|
||||
TRY(recurse_output_paths(connection, path));
|
||||
path.pop_back();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void HDAudioFunctionGroup::handle_new_data()
|
||||
{
|
||||
queue_bdl_data();
|
||||
}
|
||||
|
||||
void HDAudioFunctionGroup::queue_bdl_data()
|
||||
{
|
||||
ASSERT(m_spinlock.current_processor_has_lock());
|
||||
|
||||
const size_t bdl_entry_bytes = m_bdl_entry_sample_frames * get_channels() * sizeof(uint16_t);
|
||||
|
||||
while ((m_bdl_head + 1) % m_bdl_entry_count != m_bdl_tail)
|
||||
{
|
||||
const size_t sample_data_tail = (m_sample_data_head + m_sample_data_capacity - m_sample_data_size) % m_sample_data_capacity;
|
||||
|
||||
const size_t sample_frames = BAN::Math::min(m_sample_data_size / get_channels() / sizeof(uint16_t), m_bdl_entry_sample_frames);
|
||||
if (sample_frames == 0)
|
||||
break;
|
||||
|
||||
const size_t copy_total_bytes = sample_frames * get_channels() * sizeof(uint16_t);
|
||||
const size_t copy_before_wrap = BAN::Math::min(copy_total_bytes, m_sample_data_capacity - sample_data_tail);
|
||||
|
||||
memcpy(
|
||||
reinterpret_cast<void*>(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes),
|
||||
&m_sample_data[sample_data_tail],
|
||||
copy_before_wrap
|
||||
);
|
||||
|
||||
if (copy_before_wrap < copy_total_bytes)
|
||||
{
|
||||
memcpy(
|
||||
reinterpret_cast<void*>(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes + copy_before_wrap),
|
||||
&m_sample_data[0],
|
||||
copy_total_bytes - copy_before_wrap
|
||||
);
|
||||
}
|
||||
|
||||
if (copy_total_bytes < bdl_entry_bytes)
|
||||
{
|
||||
memset(
|
||||
reinterpret_cast<void*>(m_bdl_region->vaddr() + m_bdl_head * bdl_entry_bytes + copy_total_bytes),
|
||||
0x00,
|
||||
bdl_entry_bytes - copy_total_bytes
|
||||
);
|
||||
}
|
||||
|
||||
m_sample_data_size -= copy_total_bytes;
|
||||
|
||||
m_bdl_head = (m_bdl_head + 1) % m_bdl_entry_count;
|
||||
}
|
||||
|
||||
if (m_bdl_head == m_bdl_tail || m_stream_running)
|
||||
return;
|
||||
|
||||
// start the stream and enable IOC and descriptor error interrupts
|
||||
auto& bar = m_controller->bar0();
|
||||
const auto base = 0x80 + m_stream_index * 0x20;
|
||||
bar.write8(base + HDAudio::Regs::SDCTL, bar.read8(base + HDAudio::Regs::SDCTL) | 0x16);
|
||||
|
||||
m_stream_running = true;
|
||||
}
|
||||
|
||||
void HDAudioFunctionGroup::on_stream_interrupt(uint8_t stream_index)
|
||||
{
|
||||
using Regs = HDAudio::Regs;
|
||||
|
||||
ASSERT(stream_index == m_stream_index);
|
||||
|
||||
auto& bar = m_controller->bar0();
|
||||
const uint16_t base = 0x80 + stream_index * 0x20;
|
||||
|
||||
const uint8_t sts = bar.read8(base + Regs::SDSTS);
|
||||
bar.write8(base + Regs::SDSTS, sts & 0x3C);
|
||||
|
||||
if (sts & (1 << 4))
|
||||
derrorln("descriptor error");
|
||||
|
||||
// ignore fifo errors as they are too common on real hw :D
|
||||
//if (sts & (1 << 3))
|
||||
// derrorln("fifo error");
|
||||
|
||||
if (sts & (1 << 2))
|
||||
{
|
||||
SpinLockGuard _(m_spinlock);
|
||||
|
||||
ASSERT(m_stream_running);
|
||||
|
||||
m_bdl_tail = (m_bdl_tail + 1) % m_bdl_entry_count;
|
||||
if (m_bdl_tail == m_bdl_head)
|
||||
reset_stream();
|
||||
|
||||
queue_bdl_data();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,492 @@
|
|||
#include <kernel/Audio/HDAudio/AudioFunctionGroup.h>
|
||||
#include <kernel/Audio/HDAudio/Controller.h>
|
||||
#include <kernel/Audio/HDAudio/Registers.h>
|
||||
#include <kernel/Lock/LockGuard.h>
|
||||
#include <kernel/Lock/SpinLockAsMutex.h>
|
||||
#include <kernel/MMIO.h>
|
||||
#include <kernel/Timer/Timer.h>
|
||||
|
||||
namespace Kernel
|
||||
{
|
||||
|
||||
BAN::ErrorOr<void> HDAudioController::create(PCI::Device& pci_device)
|
||||
{
|
||||
auto intel_hda_ptr = new HDAudioController(pci_device);
|
||||
if (intel_hda_ptr == nullptr)
|
||||
return BAN::Error::from_errno(ENOMEM);
|
||||
auto intel_hda = BAN::RefPtr<HDAudioController>::adopt(intel_hda_ptr);
|
||||
TRY(intel_hda->initialize());
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> HDAudioController::initialize()
|
||||
{
|
||||
using Regs = HDAudio::Regs;
|
||||
|
||||
m_pci_device.enable_bus_mastering();
|
||||
|
||||
m_bar0 = TRY(m_pci_device.allocate_bar_region(0));
|
||||
|
||||
dprintln("HD audio");
|
||||
dprintln(" revision {}.{}",
|
||||
m_bar0->read8(Regs::VMAJ),
|
||||
m_bar0->read8(Regs::VMIN)
|
||||
);
|
||||
|
||||
const uint16_t global_cap = m_bar0->read16(Regs::GCAP);
|
||||
m_output_streams = (global_cap >> 12) & 0x0F;
|
||||
m_input_streams = (global_cap >> 8) & 0x0F;
|
||||
m_bidir_streams = (global_cap >> 3) & 0x1F;
|
||||
m_is64bit = (global_cap & 1);
|
||||
|
||||
if (m_output_streams + m_input_streams + m_bidir_streams > 30)
|
||||
{
|
||||
dwarnln("HD audio controller has {} streams, 30 is the maximum valid count",
|
||||
m_output_streams + m_input_streams + m_bidir_streams
|
||||
);
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
}
|
||||
|
||||
dprintln(" output streams: {}", m_output_streams);
|
||||
dprintln(" input streams: {}", m_input_streams);
|
||||
dprintln(" bidir streams: {}", m_bidir_streams);
|
||||
dprintln(" 64 bit support: {}", m_is64bit);
|
||||
|
||||
TRY(reset_controller());
|
||||
|
||||
if (auto ret = initialize_ring_buffers(); ret.is_error())
|
||||
{
|
||||
if (ret.error().get_error_code() != ETIMEDOUT)
|
||||
return ret.release_error();
|
||||
m_use_immediate_command = true;
|
||||
}
|
||||
|
||||
TRY(m_pci_device.reserve_interrupts(1));
|
||||
m_pci_device.enable_interrupt(0, *this);
|
||||
m_bar0->write32(Regs::INTCTL, UINT32_MAX);
|
||||
|
||||
for (uint8_t codec_id = 0; codec_id < 0x10; codec_id++)
|
||||
{
|
||||
auto codec_or_error = initialize_codec(codec_id);
|
||||
if (codec_or_error.is_error())
|
||||
continue;
|
||||
|
||||
auto codec = codec_or_error.release_value();
|
||||
for (auto& node : codec.nodes)
|
||||
if (auto ret = HDAudioFunctionGroup::create(this, codec.id, BAN::move(node)); ret.is_error())
|
||||
dwarnln("Failed to initialize AFG: {}", ret.error());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> HDAudioController::reset_controller()
|
||||
{
|
||||
using HDAudio::Regs;
|
||||
|
||||
const auto timeout_ms = SystemTimer::get().ms_since_boot() + 100;
|
||||
|
||||
// transition into reset state
|
||||
if (const uint32_t gcap = m_bar0->read32(Regs::GCTL); gcap & 1)
|
||||
{
|
||||
m_bar0->write32(Regs::GCTL, gcap & 0xFFFFFEFC);
|
||||
while (m_bar0->read32(Regs::GCTL) & 1)
|
||||
{
|
||||
if (SystemTimer::get().ms_since_boot() > timeout_ms)
|
||||
return BAN::Error::from_errno(ETIMEDOUT);
|
||||
Processor::pause();
|
||||
}
|
||||
}
|
||||
|
||||
m_bar0->write32(Regs::GCTL, (m_bar0->read32(Regs::GCTL) & 0xFFFFFEFC) | 1);
|
||||
while (!(m_bar0->read32(Regs::GCTL) & 1))
|
||||
{
|
||||
if (SystemTimer::get().ms_since_boot() > timeout_ms)
|
||||
return BAN::Error::from_errno(ETIMEDOUT);
|
||||
Processor::pause();
|
||||
}
|
||||
|
||||
// 4.3 The software must wait at least 521 us (25 frames) after reading CRST as a 1
|
||||
// before assuming that codecs have all made status change requests and have been
|
||||
// registered by the controller
|
||||
SystemTimer::get().sleep_ms(1);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<void> HDAudioController::initialize_ring_buffers()
|
||||
{
|
||||
using Regs = HDAudio::Regs;
|
||||
|
||||
// CORB: at most 1024 bytes (256 * uint32_t)
|
||||
// RIRB: at most 2048 bytes (256 * uint32_t * 2)
|
||||
m_ring_buffer_region = TRY(DMARegion::create(3 * 256 * sizeof(uint32_t)));
|
||||
|
||||
struct SizeInfo
|
||||
{
|
||||
uint16_t size;
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
const auto get_size_info =
|
||||
[](uint8_t byte) -> BAN::ErrorOr<SizeInfo>
|
||||
{
|
||||
if (byte & 0x40)
|
||||
return SizeInfo { 256, 2 };
|
||||
if (byte & 0x20)
|
||||
return SizeInfo { 16, 1 };
|
||||
if (byte & 0x10)
|
||||
return SizeInfo { 2, 0 };
|
||||
return BAN::Error::from_errno(EINVAL);
|
||||
};
|
||||
|
||||
const auto corb_size = TRY(get_size_info(m_bar0->read8(Regs::CORBSIZE)));
|
||||
const auto rirb_size = TRY(get_size_info(m_bar0->read8(Regs::RIRBSIZE)));
|
||||
|
||||
m_corb = {
|
||||
.vaddr = m_ring_buffer_region->vaddr(),
|
||||
.index = 1,
|
||||
.size = corb_size.size,
|
||||
};
|
||||
|
||||
m_rirb = {
|
||||
.vaddr = m_ring_buffer_region->vaddr() + 1024,
|
||||
.index = 1,
|
||||
.size = rirb_size.size,
|
||||
};
|
||||
|
||||
const paddr_t corb_paddr = m_ring_buffer_region->paddr();
|
||||
const paddr_t rirb_paddr = m_ring_buffer_region->paddr() + 1024;
|
||||
if (!m_is64bit && ((corb_paddr >> 32) || (rirb_paddr >> 32)))
|
||||
{
|
||||
dwarnln("no 64 bit support but allocated ring buffers have 64 bit addresses :(");
|
||||
return BAN::Error::from_errno(ENOTSUP);
|
||||
}
|
||||
|
||||
// disable corb and rirb
|
||||
m_bar0->write8(Regs::CORBCTL, (m_bar0->read8(Regs::CORBCTL) & 0xFC));
|
||||
m_bar0->write8(Regs::RIRBCTL, (m_bar0->read8(Regs::RIRBCTL) & 0xF8));
|
||||
|
||||
// set base address
|
||||
m_bar0->write32(Regs::CORBLBASE, corb_paddr | (m_bar0->read32(Regs::CORBLBASE) & 0x0000007F));
|
||||
if (m_is64bit)
|
||||
m_bar0->write32(Regs::CORBUBASE, corb_paddr >> 32);
|
||||
// set number of entries
|
||||
m_bar0->write8(Regs::CORBSIZE, (m_bar0->read8(Regs::CORBSIZE) & 0xFC) | corb_size.value);
|
||||
// zero write pointer
|
||||
m_bar0->write16(Regs::CORBWP, (m_bar0->read16(Regs::CORBWP) & 0xFF00));
|
||||
// reset read pointer
|
||||
const uint64_t corb_timeout_ms = SystemTimer::get().ms_since_boot() + 100;
|
||||
m_bar0->write16(Regs::CORBRP, (m_bar0->read16(Regs::CORBRP) & 0x7FFF) | 0x8000);
|
||||
while (!(m_bar0->read16(Regs::CORBRP) & 0x8000))
|
||||
{
|
||||
if (SystemTimer::get().ms_since_boot() > corb_timeout_ms)
|
||||
return BAN::Error::from_errno(ETIMEDOUT);
|
||||
Processor::pause();
|
||||
}
|
||||
m_bar0->write16(Regs::CORBRP, (m_bar0->read16(Regs::CORBRP) & 0x7FFF));
|
||||
while ((m_bar0->read16(Regs::CORBRP) & 0x8000))
|
||||
{
|
||||
if (SystemTimer::get().ms_since_boot() > corb_timeout_ms)
|
||||
return BAN::Error::from_errno(ETIMEDOUT);
|
||||
Processor::pause();
|
||||
}
|
||||
|
||||
// set base address
|
||||
m_bar0->write32(Regs::RIRBLBASE, rirb_paddr | (m_bar0->read32(Regs::RIRBLBASE) & 0x0000007F));
|
||||
if (m_is64bit)
|
||||
m_bar0->write32(Regs::RIRBUBASE, rirb_paddr >> 32);
|
||||
// set number of entries
|
||||
m_bar0->write8(Regs::RIRBSIZE, (m_bar0->read8(Regs::RIRBSIZE) & 0xFC) | rirb_size.value);
|
||||
// reset write pointer
|
||||
m_bar0->write16(Regs::RIRBWP, (m_bar0->read16(Regs::RIRBWP) & 0x7FFF) | 0x8000);
|
||||
// send interrupt on every packet
|
||||
m_bar0->write16(Regs::RINTCNT, (m_bar0->read16(Regs::RINTCNT) & 0xFF00) | 0x01);
|
||||
|
||||
// enable corb and rirb
|
||||
m_bar0->write8(Regs::CORBCTL, (m_bar0->read8(Regs::CORBCTL) & 0xFC) | 3);
|
||||
m_bar0->write8(Regs::RIRBCTL, (m_bar0->read8(Regs::RIRBCTL) & 0xF8) | 7);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
BAN::ErrorOr<HDAudio::Codec> HDAudioController::initialize_codec(uint8_t codec)
|
||||
{
|
||||
const auto resp = TRY(send_command({
|
||||
.data = 0x04,
|
||||
.command = 0xF00,
|
||||
.node_index = 0,
|
||||
.codec_address = codec,
|
||||
}));
|
||||
const uint8_t start = (resp >> 16) & 0xFF;
|
||||
const uint8_t count = (resp >> 0) & 0xFF;
|
||||
if (count == 0)
|
||||
return BAN::Error::from_errno(ENODEV);
|
||||
|
||||
HDAudio::Codec result {};
|
||||
result.id = codec;
|
||||
TRY(result.nodes.reserve(count));
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
if (auto node_or_error = initialize_node(codec, start + i); !node_or_error.is_error())
|
||||
MUST(result.nodes.emplace_back(node_or_error.release_value()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<HDAudio::AFGNode> HDAudioController::initialize_node(uint8_t codec, uint8_t node)
|
||||
{
|
||||
{
|
||||
const auto resp = TRY(send_command({
|
||||
.data = 0x05,
|
||||
.command = 0xF00,
|
||||
.node_index = node,
|
||||
.codec_address = codec,
|
||||
}));
|
||||
const uint8_t type = (resp >> 0) & 0xFF;
|
||||
if (type != 0x01)
|
||||
return BAN::Error::from_errno(ENODEV);
|
||||
}
|
||||
|
||||
const auto resp = TRY(send_command({
|
||||
.data = 0x04,
|
||||
.command = 0xF00,
|
||||
.node_index = node,
|
||||
.codec_address = codec,
|
||||
}));
|
||||
const uint8_t start = (resp >> 16) & 0xFF;
|
||||
const uint8_t count = (resp >> 0) & 0xFF;
|
||||
|
||||
HDAudio::AFGNode result {};
|
||||
result.id = node;
|
||||
TRY(result.widgets.reserve(count));
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
if (auto widget_or_error = initialize_widget(codec, start + i); !widget_or_error.is_error())
|
||||
MUST(result.widgets.emplace_back(widget_or_error.release_value()));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<HDAudio::AFGWidget> HDAudioController::initialize_widget(uint8_t codec, uint8_t widget)
|
||||
{
|
||||
const auto send_command_or_zero =
|
||||
[codec, widget, this](uint16_t cmd, uint8_t data) -> uint32_t
|
||||
{
|
||||
const auto command = HDAudio::CORBEntry {
|
||||
.data = data,
|
||||
.command = cmd,
|
||||
.node_index = widget,
|
||||
.codec_address = codec,
|
||||
};
|
||||
if (auto res = send_command(command); !res.is_error())
|
||||
return res.release_value();
|
||||
return 0;
|
||||
};
|
||||
|
||||
using HDAudio::AFGWidget;
|
||||
const AFGWidget::Type type_list[] {
|
||||
AFGWidget::Type::OutputConverter,
|
||||
AFGWidget::Type::InputConverter,
|
||||
AFGWidget::Type::Mixer,
|
||||
AFGWidget::Type::Selector,
|
||||
AFGWidget::Type::PinComplex,
|
||||
AFGWidget::Type::Power,
|
||||
AFGWidget::Type::VolumeKnob,
|
||||
AFGWidget::Type::BeepGenerator,
|
||||
};
|
||||
|
||||
const uint8_t type = (send_command_or_zero(0xF00, 0x09) >> 20) & 0x0F;
|
||||
if (type > sizeof(type_list) / sizeof(*type_list))
|
||||
return BAN::Error::from_errno(ENOTSUP);
|
||||
|
||||
AFGWidget result {};
|
||||
result.type = type_list[type];
|
||||
result.id = widget;
|
||||
|
||||
if (result.type == AFGWidget::Type::PinComplex)
|
||||
{
|
||||
const uint32_t cap = send_command_or_zero(0xF00, 0x0C);
|
||||
result.pin_complex.output = !!(cap & (1 << 4));
|
||||
result.pin_complex.input = !!(cap & (1 << 5));
|
||||
}
|
||||
|
||||
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;
|
||||
const uint16_t conn_mask = (1 << (8 * conn_width)) - 1;
|
||||
|
||||
TRY(result.connections.resize(conn_count, 0));
|
||||
for (size_t i = 0; i < conn_count; i += 4 / conn_width)
|
||||
{
|
||||
const uint32_t conn = send_command_or_zero(0xF02, i);
|
||||
for (size_t j = 0; j < sizeof(conn) / conn_width && i + j < conn_count; j++)
|
||||
result.connections[i + j] = (conn >> (8 * conn_width * j)) & conn_mask;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<uint32_t> HDAudioController::send_command(HDAudio::CORBEntry command)
|
||||
{
|
||||
using Regs = HDAudio::Regs;
|
||||
|
||||
// TODO: allow concurrent commands with CORB/RIRB
|
||||
LockGuard _(m_command_mutex);
|
||||
|
||||
if (!m_use_immediate_command)
|
||||
{
|
||||
SpinLockGuard sguard(m_rb_lock);
|
||||
|
||||
MMIO::write32(m_corb.vaddr + m_corb.index * sizeof(uint32_t), command.raw);
|
||||
m_bar0->write16(Regs::CORBWP, (m_bar0->read16(Regs::CORBWP) & 0xFF00) | m_corb.index);
|
||||
m_corb.index = (m_corb.index + 1) % m_corb.size;
|
||||
|
||||
const uint64_t waketime_ms = SystemTimer::get().ms_since_boot() + 10;
|
||||
while ((m_bar0->read16(Regs::RIRBWP) & 0xFF) != m_rirb.index)
|
||||
{
|
||||
if (SystemTimer::get().ms_since_boot() > waketime_ms)
|
||||
return BAN::Error::from_errno(ETIMEDOUT);
|
||||
SpinLockGuardAsMutex smutex(sguard);
|
||||
m_rb_blocker.block_with_timeout_ms(10, &smutex);
|
||||
}
|
||||
|
||||
const size_t offset = 2 * m_rirb.index * sizeof(uint32_t);
|
||||
m_rirb.index = (m_rirb.index + 1) % m_rirb.size;
|
||||
return MMIO::read32(m_rirb.vaddr + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t waketime_ms = SystemTimer::get().ms_since_boot() + 10;
|
||||
while (m_bar0->read16(Regs::ICIS) & 1)
|
||||
{
|
||||
if (SystemTimer::get().ms_since_boot() > waketime_ms)
|
||||
break;
|
||||
Processor::pause();
|
||||
}
|
||||
|
||||
// clear ICB if it did not clear "in reasonable timeout period"
|
||||
// and make sure IRV is cleared
|
||||
if (m_bar0->read16(Regs::ICIS) & 3)
|
||||
m_bar0->write16(Regs::ICIS, (m_bar0->read16(Regs::ICIS) & 0x00FC) | 2);
|
||||
|
||||
m_bar0->write32(Regs::ICOI, command.raw);
|
||||
|
||||
m_bar0->write16(Regs::ICIS, (m_bar0->read16(Regs::ICIS) & 0x00FC) | 1);
|
||||
|
||||
waketime_ms = SystemTimer::get().ms_since_boot() + 10;
|
||||
while (!(m_bar0->read16(Regs::ICIS) & 2))
|
||||
{
|
||||
if (SystemTimer::get().ms_since_boot() > waketime_ms)
|
||||
return BAN::Error::from_errno(ETIMEDOUT);
|
||||
Processor::pause();
|
||||
}
|
||||
|
||||
return m_bar0->read32(Regs::ICII);
|
||||
}
|
||||
}
|
||||
|
||||
void HDAudioController::handle_irq()
|
||||
{
|
||||
using Regs = HDAudio::Regs;
|
||||
|
||||
const uint32_t intsts = m_bar0->read32(Regs::INTSTS);
|
||||
if (!(intsts & (1u << 31)))
|
||||
return;
|
||||
|
||||
if (intsts & (1 << 30))
|
||||
{
|
||||
if (const uint8_t rirbsts = m_bar0->read8(Regs::RIRBSTS) & ((1 << 2) | (1 << 0)))
|
||||
{
|
||||
if (rirbsts & (1 << 2))
|
||||
dwarnln("RIRB response overrun");
|
||||
if (rirbsts & (1 << 0))
|
||||
{
|
||||
SpinLockGuard _(m_rb_lock);
|
||||
m_rb_blocker.unblock();
|
||||
}
|
||||
m_bar0->write8(Regs::RIRBSTS, rirbsts);
|
||||
}
|
||||
|
||||
if (const uint8_t corbsts = m_bar0->read8(Regs::CORBSTS) & (1 << 0))
|
||||
{
|
||||
dwarnln("CORB memory error");
|
||||
m_bar0->write8(Regs::CORBSTS, corbsts);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 30; i++)
|
||||
{
|
||||
if (!(intsts & (1 << i)))
|
||||
continue;
|
||||
if (m_allocated_streams[i] == nullptr)
|
||||
dwarnln("interrupt from an unallocated stream??");
|
||||
else
|
||||
static_cast<HDAudioFunctionGroup*>(m_allocated_streams[i])->on_stream_interrupt(i);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t HDAudioController::get_stream_index(HDAudio::StreamType type, uint8_t index) const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case HDAudio::StreamType::Bidirectional:
|
||||
index += m_output_streams;
|
||||
[[fallthrough]];
|
||||
case HDAudio::StreamType::Output:
|
||||
index += m_input_streams;
|
||||
[[fallthrough]];
|
||||
case HDAudio::StreamType::Input:
|
||||
break;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
BAN::ErrorOr<uint8_t> HDAudioController::allocate_stream_id()
|
||||
{
|
||||
for (uint8_t id = 1; id < 16; id++)
|
||||
{
|
||||
if (m_allocated_stream_ids & (1 << id))
|
||||
continue;
|
||||
m_allocated_stream_ids |= 1 << id;
|
||||
return id;
|
||||
}
|
||||
|
||||
return BAN::Error::from_errno(EAGAIN);
|
||||
}
|
||||
|
||||
void HDAudioController::deallocate_stream_id(uint8_t id)
|
||||
{
|
||||
ASSERT(m_allocated_stream_ids & (1 << id));
|
||||
m_allocated_stream_ids &= ~(1 << id);
|
||||
}
|
||||
|
||||
BAN::ErrorOr<uint8_t> HDAudioController::allocate_stream(HDAudio::StreamType type, void* afg)
|
||||
{
|
||||
const uint8_t stream_count_lookup[] {
|
||||
[(int)HDAudio::StreamType::Input] = m_input_streams,
|
||||
[(int)HDAudio::StreamType::Output] = m_output_streams,
|
||||
[(int)HDAudio::StreamType::Bidirectional] = m_bidir_streams,
|
||||
};
|
||||
|
||||
const uint8_t stream_count = stream_count_lookup[static_cast<int>(type)];
|
||||
for (uint8_t i = 0; i < stream_count; i++)
|
||||
{
|
||||
const uint8_t index = get_stream_index(type, i);
|
||||
if (m_allocated_streams[index])
|
||||
continue;
|
||||
m_allocated_streams[index] = afg;
|
||||
return index;
|
||||
}
|
||||
|
||||
return BAN::Error::from_errno(EAGAIN);
|
||||
}
|
||||
|
||||
void HDAudioController::deallocate_stream(uint8_t index)
|
||||
{
|
||||
ASSERT(m_allocated_streams[index]);
|
||||
m_allocated_streams[index] = nullptr;
|
||||
// TODO: maybe make sure the stream is stopped/reset (?)
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue