mirror of
https://github.com/Fennix-Project/Kernel.git
synced 2025-05-28 15:34:33 +00:00
371 lines
10 KiB
C++
371 lines
10 KiB
C++
#include "ac97.hpp"
|
|
|
|
#include <debug.h>
|
|
#include <pci.hpp>
|
|
#include <io.h>
|
|
|
|
#include "../../DAPI.hpp"
|
|
#include "../drv.hpp"
|
|
|
|
using namespace PCI;
|
|
|
|
namespace AudioCodec97
|
|
{
|
|
KernelAPI KAPI;
|
|
|
|
/* https://wiki.osdev.org/AC97 */
|
|
|
|
PCIDeviceHeader *PCIBaseAddress;
|
|
BARData BAR;
|
|
BufferDescriptorList *DescriptorList = nullptr;
|
|
|
|
AudioEncodingValues Encoding = AE_PCMs16le;
|
|
char Channels = 2;
|
|
uint8_t Volume = AV_Maximum;
|
|
bool Mute = false;
|
|
int SampleRate = 48000;
|
|
char SampleSize = 2;
|
|
|
|
uint16_t MixerVolume(uint8_t Left, uint8_t Right, bool Mute)
|
|
{
|
|
return ((uint16_t)((Right & 0x3F) |
|
|
((Left & 0x3F) << 0x8) |
|
|
(Mute & 1 << 0xF)));
|
|
}
|
|
|
|
int DriverEntry(void *Data)
|
|
{
|
|
if (!Data)
|
|
return INVALID_KERNEL_API;
|
|
KAPI = *(KernelAPI *)Data;
|
|
if (KAPI.Version.Major < 0 || KAPI.Version.Minor < 0 || KAPI.Version.Patch < 0)
|
|
return KERNEL_API_VERSION_NOT_SUPPORTED;
|
|
|
|
return OK;
|
|
}
|
|
|
|
int CallbackHandler(KernelCallback *Data)
|
|
{
|
|
switch (Data->Reason)
|
|
{
|
|
case AcknowledgeReason:
|
|
{
|
|
debug("Kernel acknowledged the driver.");
|
|
break;
|
|
}
|
|
case ConfigurationReason:
|
|
{
|
|
debug("Driver received configuration data.");
|
|
PCIBaseAddress = reinterpret_cast<PCIDeviceHeader *>(Data->RawPtr);
|
|
PCIBaseAddress->Command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
|
|
|
|
/* Native Audio Mixer Base Address */
|
|
uint32_t PCIBAR0 = ((PCIHeader0 *)PCIBaseAddress)->BAR0;
|
|
|
|
/* Native Audio Bus Master Base Address */
|
|
uint32_t PCIBAR1 = ((PCIHeader0 *)PCIBaseAddress)->BAR1;
|
|
|
|
BAR.Type = PCIBAR0 & 1;
|
|
BAR.MixerAddress = (uint16_t)(PCIBAR0 & (~3));
|
|
BAR.BusMasterAddress = PCIBAR1 & (~15);
|
|
|
|
if (BAR.Type != 1)
|
|
{
|
|
error("BAR0 is not I/O.");
|
|
return INVALID_PCI_BAR;
|
|
}
|
|
uint16_t OutputPCMTransferControl = (uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl);
|
|
|
|
/* DescriptorList address MUST be physical. */
|
|
DescriptorList = (BufferDescriptorList *)KAPI.Memory.RequestPage((sizeof(BufferDescriptorList) * DescriptorListLength) / KAPI.Memory.PageSize + 1);
|
|
memset(DescriptorList, 0, sizeof(BufferDescriptorList) * DescriptorListLength);
|
|
|
|
uint16_t DLSampleCount = (uint16_t)(KAPI.Memory.PageSize / SampleSize);
|
|
for (int i = 0; i < DescriptorListLength; i++)
|
|
{
|
|
int DescriptorPages = (int)(sizeof(uint16_t *) / KAPI.Memory.PageSize + 1);
|
|
DescriptorList[i].Address = (uint32_t)(uint64_t)KAPI.Memory.RequestPage(DescriptorPages);
|
|
DescriptorList[i].SampleCount = DLSampleCount;
|
|
DescriptorList[i].Flags = 0;
|
|
debug("DescriptorList[%d] = { Address: 0x%x (%d %s), SampleCount: %d, Flags: 0x%x }",
|
|
i,
|
|
DescriptorList[i].Address, DescriptorPages, DescriptorPages == 1 ? "page" : "pages",
|
|
DescriptorList[i].SampleCount,
|
|
DescriptorList[i].Flags);
|
|
}
|
|
|
|
outw(BAR.MixerAddress + NAM_MasterVolume, MixerVolume(Volume, Volume, Mute));
|
|
outw(BAR.MixerAddress + NAM_PCMOutVolume, MixerVolume(Volume, Volume, Mute));
|
|
|
|
outb(OutputPCMTransferControl, inb(OutputPCMTransferControl) | TC_TransferReset);
|
|
while (inb(OutputPCMTransferControl) & TC_TransferReset)
|
|
;
|
|
|
|
uint32_t GlobalControl = inl((uint16_t)(BAR.BusMasterAddress + NABM_GlobalControl));
|
|
GlobalControl = (GlobalControl & ~((0x3U) << 22)); /* PCM 16-bit mode */
|
|
GlobalControl = (GlobalControl & ~((0x3U) << 20)); /* 2 channels */
|
|
GlobalControl |= GC_GlobalInterruptEnable;
|
|
GlobalControl &= ~GC_ShutDown;
|
|
|
|
outl((uint16_t)(BAR.BusMasterAddress + PCMOUT_BufferDescriptorList), (uint32_t)(uint64_t)DescriptorList);
|
|
outl((uint16_t)(BAR.BusMasterAddress + NABM_GlobalControl), GlobalControl);
|
|
|
|
uint8_t TransferControl = inb(OutputPCMTransferControl);
|
|
TransferControl |= TC_LastBufferEntryInterruptEnable | TC_IOCInterruptEnable | TC_FifoERRORInterruptEnable;
|
|
outb(OutputPCMTransferControl, TransferControl);
|
|
|
|
// Stop DMA
|
|
outb(OutputPCMTransferControl, inb(OutputPCMTransferControl) & ~TC_DMAControllerControl);
|
|
debug("AC'97 configured.");
|
|
break;
|
|
}
|
|
case AdjustReason:
|
|
{
|
|
if (Data->AudioCallback.Adjust._Volume)
|
|
{
|
|
Volume = (uint8_t)(0x3F - (0x3F * Data->AudioCallback.Adjust.Volume / 100));
|
|
outw(BAR.MixerAddress + NAM_MasterVolume, MixerVolume(Volume, Volume, Mute));
|
|
// outw(BAR.MixerAddress + NAM_PCMOutVolume, MixerVolume(Volume, Volume, Mute));
|
|
}
|
|
else if (Data->AudioCallback.Adjust._Encoding)
|
|
{
|
|
fixme("Encoding changing not supported yet.");
|
|
}
|
|
else if (Data->AudioCallback.Adjust._SampleRate)
|
|
{
|
|
switch (Data->AudioCallback.Adjust.SampleRate)
|
|
{
|
|
case 0:
|
|
{
|
|
SampleRate = 8000;
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
SampleRate = 11025;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
SampleRate = 16000;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
SampleRate = 22050;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
SampleRate = 32000;
|
|
break;
|
|
}
|
|
case 5:
|
|
{
|
|
SampleRate = 44100;
|
|
break;
|
|
}
|
|
case 6:
|
|
{
|
|
SampleRate = 48000;
|
|
break;
|
|
}
|
|
case 7:
|
|
{
|
|
SampleRate = 88200;
|
|
break;
|
|
}
|
|
case 8:
|
|
{
|
|
SampleRate = 96000;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
SampleRate = 16000;
|
|
error("Invalid sample rate. Defaulting to 16000.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (Data->AudioCallback.Adjust._Channels)
|
|
{
|
|
switch (Data->AudioCallback.Adjust.Channels)
|
|
{
|
|
case 0:
|
|
{
|
|
Channels = 1; // Mono
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
Channels = 2; // Stereo
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
Channels = 2;
|
|
error("Invalid channel count. Defaulting to 2.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case FetchReason:
|
|
{
|
|
Data->AudioCallback.Fetch.Volume = (unsigned char)((inw(BAR.MixerAddress + NAM_MasterVolume) & 0x3F) * 100 / 0x3F);
|
|
Data->AudioCallback.Fetch.Encoding = Encoding; /* FIXME */
|
|
// Data->AudioCallback.Fetch.SampleRate = SampleRate; /* FIXME */
|
|
Data->AudioCallback.Fetch.Channels = Channels;
|
|
break;
|
|
}
|
|
case SendReason:
|
|
{
|
|
unsigned char *Buffer = (unsigned char *)Data->AudioCallback.Send.Data;
|
|
unsigned int Length = (unsigned int)Data->AudioCallback.Send.Length;
|
|
|
|
if (Buffer == nullptr)
|
|
{
|
|
error("Invalid buffer.");
|
|
return INVALID_DATA;
|
|
}
|
|
|
|
if ((Length == 0) || (Length % (SampleSize * Channels)))
|
|
{
|
|
error("Invalid buffer length.");
|
|
return INVALID_DATA;
|
|
}
|
|
|
|
int TotalBDLToFill = (int)((Length + KAPI.Memory.PageSize - 1) >> 12);
|
|
|
|
while (Length > 0)
|
|
{
|
|
bool ActiveDMA = !(inw((uint16_t)(BAR.BusMasterAddress + PCMOUT_Status)) & TC_DMAControllerControl);
|
|
|
|
if (ActiveDMA)
|
|
{
|
|
int RemainingBDL = 0;
|
|
|
|
do
|
|
{
|
|
int CurrentBDL = inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_BufferDescriptorEntry));
|
|
int LastBDL = inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_DescriptorEntries));
|
|
|
|
RemainingBDL = LastBDL - CurrentBDL;
|
|
if (RemainingBDL < 0)
|
|
RemainingBDL += DescriptorListLength;
|
|
|
|
RemainingBDL += 1;
|
|
|
|
if (RemainingBDL >= DescriptorListLength - 1)
|
|
{
|
|
long SampleCount = DescriptorList[(CurrentBDL + 1) % DescriptorListLength].SampleCount / Channels;
|
|
if (SampleCount > 0)
|
|
KAPI.Util.Sleep(SampleCount * 1000 / SampleRate); // milliseconds
|
|
}
|
|
|
|
} while (RemainingBDL >= DescriptorListLength - 1 && !(inw((uint16_t)(BAR.BusMasterAddress + PCMOUT_Status)) & TC_DMAControllerControl));
|
|
}
|
|
|
|
{
|
|
uint8_t CurrentBDL = inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_BufferDescriptorEntry));
|
|
uint8_t LastBDL = inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_DescriptorEntries));
|
|
uint8_t NextBDL = LastBDL % DescriptorListLength;
|
|
|
|
ActiveDMA = !(inw((uint16_t)(BAR.BusMasterAddress + PCMOUT_Status)) & TC_DMAControllerControl);
|
|
if (ActiveDMA)
|
|
{
|
|
NextBDL = (uint8_t)((LastBDL + 1) % DescriptorListLength);
|
|
if (NextBDL == CurrentBDL)
|
|
continue;
|
|
}
|
|
|
|
do
|
|
{
|
|
size_t Wrote = (KAPI.Memory.PageSize > Length) ? Length : KAPI.Memory.PageSize;
|
|
|
|
if (Wrote == 0)
|
|
break;
|
|
memcpy((void *)((uint64_t)DescriptorList[NextBDL].Address), Buffer, Wrote);
|
|
DescriptorList[NextBDL].Flags = 0;
|
|
|
|
Buffer += Wrote;
|
|
Length -= (unsigned int)Wrote;
|
|
|
|
DescriptorList[NextBDL].SampleCount = (uint16_t)(Wrote / SampleSize);
|
|
TotalBDLToFill--;
|
|
NextBDL = (uint8_t)((NextBDL + 1) % DescriptorListLength);
|
|
} while (TotalBDLToFill-- && NextBDL != CurrentBDL);
|
|
|
|
outb((uint16_t)(BAR.BusMasterAddress + PCMOUT_DescriptorEntries), NextBDL - 1);
|
|
|
|
ActiveDMA = !(inw((uint16_t)(BAR.BusMasterAddress + PCMOUT_Status)) & TC_DMAControllerControl);
|
|
if (!ActiveDMA)
|
|
{
|
|
// Start DMA
|
|
outb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl), inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl) | TC_DMAControllerControl));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case StopReason:
|
|
{
|
|
outw(BAR.MixerAddress + NAM_MasterVolume, MixerVolume(AV_Maximum, AV_Maximum, true));
|
|
outw(BAR.MixerAddress + NAM_PCMOutVolume, MixerVolume(AV_Maximum, AV_Maximum, true));
|
|
|
|
// Stop DMA
|
|
outb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl), inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl)) & ~TC_DMAControllerControl);
|
|
|
|
// Disable interrupts
|
|
uint8_t TransferControl = inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl));
|
|
TransferControl &= ~(TC_LastBufferEntryInterruptEnable | TC_IOCInterruptEnable | TC_FifoERRORInterruptEnable);
|
|
outb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl), TransferControl);
|
|
|
|
// Disable global control
|
|
uint32_t GlobalControl = inl((uint16_t)(BAR.BusMasterAddress + NABM_GlobalControl));
|
|
GlobalControl &= ~GC_GlobalInterruptEnable;
|
|
GlobalControl |= GC_ShutDown;
|
|
outl((uint16_t)(BAR.BusMasterAddress + NABM_GlobalControl), GlobalControl);
|
|
|
|
debug("Driver stopped.");
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
warn("Unknown reason.");
|
|
break;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
int InterruptCallback(CPURegisters *)
|
|
{
|
|
uint16_t Status = inw(BAR.MixerAddress + PCMOUT_Status);
|
|
|
|
if (Status & TC_IOCInterruptEnable)
|
|
{
|
|
debug("Interrupt on completion.");
|
|
}
|
|
else if (Status & TC_LastBufferEntryInterruptEnable)
|
|
{
|
|
debug("Last buffer entry.");
|
|
// Stop DMA
|
|
outb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl), inb((uint16_t)(BAR.BusMasterAddress + PCMOUT_TransferControl)) & ~TC_DMAControllerControl);
|
|
}
|
|
else if (Status & TC_FifoERRORInterruptEnable)
|
|
{
|
|
debug("FIFO error.");
|
|
}
|
|
else if (Status != 0x0)
|
|
{
|
|
error("Unknown status: %#lx", Status);
|
|
}
|
|
|
|
outw(BAR.MixerAddress + PCMOUT_Status, 0xFFFF);
|
|
return OK;
|
|
}
|
|
}
|