/* * Copyright (c) 2021-2022, Jelle Raaijmakers * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include namespace Kernel { // See: https://www-inst.eecs.berkeley.edu/~cs150/Documents/ac97_r23.pdf // And: https://www.intel.com/content/dam/doc/manual/io-controller-hub-7-hd-audio-ac97-manual.pdf class AC97 final : public AudioController , public PCI::Device , public IRQHandler { public: static ErrorOr> try_create(PCI::DeviceIdentifier const&); virtual ~AC97() override; // ^IRQHandler virtual StringView purpose() const override { return "AC97"sv; } private: enum NativeAudioMixerRegister : u8 { Reset = 0x00, SetMasterOutputVolume = 0x02, SetPCMOutputVolume = 0x18, ExtendedAudioID = 0x28, ExtendedAudioStatusControl = 0x2a, PCMFrontDACRate = 0x2c, VendorID1 = 0x7c, VendorID2 = 0x7e, }; enum ExtendedAudioMask : u16 { VariableRatePCMAudio = 1 << 0, DoubleRatePCMAudio = 1 << 1, Revision = 3 << 10, }; enum ExtendedAudioStatusControlFlag : u16 { VariableRateAudio = 1 << 0, DoubleRateAudio = 1 << 1, }; enum AC97Revision : u8 { Revision21OrEarlier = 0b00, Revision22 = 0b01, Revision23 = 0b10, Reserved = 0b11, }; enum NativeAudioBusChannel : u8 { PCMInChannel = 0x00, PCMOutChannel = 0x10, MicrophoneInChannel = 0x20, Microphone2Channel = 0x40, PCMIn2Channel = 0x50, SPDIFChannel = 0x60, }; enum NativeAudioBusRegister : u8 { GlobalControl = 0x2c, }; enum AudioStatusRegisterFlag : u16 { DMAControllerHalted = 1 << 0, CurrentEqualsLastValid = 1 << 1, LastValidBufferCompletionInterrupt = 1 << 2, BufferCompletionInterruptStatus = 1 << 3, FIFOError = 1 << 4, }; enum AudioControlRegisterFlag : u8 { RunPauseBusMaster = 1 << 0, ResetRegisters = 1 << 1, FIFOErrorInterruptEnable = 1 << 3, InterruptOnCompletionEnable = 1 << 4, }; enum GlobalControlFlag : u32 { GPIInterruptEnable = 1 << 0, AC97ColdReset = 1 << 1, }; enum Muted { Yes, No, }; struct BufferDescriptorListEntry { u32 buffer_pointer; u32 control_and_length; }; enum BufferDescriptorListEntryFlags : u32 { BufferUnderrunPolicy = 1 << 30, InterruptOnCompletion = 1u << 31, }; class AC97Channel { public: enum Register : u8 { BufferDescriptorListBaseAddress = 0x00, CurrentIndexValue = 0x04, LastValidIndex = 0x05, Status = 0x06, PositionInCurrentBuffer = 0x08, PrefetchedIndexValue = 0x0a, Control = 0x0b, }; static ErrorOr> create_with_parent_pci_device(PCI::Address pci_device_address, StringView name, NonnullOwnPtr channel_io_base); bool dma_running() const { return m_dma_running.with([](auto value) { return value; }); } void handle_dma_stopped(); StringView name() const { return m_name; } void reset(); void set_last_valid_index(u32 buffer_address, u8 last_valid_index); void start_dma(); IOWindow& io_window() { return *m_channel_io_window; } private: AC97Channel(PCI::Address pci_device_address, StringView name, NonnullOwnPtr channel_io_base) : m_channel_io_window(move(channel_io_base)) , m_device_pci_address(pci_device_address) , m_name(name) { } NonnullOwnPtr m_channel_io_window; PCI::Address m_device_pci_address; SpinlockProtected m_dma_running { LockRank::None, false }; StringView m_name; }; AC97(PCI::DeviceIdentifier const&, NonnullOwnPtr pcm_out_channel, NonnullOwnPtr mixer_io_window, NonnullOwnPtr bus_io_window); // ^IRQHandler virtual bool handle_irq(RegisterState const&) override; ErrorOr initialize(); void set_master_output_volume(u8, u8, Muted); ErrorOr set_pcm_output_sample_rate(u32); void set_pcm_output_volume(u8, u8, Muted); ErrorOr write_single_buffer(UserOrKernelBuffer const&, size_t, size_t); // ^AudioController virtual LockRefPtr audio_channel(u32 index) const override; virtual ErrorOr write(size_t channel_index, UserOrKernelBuffer const& data, size_t length) override; virtual void detect_hardware_audio_channels(Badge) override; virtual ErrorOr set_pcm_output_sample_rate(size_t channel_index, u32 samples_per_second_rate) override; virtual ErrorOr get_pcm_output_sample_rate(size_t channel_index) override; OwnPtr m_buffer_descriptor_list; u8 m_buffer_descriptor_list_index { 0 }; AC97Revision m_codec_revision { AC97Revision::Revision21OrEarlier }; bool m_double_rate_pcm_enabled { false }; NonnullOwnPtr m_mixer_io_window; NonnullOwnPtr m_bus_io_window; WaitQueue m_irq_queue; OwnPtr m_output_buffer; u8 m_output_buffer_page_count { 4 }; u8 m_output_buffer_page_index { 0 }; NonnullOwnPtr m_pcm_out_channel; u32 m_sample_rate { 0 }; bool m_variable_rate_pcm_supported { false }; LockRefPtr m_audio_channel; }; }