Aros/Developer/AHIDriversDev
Introduction
editAHI is a system that is supposed to utilize the hardware for playing prepared sound.
Open Source Drivers - M Blom and G Steger (SBLive), M Schulz (ac97), D Wentzler (HDAudio, Envy24HT and CMI8738), R Vumbaca (SB128) and Krzysztof Smiechowicz (Alsa driver)
A typical device driver is usually in one of three conditions...
- inactive - needs to be installed, power startup or enable
- busy - uninstall, power down or disable, release a device
- finished - read or write requests, lock a device
AHI reads a “modefile” that describes the basic features of the sound driver like mono, stereo, 7.1 surround, etc. The driver is opened and goes looking for a matching sound card.
In case of audio drivers yes - pci are probed. But only the init routine defined by resident structure is called in most cases. If the driver cannot find suitable hardware it quits init and gets unloaded. In AHI config you will never see drivers which do not work.
AHI never talks straight to audio chipsets but it does communicate through buffers which contain details like how many channels are needed, how many bits per sample, and how the samples arrive. It builds up one buffer for talking to the sound chip and another that gets the chips' reply.
The typical way to feed continuously is called double-buffering. While the audio chips are playing the first buffer, AHI will be filling the next sounds into the second buffer. Once the playback routine moves onto the second buffer, the first one will be filled with new stuff, and so on. Obviously, it helps if the audio chipset also supports two buffers.
8, 16, or 32 bit in mono or stereo, or 32 bit 7.1 channels to be played by HW. Mixing multiple channels from apps is done by AHI. A less known detail: ALL modes labeled "HiFi" are using 32 bit samples between AHI and the driver, regardless of the resolution of the samples going in to AHI.
All variable quantities exposed to the user (or driver) are of type Fixed, which is a signed 32 bit fixed point value, with the "fixed point" between the upper16 bits and the lower 16. A value of 1 would be stored as 0x00010000, a value of 0.5 would be 0x00008000, 0.25 would be 0x00004000 etc..
This makes for much faster calculations than a floating point variable would offer.
The sound card is expected to create a hard interrupt as it completes each buffer of audio. Assuming two (or more) buffers are in use, this triggers the re-loading of the just completed buffer so that the sound card can run freely back and forth between two buffers and never run out of new sound. (double-buffering).
Drivers are written to conform with whatever AHI expects, and not the other way around. AHI has served us very well, and it has become the standard audio device for all Amiga inspired platforms, but as we grow into more modern hardware, we may be limited by the constraints of AHI design.
USB audio devices have had some trouble structuring an AHI driver that works with DeviceIO commands instead of being driven by hard interrupts. Some of the techniques that AHI used to make accessing the sound card "easy" are now making it more difficult than it should be. USB audio device works on like any other device would. It processes a queue of write requests, each carrying some amount of data to the sound hardware. There are no interrupts involved, just the same DeviceIO techniques that Exec has been doing since Workbench 1.0. changing over to USB would be almost as simple as changing the OpenDevice() call.. but we are not so lucky. AHI will have a USB driver but the "old way" can make things more difficult than was originally expected.
AHI provides each program with volume controls.
sounds is interpreted by the ear in a logirithmic fashion so 10db is perceived as approximately twice as loud, note that's not the same as twice as powerful which is 6db of course.
But sound itself is just a pressure wave and is neither logorithmic nor linear, it just is.
Samples are typically store as LPCM (linear Pulse Code Modulation) and so can definitely be added together with simple addition.
As to AHI reducing the volume when mixing multiple samples, I believe there are several different modes, safe, safe dynamic, full volume, and -3 -6
Say you have 24 channels to mix. Given no other knowledge of the sample values in order to prevent clipping (which is very bad, for speakers or your ears if you have earphones in) you have to reduce the volume of each sample to 1/24, this is (I think) analogous to the safe mode.
This throws away a lot of quality by reducing the sample resolution. There may not be any overflow or very little but it still modifies the output volume. Of course, even in the real world, a lot of sounds together will cause saturation. The trick is to combine them together and compensate for this but still retaining the original sample volumes. Naturally louder sounds will drain out softer sounds so perhaps this can be used to an advantage. But I have seen A+B-(AB) a lot just now.
So if you have a variable number of samples you can reduce the volume by only the current number of used channels. So only 6 channels in use reduce to 1/6 much less loss in clarity but the unreal effect of more sounds reducing the apparent volume of each sub sound, (safe, dynamic)
Or you can take the computer out of the loop and assume the human (or software the human is running like a game doing its own mixing, or AudioEvolution etc.) make sane judgements, no attenuation, no loss of quality, but get it wrong and you blow your ears out. (Ful Volume mode). Since 90% of the time I'm only playing a single stereo stream I always use this option.
The -3 and -6 db modes just add bit of head room into the full volume equation.
Quick Building
editsimple way to only recompile the HDAudio driver? running 'make AHI-quick' or 'make workbench-devs-AHI-quick' in AROS directory should to the trick. 'make AHI' will also rebuild dependencies of AHI
only says "MMAKE: Nothing to do for xxxx", despite changes being made The AHI build is done by using calling configure and calling make (%build_with_configure mmake macro). Because of that mmake does not see the dependencies; it just uses some stamp files. You can delete bin/__ARCH__-__CPU__/gen/workbench/devs/AHI and then call 'make AHI-quick'. I hope that should do the trick.
rebuilding AHI drivers is tricky. I use a command like this:
rm bin/pc-i386/gen/workbench/devs/AHI/.installed ; make workbench-devs-AHI-quick && cp bin/pc-i386/AROS/Devs/AHI/hdaudio.audio ...
Overview of an AHI driver
editThe AHI source drivers are contained in the main source.bz2 and not contrib, each drivers sources has its own drawer in workbench/devs/AHI/Drivers.
Playback just does a straight copy to the buffer, and recording does nothing at all with the buffer. The buffer size is set by AHI based on sample rate and size, so that the interrupt rate will be 50 Hz. Typically they are around 8KB if I recall correctly for 44100/16bit. The size should not be changed.
If the hardware does not support 32 bit samples, you should re-enable HiFi in the AUDIOMODE so that AHI mixes at "HiFi" quality. The driver already takes care of skipping the upper part when a HiFi mode is used.
- MODEID
30 FM 801 Benjamin Vernoux <email@address.com> 31 ???/Mediator Pawel Stypula <> 32 FM 801/AOS4 Davy Wentzler <> +33 OSS/AROS Martin Blom <> 34 SB128/AOS4 Ross Vumbaca <> 35 CMI8738/AOS4 Davy Wentzler <> 36 ICE1724/AOS4 Davy Wentzler <> 37 ICE1712/AOS4 Davy Wentzler <> 38 Trid. 4DWAVE-DX Marcus Comstedt +39 ac97/AROS Michal Schulz <> +3E HDAudio/AROS Davy Wentzler <> +42 Alsa/AROS Krzysztof Smiechowicz <> 200 via-ac97/AROS Davy Wentzler <>
name.s name-init.c name-main.c name-accel.c name-interrupt.c DriverData.h pci.c
The Void drawer contains a dummy playback only driver
name.s
editFORM_START AHIM CHUNK_START AUDN .asciz "ac97" CHUNK_END CHUNK_START AUDM 1: LONG2 AHIDB_AudioID, 0x00390004 LONG2 AHIDB_Volume, TRUE LONG2 AHIDB_Panning, TRUE LONG2 AHIDB_Stereo, TRUE LONG2 AHIDB_HiFi, FALSE LONG2 AHIDB_MultTable,FALSE LONG2 AHIDB_Name, 2f-1b LONG TAG_DONE 2: .asciz "ac97:16 bit stereo++" CHUNK_END FORM_END .balign 4,0 .END
name-init.c
edit#include <exec/memory.h> #include <proto/expansion.h> #include <proto/dos.h> #include <proto/exec.h> #include <clib/alib_protos.h> #ifdef __AROS__ #include <aros/debug.h> struct ExecBase* SysBase = NULL; struct DosLibrary* DOSBase; #endif #include <stdlib.h> #include "library.h" #include "version.h" #include "pci_wrapper.h" #include "misc.h" struct DriverBase* AHIsubBase; struct VendorDevice { UWORD vendor; UWORD device; }; struct VendorDevice *vendor_device_list = NULL; static int vendor_device_list_size = 0; static void parse_config_file(); static int hex_char_to_int(char c); #define MAX_DEVICE_VENDORS 512 /****************************************************************************** ** Custom driver init ********************************************************* ******************************************************************************/ BOOL DriverInit(struct DriverBase* ahisubbase) { bug("exit init\n"); return TRUE; } /****************************************************************************** ** Custom driver clean-up ***************************************************** ******************************************************************************/ VOID DriverCleanup(struct DriverBase* AHIsubBase) { }
name-main.c
editRead more here
#include <config.h> #include <devices/ahi.h> #include <exec/memory.h> #include <libraries/ahi_sub.h> #include <math.h> #include <proto/ahi_sub.h> #include <proto/exec.h> #include <proto/dos.h> #include <proto/utility.h> #ifdef __AROS__ #include <aros/debug.h> #define DebugPrintF bug #endif #include <string.h> #include "library.h" #include "regs.h" #include "misc.h" #include "pci_wrapper.h" extern int z, timer; /****************************************************************************** ** Globals ******************************************************************** ******************************************************************************/ #define uint32 unsigned int /****************************************************************************** ** AHIsub_AllocAudio ********************************************************** ******************************************************************************/ ULONG _AHIsub_AllocAudio(struct TagItem* taglist, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { } /****************************************************************************** ** AHIsub_FreeAudio *********************************************************** ******************************************************************************/ void _AHIsub_FreeAudio(struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { } /****************************************************************************** ** AHIsub_Disable ************************************************************* ******************************************************************************/ void _AHIsub_Disable(struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { // V6 drivers do not have to preserve all registers Disable(); } /****************************************************************************** ** AHIsub_Enable ************************************************************** ******************************************************************************/ void _AHIsub_Enable(struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { // V6 drivers do not have to preserve all registers Enable(); } /****************************************************************************** ** AHIsub_Start *************************************************************** ******************************************************************************/ ULONG _AHIsub_Start(ULONG flags, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { return AHIE_OK; } /****************************************************************************** ** AHIsub_Update ************************************************************** ******************************************************************************/ void _AHIsub_Update(ULONG flags, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { } /****************************************************************************** ** AHIsub_Stop **************************************************************** ******************************************************************************/ void _AHIsub_Stop(ULONG flags, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { } /****************************************************************************** ** AHIsub_GetAttr ************************************************************* ******************************************************************************/ LONG _AHIsub_GetAttr(ULONG attribute, LONG argument, LONG def, struct TagItem* taglist, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { } /****************************************************************************** ** AHIsub_HardwareControl ***************************************************** ******************************************************************************/ ULONG _AHIsub_HardwareControl(ULONG attribute, LONG argument, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { }
name-accel.c
edit#include <config.h> #include <devices/ahi.h> #include <libraries/ahi_sub.h> #include "library.h" /****************************************************************************** ** AHIsub_SetVol ************************************************************** ******************************************************************************/ ULONG _AHIsub_SetVol(UWORD channel, Fixed volume, sposition pan, struct AHIAudioCtrlDrv* AudioCtrl, ULONG flags, struct DriverBase* AHIsubBase) { return AHIS_UNKNOWN; } /****************************************************************************** ** AHIsub_SetFreq ************************************************************* ******************************************************************************/ ULONG _AHIsub_SetFreq(UWORD channel, ULONG freq, struct AHIAudioCtrlDrv* AudioCtrl, ULONG flags, struct DriverBase* AHIsubBase) { return AHIS_UNKNOWN; } /****************************************************************************** ** AHIsub_SetSound ************************************************************ ******************************************************************************/ ULONG _AHIsub_SetSound(UWORD channel, UWORD sound, ULONG offset, LONG length, struct AHIAudioCtrlDrv* AudioCtrl, ULONG flags, struct DriverBase* AHIsubBase) { return AHIS_UNKNOWN; } /****************************************************************************** ** AHIsub_SetEffect *********************************************************** ******************************************************************************/ ULONG _AHIsub_SetEffect(APTR effect, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { return AHIS_UNKNOWN; } /****************************************************************************** ** AHIsub_LoadSound *********************************************************** ******************************************************************************/ ULONG _AHIsub_LoadSound(UWORD sound, ULONG type, APTR info, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { return AHIS_UNKNOWN; } /****************************************************************************** ** AHIsub_UnloadSound ********************************************************* ******************************************************************************/ ULONG _AHIsub_UnloadSound(UWORD sound, struct AHIAudioCtrlDrv* AudioCtrl, struct DriverBase* AHIsubBase) { return AHIS_UNKNOWN; }
name-interrupt.c
edit#include <config.h> #include <proto/expansion.h> #include <libraries/ahi_sub.h> #include <proto/exec.h> #include <stddef.h> #include "library.h" #include "regs.h" #include "interrupt.h" #include "misc.h" #include "pci_wrapper.h" #ifdef __AROS__ #include <aros/debug.h> #endif #define min(a,b) ((a)<(b)?(a):(b)) int z = 0; ULONG timer = 0; // for demo/test #define TIME_LIMIT 150 // 150 irq's /****************************************************************************** ** Hardware interrupt handler ************************************************* ******************************************************************************/ #ifdef __AMIGAOS4__ ULONG CardInterrupt( struct ExceptionContext *pContext, struct ExecBase *SysBase, struct HDAudioChip* card ) #else ULONG CardInterrupt( struct HDAudioChip* card ) #endif { struct AHIAudioCtrlDrv* AudioCtrl = card->audioctrl; struct DriverBase* AHIsubBase = (struct DriverBase*) card->ahisubbase; struct PCIDevice *dev = (struct PCIDevice *) card->pci_dev; ULONG intreq, status; LONG handled = 0; UBYTE rirb_status; int i; intreq = pci_inl(HD_INTSTS, card); if (intreq & HD_INTCTL_GLOBAL) { if (intreq & 0x3fffffff) // stream interrupt { ULONG position; BOOL playback = FALSE; BOOL recording = FALSE; //bug("Stream irq\n"); for (i = 0; i < card->nr_of_streams; i++) { if (intreq & (1 << card->streams[i].index)) { // acknowledge stream interrupt pci_outb(0x1C, card->streams[i].sd_reg_offset + HD_SD_OFFSET_STATUS, card); if (i < card->nr_of_input_streams) { recording = TRUE; } else { playback = TRUE; } } } pci_outb(0xFF, HD_INTSTS, card); z++; #ifdef TIME_LIMITED timer++; if (timer > TIME_LIMIT) // stop playback { outb_clearbits(HD_SD_CONTROL_STREAM_RUN, card->streams[card->nr_of_input_streams].sd_reg_offset + HD_SD_OFFSET_CONTROL, card); } #endif //bug("SIRQ\n"); if (playback) { // bug("PB\n"); position = pci_inl(card->streams[card->nr_of_input_streams].sd_reg_offset + HD_SD_OFFSET_LINKPOS, card); if (card->flip == 1) //position <= card->current_bytesize + 64) { if (card->flip == 0) { bug("Lost IRQ!\n"); } card->flip = 0; card->current_buffer = card->playback_buffer1; } else { if (card->flip == 1) { bug("Lost IRQ!\n"); } card->flip = 1; card->current_buffer = card->playback_buffer2; } Cause(&card->playback_interrupt); } if (recording) { position = pci_inl(card->streams[0].sd_reg_offset + HD_SD_OFFSET_LINKPOS, card); if (card->recflip == 1) //position <= card->current_record_bytesize + 64) { if (card->recflip == 0) { bug("Lost rec IRQ!\n"); } card->recflip = 0; card->current_record_buffer = card->record_buffer1; } else { if (card->recflip == 1) { bug("Lost rec IRQ!\n"); } card->recflip = 1; card->current_record_buffer = card->record_buffer2; } Cause(&card->record_interrupt); } } if (intreq & HD_INTCTL_CIE) { //bug("CIE\n"); pci_outb(0x4, HD_INTSTS + 3, card); // only byte access allowed // if (card->is_playing) // bug("CIE irq! rirb is %x, STATESTS = %x\n", pci_inb(HD_RIRBSTS, card), pci_inw(HD_STATESTS, card)); // check for RIRB status rirb_status = pci_inb(HD_RIRBSTS, card); if (rirb_status & 0x5) { if (rirb_status & 0x4) // RIRBOIS { // bug("RIRB overrun!\n"); } if (rirb_status & 0x1) // RINTFL { card->rirb_irq++; /*if (card->rirb_irq > 1) { bug("IRQ: rirb_irq = %d\n", card->rirb_irq); }*/ //bug("RIRB IRQ!\n"); } pci_outb(0x5, HD_RIRBSTS, card); } } handled = 1; } return handled; } /****************************************************************************** ** Playback interrupt handler ************************************************* ******************************************************************************/ #ifdef __AMIGAOS4__ void PlaybackInterrupt( struct ExceptionContext *pContext, struct ExecBase *SysBase, struct HDAudioChip* card ) #else void PlaybackInterrupt( struct HDAudioChip* card ) #endif { struct AHIAudioCtrlDrv* AudioCtrl = card->audioctrl; struct DriverBase* AHIsubBase = (struct DriverBase*) card->ahisubbase; if (card->mix_buffer != NULL && card->current_buffer != NULL && card->is_playing) { BOOL skip_mix; WORD* src; int i; LONG* srclong, *dstlong, left, right; int frames = card->current_frames; skip_mix = CallHookPkt(AudioCtrl->ahiac_PreTimerFunc, (Object*) AudioCtrl, 0); CallHookPkt(AudioCtrl->ahiac_PlayerFunc, (Object*) AudioCtrl, NULL); if (! skip_mix) { CallHookPkt(AudioCtrl->ahiac_MixerFunc, (Object*) AudioCtrl, card->mix_buffer); } /* Now translate and transfer to the DMA buffer */ srclong = (LONG*) card->mix_buffer; dstlong = (LONG*) card->current_buffer; i = frames; if (AudioCtrl->ahiac_Flags & AHIACF_HIFI) { while(i > 0) { *dstlong++ = *srclong++; *dstlong++ = *srclong++; --i; } } else { while(i > 0) { *dstlong++ = (*srclong & 0xFF00) >> 16; srclong++; // tbd *dstlong++ = (*srclong & 0xFF000000) >> 16; srclong++; --i; } } CallHookPkt(AudioCtrl->ahiac_PostTimerFunc, (Object*) AudioCtrl, 0); } } /****************************************************************************** ** Record interrupt handler *************************************************** ******************************************************************************/ #ifdef __AMIGAOS4__ void RecordInterrupt( struct ExceptionContext *pContext, struct ExecBase *SysBase, struct HDAudioChip* card ) #else void RecordInterrupt( struct HDAudioChip* card ) #endif { struct AHIAudioCtrlDrv* AudioCtrl = card->audioctrl; struct DriverBase* AHIsubBase = (struct DriverBase*) card->ahisubbase; int i = 0; int frames = card->current_record_bytesize / 2; struct AHIRecordMessage rm = { AHIST_S16S, card->current_record_buffer, RECORD_BUFFER_SAMPLES }; WORD *src = card->current_record_buffer; WORD* dst = card->current_record_buffer; #ifdef __AMIGAOS4__ while( i < frames ) { *dst = ( ( *src & 0x00FF ) << 8 ) | ( ( *src & 0xFF00 ) >> 8 ); ++i; ++src; ++dst; } #else /*while( i < frames ) { *dst = (*src); ++i; ++src; ++dst; }*/ #endif CallHookPkt(AudioCtrl->ahiac_SamplerFunc, (Object*) AudioCtrl, &rm); }
DriverData.h
edit#ifndef AHI_Drivers_Card_DriverData_h #define AHI_Drivers_Card_DriverData_h #include <exec/types.h> #include <exec/interrupts.h> #include <devices/ahi.h> /** Make the common library code initialize a global SysBase for us. It's required for hwaccess.c */ #define DRIVER "hdaudio.audio" #define DRIVER_NEEDS_GLOBAL_EXECBASE #define INPUTS 5 #ifdef __AROS__ #define DRIVER_NEED_GLOBAL_EXECBASE #endif #ifdef __amigaos4__ #define DRIVER_NEED_GLOBAL_EXECBASE #endif #include "DriverBase.h" { }; #endif /* AHI_Drivers_Card_DriverData_h */
#define DEBUG 0
#include <aros/debug.h>
#include <asm/io.h>
#include <hidd/irq.h>
#include <config.h>
#include "library.h"
#include "DriverData.h"
OOP_AttrBase __IHidd_PCIDev;
static const struct {
UWORD VendorID;
UWORD ProductID;
STRPTR Model;
} support[] = {
{ 0x8086, 0x2415, "Intel 82801AA" },
{ 0x8086, 0x2425, "Intel 82801AB" },
{ 0x8086, 0x2445, "Intel 82801BA" },
{ 0x8086, 0x2485, "Intel ICH3" },
{ 0x8086, 0x24c5, "Intel ICH4" },
{ 0x8086, 0x24d5, "Intel ICH5" },
{ 0x8086, 0x25a6, "ESB" },
{ 0x8086, 0x266e, "Intel ICH6" },
{ 0x8086, 0x27de, "Intel ICH7" },
{ 0x8086, 0x2698, "ESB2" },
{ 0x8086, 0x7195, "Intel 440MX" },
{ 0x1039, 0x7012, "SIS 7012" },
{ 0x10de, 0x01b1, "NVIDIA nForce" },
{ 0x10de, 0x003a, "MCP04" },
{ 0x10de, 0x006a, "NVIDIA nForce2" },
{ 0x10de, 0x0059, "CK804" },
{ 0x10de, 0x008a, "MCP2S AC'97 Audio Controller" },
{ 0x10de, 0x00da, "NVIDIA nForce3" },
{ 0x10de, 0x00ea, "CK8S" },
{ 0x10de, 0x026b, "MCP51" },
{ 0x1022, 0x746d, "AMD 8111" },
{ 0x1022, 0x7445, "AMD 768" },
{ 0x10b9, 0x5455, "Ali 5455" },
{0,0,NULL},
};
static void i8x0_set_reg(struct ac97Base *ac97Base, ULONG reg, UWORD value)
{
int count=1000000;
while(count-- && (inb(ac97Base->dmabase + ACC_SEMA) & 1));
outw(value, reg+ac97Base->mixerbase);
}
static UWORD i8x0_get_reg(struct ac97Base *ac97Base, ULONG reg)
{
int count=1000000;
while(count-- && (inb(ac97Base->dmabase + ACC_SEMA) & 1));
return inw(reg+ac97Base->mixerbase);
}
/******************************************************************************
** Custom driver init *********************************************************
******************************************************************************/
#define ac97Base ((struct ac97Base *)hook->h_Data)
#define AHIsubBase ((struct DriverBase *)hook->h_Data)
static AROS_UFH3(void, Enumerator,
AROS_UFHA(struct Hook *, hook, A0),
AROS_UFHA(OOP_Object *, device, A2),
AROS_UFHA(APTR, msg, A1))
{
AROS_USERFUNC_INIT
ULONG VendorID,ProductID;
int i;
OOP_GetAttr(device, aHidd_PCIDevice_ProductID, &ProductID);
OOP_GetAttr(device, aHidd_PCIDevice_VendorID, &VendorID);
D(bug("[ac97] Found audio device %04x:%04x\n", VendorID, ProductID));
for (i=0; support[i].VendorID; i++)
{
if (VendorID == support[i].VendorID && ProductID == support[i].ProductID)
{
struct TagItem attrs[] = {
{ aHidd_PCIDevice_isIO, TRUE },
{ aHidd_PCIDevice_isMEM, FALSE },
{ aHidd_PCIDevice_isMaster, TRUE },
{ TAG_DONE, 0UL },
};
D(bug("[ac97] Found supported '%s' card\n", support[i].Model));
ac97Base->cardfound = TRUE;
ac97Base->mixer_set_reg = i8x0_set_reg;
ac97Base->mixer_get_reg = i8x0_get_reg;
OOP_SetAttrs(device, (struct TagItem *)&attrs);
OOP_GetAttr(device, aHidd_PCIDevice_Base0, &ac97Base->mixerbase);
OOP_GetAttr(device, aHidd_PCIDevice_Base1, &ac97Base->dmabase);
OOP_GetAttr(device, aHidd_PCIDevice_INTLine, &ac97Base->irq_num);
D(bug("[ac97] Mixer IO base %x\n", ac97Base->mixerbase));
D(bug("[ac97] DMA IO base %x\n", ac97Base->dmabase));
if (VendorID == 0x1039 && ProductID == 0x7012)
{
/* SIS 7012 */
ac97Base->off_po_sr = DEFAULT_PO_PICB; /* swap registers */
ac97Base->off_po_picb = DEFAULT_PO_SR;
ac97Base->size_shift = 1; /* chip requires size in bytes, not samples */
}
else
{
/* All other cards */
ac97Base->off_po_sr = DEFAULT_PO_SR; /* swap registers */
ac97Base->off_po_picb = DEFAULT_PO_PICB;
ac97Base->size_shift = 0;
}
outl(2, ac97Base->dmabase + GLOB_CNT);
ac97Base->mixer_set_reg(ac97Base, AC97_RESET, 0);
ac97Base->mixer_set_reg(ac97Base, AC97_POWERDOWN, 0);
/* Set master volume to no attenuation, mute off */
ac97Base->mixer_set_reg(ac97Base, AC97_MASTER_VOL, 0x0000);
ac97Base->mixer_set_reg(ac97Base, AC97_HEADPHONE_VOL, 0x0000);
ac97Base->mixer_set_reg(ac97Base, AC97_TONE, 0x0f0f);
ac97Base->mixer_set_reg(ac97Base, AC97_PCM_VOL, 0x0000);
D(bug("[ac97] Powerdown = %02x\n", ac97Base->mixer_get_reg(ac97Base, AC97_POWERDOWN)));
D(bug("[ac97] GLOB_CNT = %08x\n", inl(ac97Base->dmabase + GLOB_CNT)));
D(bug("[ac97] GLOB_STA = %08x\n", inl(ac97Base->dmabase + GLOB_STA)));
/*
int i;
for (i=0; i < 64; i+=2)
{
D(bug("[ac97] reg %02x = %04x\n", i, ac97Base->mixer_get_reg(ac97Base, i)));
}
*/
outl(ac97Base->PCM_out, ac97Base->dmabase + PO_BDBAR);
D(bug("[ac97] PO_BDBAR=%08x\n", inl(ac97Base->dmabase + PO_BDBAR)));
D(bug("[ac97] PO_REGS=%08x\n", inl(ac97Base->dmabase + PO_CIV)));
D(bug("[ac97] PO_PICB=%04x\n", inw(ac97Base->dmabase + ac97Base->off_po_picb)));
D(bug("[ac97] PO_PIV=%02x\n", inb(ac97Base->dmabase + PO_PIV)));
D(bug("[ac97] PO_CR=%02x\n", inb(ac97Base->dmabase + PO_CR)));
}
}
AROS_USERFUNC_EXIT
}
#undef ac97Base
#undef AHIsubBase
BOOL DriverInit( struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
ac97Base->dosbase = OpenLibrary( DOSNAME, 37 );
ac97Base->sysbase = SysBase;
D(bug("[ac97] Init\n"));
if(DOSBase)
{
ac97Base->oopbase = OpenLibrary(AROSOOP_NAME, 0);
if (OOPBase)
{
__IHidd_PCIDev = OOP_ObtainAttrBase(IID_Hidd_PCIDevice);
D(bug("[ac97] Libraries opened\n"));
if (__IHidd_PCIDev)
{
OOP_Object *pci = OOP_NewObject(NULL, CLID_Hidd_PCI, NULL);
D(bug("[ac97] PCIDevice AttrBase = %x\n",__IHidd_PCIDev));
if (pci)
{
struct Hook FindHook = {
h_Entry: (IPTR(*)())Enumerator,
h_Data: ac97Base,
};
struct TagItem Reqs[] = {
{ tHidd_PCI_Class, 0x04 },
{ tHidd_PCI_SubClass, 0x01 },
{ TAG_DONE, 0UL },
};
struct pHidd_PCI_EnumDevices enummsg = {
mID: OOP_GetMethodID(CLID_Hidd_PCI, moHidd_PCI_EnumDevices),
callback: &FindHook,
requirements: (struct TagItem *)&Reqs,
}, *msg = &enummsg;
D(bug("[ac97] Got PCI object\n"));
ac97Base->cardfound = FALSE;
ac97Base->PCM_out = AllocMem(8*32, MEMF_PUBLIC | MEMF_CLEAR);
OOP_DoMethod(pci, (OOP_Msg)msg);
OOP_DisposeObject(pci);
D(bug("[ac97] PCM out base %08x\n", ac97Base->PCM_out));
return ac97Base->cardfound;
}
}
}
else
{
Req("Unable to open 'oop.library'\n");
}
}
else
{
Req( "Unable to open 'dos.library' version 37.\n" );
}
return FALSE;
}
/******************************************************************************
** Custom driver clean-up *****************************************************
******************************************************************************/
VOID DriverCleanup( struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
OOP_ReleaseAttrBase(IID_Hidd_PCIDevice);
CloseLibrary( (struct Library*) DOSBase );
CloseLibrary( (struct Library*) ac97Base->oopbase);
}
#define DEBUG 0
#include <aros/debug.h>
#include <config.h>
#include <devices/ahi.h>
#include <dos/dostags.h>
#include <exec/memory.h>
#include <libraries/ahi_sub.h>
#include <utility/tagitem.h>
#include <proto/ahi_sub.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/utility.h>
#include <stddef.h>
#include <hidd/irq.h>
#include <asm/io.h>
#include "library.h"
#include "DriverData.h"
void
SlaveEntry( void );
PROCGW( static, void, slaveentry, SlaveEntry );
/* There is probably no reason to support all these frequencies. If,
* for example, your hardware is locked at 48 kHz, it's ok to only
* present one single mixing/recording frequency to the user. If your
* hardware has internal resamples and accepts any frequency, select a
* few common ones.
*/
static const LONG frequencies[] =
{
48000, // DAT
};
#define FREQUENCIES (sizeof frequencies / sizeof frequencies[ 0 ])
static const ULONG table_5bit[] = {
0xb53c,
0x804e,
0x5ad5,
0x404e,
0x2d86,
0x203a,
0x16d1,
0x1027,
0x0b6f,
0x0818,
0x05bb,
0x040f,
0x02df,
0x0209,
0x0171,
0x0105,
0x00b9,
0x0083,
0x005d,
0x0042,
0x002e,
0x0021,
0x0017,
0x0010,
0x000c,
0x0008,
0x0006,
0x0004,
0x0003,
0x0002,
0x0001,
0x0000
};
static UWORD LinToLog(ULONG vol)
{
int i;
if (!vol) return 0x20;
for (i=0; i < 32; i++)
{
if (vol > table_5bit[i])
{
return i;
}
}
return 0x1f;
}
static void play_int(HIDDT_IRQ_Handler *irq, HIDDT_IRQ_HwInfo *hw);
/******************************************************************************
** AHIsub_AllocAudio **********************************************************
******************************************************************************/
ULONG
_AHIsub_AllocAudio( struct TagItem* taglist,
struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
OOP_Object *irq = OOP_NewObject(NULL, CLID_Hidd_IRQ, NULL);
AudioCtrl->ahiac_DriverData = AllocVec( sizeof( struct AC97Data ),
MEMF_CLEAR | MEMF_PUBLIC );
#define dd ((struct AC97Data *) AudioCtrl->ahiac_DriverData)
D(bug("AHI: AllocAudio: dd=%08x\n", dd));
if( dd != NULL )
{
dd->slavesignal = -1;
dd->mastersignal = AllocSignal( -1 );
dd->mastertask = (struct Process*) FindTask( NULL );
dd->ahisubbase = ac97Base;
dd->out_volume = 0x10000;
}
else
{
return AHISF_ERROR;
}
dd->irq = AllocVec(sizeof (HIDDT_IRQ_Handler), MEMF_CLEAR | MEMF_PUBLIC);
if (dd->irq)
{
struct pHidd_IRQ_AddHandler __msg__ = {
mID: OOP_GetMethodID(CLID_Hidd_IRQ, moHidd_IRQ_AddHandler),
handlerinfo: dd->irq,
id: ac97Base->irq_num,
}, *msg = &__msg__;
dd->irq->h_Node.ln_Pri = 0;
dd->irq->h_Node.ln_Name = "AHI Int";
dd->irq->h_Code = play_int;
dd->irq->h_Data = AudioCtrl;
OOP_DoMethod(irq, (OOP_Msg)msg);
OOP_DisposeObject(irq);
}
D(bug("AHI: AllocAudio: Everything OK\n"));
if( dd->mastersignal == -1 )
{
return AHISF_ERROR;
}
/* Setting the only working frequency for AC97 */
AudioCtrl->ahiac_MixFreq = 48000;
return ( AHISF_KNOWSTEREO |
AHISF_MIXING |
AHISF_TIMING );
}
/******************************************************************************
** AHIsub_FreeAudio ***********************************************************
******************************************************************************/
void
_AHIsub_FreeAudio( struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
OOP_Object *irq = OOP_NewObject(NULL, CLID_Hidd_IRQ, NULL);
D(bug("AHI: FreeAudio\n"));
if (dd->irq)
{
struct pHidd_IRQ_RemHandler __msg__ = {
mID: OOP_GetMethodID(CLID_Hidd_IRQ, moHidd_IRQ_RemHandler),
handlerinfo: dd->irq,
}, *msg = &__msg__;
OOP_DoMethod(irq, (OOP_Msg)msg);
FreeVec(dd->irq);
}
D(bug("AHI: FreeAudio: IRQ removed\n"));
if( AudioCtrl->ahiac_DriverData != NULL )
{
FreeSignal( dd->mastersignal );
D(bug("AHI: FreeAudio: Signal freed\n"));
FreeVec( AudioCtrl->ahiac_DriverData );
D(bug("AHI: FreeAudio: DriverData freed\n"));
AudioCtrl->ahiac_DriverData = NULL;
}
OOP_DisposeObject(irq);
D(bug("AHI: FreeAudio: IRQ object freed\n"));
}
/******************************************************************************
** AHIsub_Disable *************************************************************
******************************************************************************/
void
_AHIsub_Disable( struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
// V6 drivers do not have to preserve all registers
Disable();
}
/******************************************************************************
** AHIsub_Enable **************************************************************
******************************************************************************/
void
_AHIsub_Enable( struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
// V6 drivers do not have to preserve all registers
Enable();
}
/******************************************************************************
** AHIsub_Start ***************************************************************
******************************************************************************/
ULONG
_AHIsub_Start( ULONG flags,
struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
D(bug("AHI: Start\n"));
AHIsub_Stop( flags, AudioCtrl );
D(bug("AHI: Start: Stop called\n"));
if(flags & AHISF_PLAY)
{
struct TagItem proctags[] =
{
{ NP_Entry, (IPTR) &slaveentry },
{ NP_Name, (IPTR) LibName },
{ NP_Priority, -1 },
{ TAG_DONE, 0 }
};
dd->mixbuffer = AllocVec( AudioCtrl->ahiac_BuffSize,
MEMF_ANY | MEMF_PUBLIC );
D(bug("AHI: Start: Mixing buffer = %08x\n",dd->mixbuffer));
if( dd->mixbuffer == NULL ) return AHIE_NOMEM;
Forbid();
dd->slavetask = CreateNewProc( proctags );
D(bug("AHI: Start: Slave task = %08x\n",dd->slavetask));
if( dd->slavetask != NULL )
{
dd->slavetask->pr_Task.tc_UserData = AudioCtrl;
}
D(bug("AHI: Start: Slave task UserData set\n"));
Permit();
if( dd->slavetask != NULL )
{
Wait( 1L << dd->mastersignal ); // Wait for slave to come alive
D(bug("AHI: Start: Slave task UP and running\n"));
if( dd->slavetask == NULL ) // Is slave alive or dead?
{
return AHIE_UNKNOWN;
}
}
else
{
return AHIE_NOMEM; // Well, out of memory or whatever...
}
}
if( flags & AHISF_RECORD )
{
return AHIE_UNKNOWN;
}
D(bug("AHI: Start: Everything OK\n"));
return AHIE_OK;
}
/******************************************************************************
** AHIsub_Update **************************************************************
******************************************************************************/
void
_AHIsub_Update( ULONG flags,
struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
// Empty function
}
/******************************************************************************
** AHIsub_Stop ****************************************************************
******************************************************************************/
void
_AHIsub_Stop( ULONG flags,
struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
if( flags & AHISF_PLAY )
{
if( dd->slavetask != NULL )
{
if( dd->slavesignal != -1 )
{
Signal( (struct Task*) dd->slavetask,
1L << dd->slavesignal ); // Kill him!
}
Wait( 1L << dd->mastersignal ); // Wait for slave to die
}
FreeVec( dd->mixbuffer );
dd->mixbuffer = NULL;
}
if(flags & AHISF_RECORD)
{
// Do nothing
}
}
/******************************************************************************
** AHIsub_GetAttr *************************************************************
******************************************************************************/
IPTR
_AHIsub_GetAttr( ULONG attribute,
LONG argument,
IPTR def,
struct TagItem* taglist,
struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
size_t i;
switch( attribute )
{
case AHIDB_Bits:
return 16;
case AHIDB_Frequencies:
return FREQUENCIES;
case AHIDB_Frequency: // Index->Frequency
return (LONG) frequencies[ argument ];
case AHIDB_Index: // Frequency->Index
if( argument <= frequencies[ 0 ] )
{
return 0;
}
if( argument >= frequencies[ FREQUENCIES - 1 ] )
{
return FREQUENCIES - 1;
}
for( i = 1; i < FREQUENCIES; i++ )
{
if( frequencies[ i ] > argument )
{
if( ( argument - frequencies[ i - 1 ] ) <
( frequencies[ i ] - argument ) )
{
return i-1;
}
else
{
return i;
}
}
}
return 0; // Will not happen
case AHIDB_Author:
return (IPTR) "Michal Schulz";
case AHIDB_Copyright:
return (IPTR) "APL";
case AHIDB_Version:
return (IPTR) LibIDString;
case AHIDB_Record:
return FALSE;
case AHIDB_Realtime:
return TRUE;
case AHIDB_Outputs:
return 1;
/*
case AHIDB_MinMonitorVolume:
return 0x00000;
case AHIDB_MaxMonitorVolume:
return 0x10000;
*/
case AHIDB_MinOutputVolume:
return 0x00000;
case AHIDB_MaxOutputVolume:
return 0x10000;
case AHIDB_Output:
return (IPTR) "Default"; // We have only one "output"!
default:
return def;
}
}
/******************************************************************************
** AHIsub_HardwareControl *****************************************************
******************************************************************************/
ULONG
_AHIsub_HardwareControl( ULONG attribute,
LONG argument,
struct AHIAudioCtrlDrv* AudioCtrl,
struct DriverBase* AHIsubBase )
{
struct ac97Base* ac97Base = (struct ac97Base*) AHIsubBase;
UWORD vol;
switch(attribute)
{
case AHIC_OutputVolume:
vol = LinToLog(argument);
if (vol == 0x20) vol = 0x8000;
else vol = vol | vol << 8;
D(bug("SetVol %05x translated to %04x\n", argument, vol));
dd->out_volume = argument;
if (ac97Base->mixer_set_reg)
ac97Base->mixer_set_reg(ac97Base, AC97_PCM_VOL, vol);
return TRUE;
case AHIC_OutputVolume_Query:
return dd->out_volume;
}
return 0;
}
#undef SysBase
static void play_int(HIDDT_IRQ_Handler *irq, HIDDT_IRQ_HwInfo *hw)
{
struct AHIAudioCtrlDrv* AudioCtrl;
struct DriverBase* AHIsubBase;
struct ac97Base* ac97Base;
struct ExecBase *SysBase;
AudioCtrl = (struct AHIAudioCtrlDrv*) irq->h_Data;
AHIsubBase = (struct DriverBase*) dd->ahisubbase;
ac97Base = (struct ac97Base*) AHIsubBase;
SysBase = (struct SysBase*) ac97Base->sysbase;
dd->old_SR = inw(ac97Base->dmabase + ac97Base->off_po_sr);
outw(dd->old_SR & 0x1c, ac97Base->dmabase + ac97Base->off_po_sr);
if ((dd->old_SR & 0x1c) && dd->slavetask)
{
/* Signaling the slave task */
Signal((struct Task *)dd->slavetask, SIGBREAKF_CTRL_E);
}
}
Structure of OpenSound OSS driver
editThe OSS API is designed to use the traditional Unix framework of open(), read(), write(), and ioctl(), via special devices. For instance, the default device for sound input and output is /dev/dsp.
PDF and Manual Page and sources.
Home Page and OSS BSD Source which is sometimes down.
AC97 Codec/Mixer support in /drv/ sources
via AC97 has Mixer, Read, Write, Interrupts, Audio Set Rate, Audio Set Channels, Audio IOctl, Audio Reset, Audio Reset Input and Output, Audio Open, Audio Close, Audio Start Input, Audio Trigger, Audio Prepare Input, Audio Prepare Output, Alloc Buffers, Get Buffer Pointer, SGD buffers, Control.
SBpci has SRC init, SRC Reg Read, SRC Reg Write, SRC Get Rate, SRC Set Rate, Writemem, Readmem, Interrupt, Audio Set Rate, Audio Set Channels, Audio Set Format, Audio IOctl, Audio Reset, Audio Reset Input and Output, Audio Open, Audio Close, Audio Output Block, Audio Start Input, Audio Trigger, Audio Prepare Input, Audio Prepare Output, Get Buffer Pointer, Control, Mix Init, SBPCI Attach, SBPCI Detach.
cmpci has Defines, Set SPDIF Rate, Setup AC3, Set Mixer, Get Mixer, Change Bits, Mixer Set, Outsw, Mixer IOCtl, Set Recmask, Outsw, Mix Init, Mixer Reset, Interrupts, Audio Set Rate, Audio Set Channels, Audio Set Format, Audio IOctl, Audio Reset, Audio Reset Input and Output, Audio Open, Audio Close, Audio Output Block, Audio Start Input, Audio Trigger, Set DAC Rate, Set DAC Fmt, Set ADC Rate, Set ADC Fmt, Setup Record, Audio Prepare Input, Setup Play, Audio Prepare Output, Get Buffer Pointer, Control, Mix Init, cmpci Attach, cmpci Detach.
Interrupts
editInterrupts can be triggered by the CPU (limited number) or through an additional chip which will have many more.
- asynchronously - resets or power failures
- synchronously - system calls, illegal instructions
How to adapt an OSS driver to an AHI driver
editReferences
editINTB_TIMERTICK hack was removed, do not use it any more At what rate does VERTB interrupt happen now?
How it used to be:
VERTB: always 50 Hz TIMERTICK: 100 Hz (default, could be changed with "-t" argument when starting AROS)
Some things like that AHI driver do not work well if it only happens at a rate of 50 Hz. Also a general timer precision of 50 Hz is bad. That's what the TIMERTICK hack fixed.
It was caused by the fact that sound chip did not have correctly set interrupt number. I managed to get correct IRQ by fiddling with BIOS settings.
If you look with PCITool your sound chips information and if it says something that IRQ is 255 or 0 then it is not correctly set.
I have posted about this IRQ thing before, at the moment PCI.hidd just takes the IRQ number from pci config space and does not check it's integrity. Bios may or may have not set the irq number, it all depends of your Bios settings.
the generic AC97 code separated out into an OO class which AHI drivers may utilise to provide the necessary functionality. (fwiw - an es137x driver for vmware/real hardware would also need AC97 "support")
/* SIS7012 handles the pcm data in bytes, others are in samples *
Ac97 driver supports 48 kHz audio only (this one is default to the ac97 codecs). Mixing does work (tried to run 3 copies of madahi at once with different mp3's). The driver's task works with priority 30 in order to override the input.device (well, sound should be played nicely when user moves/resizes the window, shouldn't it?) and fully utilizes interrupt :)
everybody likes to use the device interface to stream audio? In that case can you cache the sound until you have enough to send to the sound card on a hard interrupt?
Even with the low level library interface, which is made for module playing really, the driver caches the audio stream for each module interrupt (soft interrupt if you will), which is then sent to the sound card on the hardware interrupt.
Of course, this breaks if the module data plays faster than the card can handle, such as the OS4 EMU10K driver. Which was set to a 80 Hz hardware interrupt because the A1 had a problem IIRC with the normal value of 1000 Hz which worked on all other Amiga related drivers including the real thing. A module with a sound interrupt going over 80 Hz breaks and messed up the audio causing looping.