mirror of
https://github.com/Fennix-Project/Drivers.git
synced 2025-05-28 15:34:29 +00:00
395 lines
13 KiB
C++
395 lines
13 KiB
C++
#include "ac97.hpp"
|
|
#include "../../../Kernel/DAPI.hpp"
|
|
#include "../../../Kernel/Fex.hpp"
|
|
|
|
extern "C" int DriverEntry(void *Data);
|
|
int CallbackHandler(KernelCallback *Data);
|
|
int InterruptCallback(CPURegisters *Registers);
|
|
|
|
HEAD(FexFormatType_Driver, FexOSType_Fennix, DriverEntry);
|
|
|
|
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
|
|
|
__attribute__((section(".extended"))) FexExtended ExtendedHeader = {
|
|
.Driver = {
|
|
.Name = "Audio Codec '97 Driver",
|
|
.Type = FexDriverType_Audio,
|
|
.TypeFlags = FexDriverInputTypes_None,
|
|
.OverrideOnConflict = false,
|
|
.Callback = CallbackHandler,
|
|
.InterruptCallback = InterruptCallback,
|
|
.Bind = {
|
|
.Type = BIND_PCI,
|
|
.PCI = {
|
|
.VendorID = {0x8086},
|
|
.DeviceID = {0x2415},
|
|
.Class = 0x4,
|
|
.SubClass = 0x3,
|
|
.ProgIF = 0x0,
|
|
}}}};
|
|
|
|
KernelAPI *KAPI;
|
|
|
|
#define print(msg) KAPI->Util.DebugPrint((char *)(msg), KAPI->Info.DriverUID)
|
|
|
|
/* --------------------------------------------------------------------------------------------------------- */
|
|
|
|
/* https://wiki.osdev.org/AC97 */
|
|
|
|
PCIDeviceHeader *PCIBaseAddress;
|
|
BARData BAR;
|
|
BufferDescriptorList *DescriptorList = nullptr;
|
|
|
|
AudioEncodingValues Encoding = AE_PCMs16le;
|
|
char Channels = 2;
|
|
char 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:
|
|
{
|
|
print("Kernel acknowledged the driver.");
|
|
break;
|
|
}
|
|
case ConfigurationReason:
|
|
{
|
|
print("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 = PCIBAR0 & (~3);
|
|
BAR.BusMasterAddress = PCIBAR1 & (~15);
|
|
|
|
if (BAR.Type != 1)
|
|
{
|
|
print("BAR0 is not I/O.");
|
|
return INVALID_PCI_BAR;
|
|
}
|
|
uint16_t OutputPCMTransferControl = BAR.BusMasterAddress + PCMOUT_TransferControl;
|
|
|
|
/* DescriptorList address MUST be physical. */
|
|
DescriptorList = (BufferDescriptorList *)KAPI->Memory.RequestPage((sizeof(BufferDescriptorList) * DescriptorListLength) / KAPI->Memory.PageSize + 1);
|
|
KAPI->Util.memset(DescriptorList, 0, sizeof(BufferDescriptorList) * DescriptorListLength);
|
|
|
|
uint16_t DLSampleCount = KAPI->Memory.PageSize / SampleSize;
|
|
char DLLogBuffer[128];
|
|
for (int i = 0; i < DescriptorListLength; i++)
|
|
{
|
|
int DescriptorPages = 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;
|
|
KAPI->Util.sprintf(DLLogBuffer, "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);
|
|
print(DLLogBuffer);
|
|
}
|
|
|
|
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(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(BAR.BusMasterAddress + PCMOUT_BufferDescriptorList, (uint32_t)(uint64_t)DescriptorList);
|
|
outl(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);
|
|
print("AC'97 configured.");
|
|
break;
|
|
}
|
|
case AdjustReason:
|
|
{
|
|
if (Data->AudioCallback.Adjust._Volume)
|
|
{
|
|
Volume = 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)
|
|
{
|
|
print("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;
|
|
print("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;
|
|
print("Invalid channel count. Defaulting to 2.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case FetchReason:
|
|
{
|
|
Data->AudioCallback.Fetch.Volume = (inw(BAR.MixerAddress + NAM_MasterVolume) & 0x3F) * 100 / 0x3F;
|
|
Data->AudioCallback.Fetch.Encoding = Encoding; /* FIXME */
|
|
Data->AudioCallback.Fetch.SampleRate = SampleRate;
|
|
Data->AudioCallback.Fetch.Channels = Channels;
|
|
break;
|
|
}
|
|
case SendReason:
|
|
{
|
|
unsigned char *Buffer = (unsigned char *)Data->AudioCallback.Send.Data;
|
|
unsigned int Length = Data->AudioCallback.Send.Length;
|
|
|
|
if (Buffer == nullptr)
|
|
{
|
|
print("Invalid buffer.");
|
|
return INVALID_DATA;
|
|
}
|
|
|
|
if ((Length == 0) || (Length % (SampleSize * Channels)))
|
|
{
|
|
print("Invalid buffer length.");
|
|
return INVALID_DATA;
|
|
}
|
|
|
|
int TotalBDLToFill = (Length + KAPI->Memory.PageSize - 1) >> 12;
|
|
|
|
while (Length > 0)
|
|
{
|
|
bool ActiveDMA = !(inw(BAR.BusMasterAddress + PCMOUT_Status) & TC_DMAControllerControl);
|
|
|
|
if (ActiveDMA)
|
|
{
|
|
int RemainingBDL = 0;
|
|
|
|
do
|
|
{
|
|
int CurrentBDL = inb(BAR.BusMasterAddress + PCMOUT_BufferDescriptorEntry);
|
|
int LastBDL = inb(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(BAR.BusMasterAddress + PCMOUT_Status) & TC_DMAControllerControl));
|
|
}
|
|
|
|
{
|
|
int CurrentBDL = inb(BAR.BusMasterAddress + PCMOUT_BufferDescriptorEntry);
|
|
int LastBDL = inb(BAR.BusMasterAddress + PCMOUT_DescriptorEntries);
|
|
int NextBDL = LastBDL % DescriptorListLength;
|
|
|
|
ActiveDMA = !(inw(BAR.BusMasterAddress + PCMOUT_Status) & TC_DMAControllerControl);
|
|
if (ActiveDMA)
|
|
{
|
|
NextBDL = (LastBDL + 1) % DescriptorListLength;
|
|
if (NextBDL == CurrentBDL)
|
|
continue;
|
|
}
|
|
|
|
do
|
|
{
|
|
int Wrote = (KAPI->Memory.PageSize > Length) ? Length : KAPI->Memory.PageSize;
|
|
|
|
if (Wrote == 0)
|
|
break;
|
|
KAPI->Util.memcpy((void *)((uint64_t)DescriptorList[NextBDL].Address), Buffer, Wrote);
|
|
DescriptorList[NextBDL].Flags = 0;
|
|
|
|
Buffer += Wrote;
|
|
Length -= Wrote;
|
|
|
|
DescriptorList[NextBDL].SampleCount = Wrote / SampleSize;
|
|
TotalBDLToFill--;
|
|
NextBDL = (NextBDL + 1) % DescriptorListLength;
|
|
} while (TotalBDLToFill-- && NextBDL != CurrentBDL);
|
|
|
|
outb(BAR.BusMasterAddress + PCMOUT_DescriptorEntries, NextBDL - 1);
|
|
|
|
ActiveDMA = !(inw(BAR.BusMasterAddress + PCMOUT_Status) & TC_DMAControllerControl);
|
|
if (!ActiveDMA)
|
|
{
|
|
// Start DMA
|
|
outb(BAR.BusMasterAddress + PCMOUT_TransferControl, inb(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(BAR.BusMasterAddress + PCMOUT_TransferControl, inb(BAR.BusMasterAddress + PCMOUT_TransferControl) & ~TC_DMAControllerControl);
|
|
|
|
// Disable interrupts
|
|
uint8_t TransferControl = inb(BAR.BusMasterAddress + PCMOUT_TransferControl);
|
|
TransferControl &= ~(TC_LastBufferEntryInterruptEnable | TC_IOCInterruptEnable | TC_FifoERRORInterruptEnable);
|
|
outb(BAR.BusMasterAddress + PCMOUT_TransferControl, TransferControl);
|
|
|
|
// Disable global control
|
|
uint32_t GlobalControl = inl(BAR.BusMasterAddress + NABM_GlobalControl);
|
|
GlobalControl &= ~GC_GlobalInterruptEnable;
|
|
GlobalControl |= GC_ShutDown;
|
|
outl(BAR.BusMasterAddress + NABM_GlobalControl, GlobalControl);
|
|
|
|
print("Driver stopped.");
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
print("Unknown reason.");
|
|
break;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
int InterruptCallback(CPURegisters *)
|
|
{
|
|
uint16_t Status = inw(BAR.MixerAddress + PCMOUT_Status);
|
|
|
|
if (Status & TC_IOCInterruptEnable)
|
|
{
|
|
print("Interrupt on completion.");
|
|
}
|
|
else if (Status & TC_LastBufferEntryInterruptEnable)
|
|
{
|
|
print("Last buffer entry.");
|
|
// Stop DMA
|
|
outb(BAR.BusMasterAddress + PCMOUT_TransferControl, inb(BAR.BusMasterAddress + PCMOUT_TransferControl) & ~TC_DMAControllerControl);
|
|
}
|
|
else if (Status & TC_FifoERRORInterruptEnable)
|
|
{
|
|
print("FIFO error.");
|
|
}
|
|
else if (Status != 0x0)
|
|
{
|
|
char UnknownStatusText[64];
|
|
KAPI->Util.sprintf(UnknownStatusText, "Unknown status: %#lx", Status);
|
|
print(UnknownStatusText);
|
|
}
|
|
|
|
outw(BAR.MixerAddress + PCMOUT_Status, 0xFFFF);
|
|
return OK;
|
|
}
|