 /*
  * UAE - The Un*x Amiga Emulator
  *
  * DOS Sound interface
  *
  * (c) 1996 Gustavo Goedert
  */

#include <go32.h>
#include <dpmi.h>
#include <pc.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int DetectInitSound(int *bits, int *rate, int *bufsize);
int SB_DetectInitSound(int *bits, int *rate, int *bufsize);
void (*PlaySample)(void *buffer);

int DetectInitSound(int *bits, int *rate, int *bufsize) {
    return(SB_DetectInitSound(bits, rate, bufsize));
}

/*
 * Sound Blaster Interface
 */

int SB_BufferSize = -1;
int SB_Port = -1;
int SB_Irq = -1;
int SB_Dma = -1;
volatile int SB_IsPlaying = 0;
unsigned int SB_BufAddr[2];
int SB_BufNum = 0;
_go32_dpmi_seginfo SB_OldIrq, SB_NewIrq, SB_Buf[2];

void SB_MyHandler(void);
int SB_AllocateBuffer(int num);
inline void SB_WriteDSP(char data);
inline unsigned char SB_ReadDSP(void);
void SB_Exit(void);
void SB_PlaySample(void *buffer);

int SB_DetectInitSound(int *bits, int *rate, int *bufsize) {
    char *SB_Env, *tmp;
    unsigned char SB_Pic1, SB_Pic2;
    int i;

    *bits = 8;
    *rate = 22050;
    if ((*bufsize) > 65536)
	*bufsize = 65536;
    if ((*bufsize) < 4)
	*bufsize = 4;
    *bufsize = ((*bufsize)>>2)<<2;
    SB_BufferSize = *bufsize;
    /* Get SB parameters */
    SB_Env = getenv("BLASTER");
    do {
	tmp = strchr(SB_Env, ' ');
	if (tmp!=NULL) {
	    *tmp = '\0';
	    tmp++;
	}
	switch (*SB_Env) {
	    case 'a':
	    case 'A':
		SB_Port = strtol(SB_Env + 1, NULL, 16);
		break;
	    case 'i':
	    case 'I':
		SB_Irq = atoi(SB_Env + 1);
		break;
	    case 'd':
	    case 'D':
		SB_Dma = atoi(SB_Env + 1);
		break;
	}
	SB_Env = tmp;
    } while(SB_Env != NULL);
    if ((SB_Port == -1) || (SB_Irq == -1) || (SB_Dma == -1))
	return(0);
    if (SB_Irq > 7) {
	fprintf(stderr, "Currently Sound Blaster IRQ must be either 5 or 7.\n");
	return(0);
    }
    if (SB_Dma != 1) {
	fprintf(stderr, "Currently Sound Blaster DMA must be equal to 1.\n");
	return(0);
    }
    /* Reset SB */
    inportb(SB_Port + 0x0A);
    outportb(SB_Port + 0x06, 1);
    inportb(SB_Port + 0x06);
    inportb(SB_Port + 0x06);
    inportb(SB_Port + 0x06);
    inportb(SB_Port + 0x06);
    outportb(SB_Port + 0x06, 0);
    for (i=0; i<100; i++) {
	if (inportb(SB_Port + 0x0E) & 0x08)
	    if (inportb(SB_Port + 0x0A) == 0xAA)
		break;
    }
    if (i == 100)
	return(0);
    /* Install IRQ Handler */
    SB_NewIrq.pm_offset = (int)SB_MyHandler;
    _go32_dpmi_allocate_iret_wrapper(&SB_NewIrq);
    _go32_dpmi_get_protected_mode_interrupt_vector(SB_Irq + 8, &SB_OldIrq);
    _go32_dpmi_set_protected_mode_interrupt_vector(SB_Irq + 8, &SB_NewIrq);
    /* Alocate Buffers */
    if ((SB_AllocateBuffer(0) == 0) || (SB_AllocateBuffer(1) == 0))
	return(0);
    /* Enable interrupts on PIC */
    SB_Pic1 = inportb(0x21);
    SB_Pic2 = ~(1 << SB_Irq);
    outportb(0x21, SB_Pic1 & SB_Pic2);
    /* Set Sample Rate */
    inportb(SB_Port + 0x0E);
    SB_WriteDSP(0x40);
    SB_WriteDSP(256-(1000000/(*rate)));
    /* Turn Speaker On */
    SB_WriteDSP(0xd1);
    /* Register Cleanup Function */
    atexit(SB_Exit);
    /* Register Play Sample Function */
    PlaySample=SB_PlaySample;
    /* Lock Memory Touched by Handler */
    _go32_dpmi_lock_code(SB_MyHandler, (unsigned int)SB_AllocateBuffer-(unsigned int)SB_MyHandler);
    _go32_dpmi_lock_data(&SB_Port, sizeof(SB_Port));
    _go32_dpmi_lock_data(&SB_IsPlaying, sizeof(SB_IsPlaying));
    /* All ok */
    return(1);
}

void SB_MyHandler(void) {
    inportb(SB_Port + 0x0E);
    outportb(0x20, 0x20);
    SB_IsPlaying = 0;
}

int SB_AllocateBuffer(int num) {
    unsigned int Page;

    SB_Buf[num].size = SB_BufferSize >> 3;
    if (_go32_dpmi_allocate_dos_memory(&SB_Buf[num]))
	return(0);
    SB_BufAddr[num] = SB_Buf[num].rm_segment << 4;
    /* Force a don't cross 64k boundary */
    Page = SB_BufAddr[num] & 0xFFFF;
    if ((Page + SB_BufferSize) > 0xFFFF)
	SB_BufAddr[num] = (SB_BufAddr[num] - Page) + 0x10000;
    return(1);
}

inline void SB_WriteDSP(char data) {
    while(inportb(SB_Port + 0x0C) & 0x80);
    outportb(SB_Port + 0x0C, data);
}

inline unsigned char SB_ReadDSP(void) {
    while(!(inportb(SB_Port + 0x0E) & 0x80));
    return(inportb(SB_Port + 0x0A));
}

void SB_Exit(void) {
    /* Turn Speaker Off */
    SB_WriteDSP(0xD3);
    /* Stop any DMA transfer */
    SB_WriteDSP(0xD0);
    /* Free DOS memory */
    _go32_dpmi_free_dos_memory(&SB_Buf[0]);
    _go32_dpmi_free_dos_memory(&SB_Buf[1]);
    /* Restore old IRQ Handler */
    _go32_dpmi_set_protected_mode_interrupt_vector(SB_Irq + 8, &SB_OldIrq);
}

void SB_PlaySample(void *buffer) {
    unsigned int Size = SB_BufferSize - 1;

    asm ("
	 push %%es
	 cld
	 movw %0, %%es
	 shrl $2, %%ecx
	 rep
	 movsl
	 pop %%es
	 "
	 :
	 : "g" (_dos_ds),
	   "S" (buffer), "D" (SB_BufAddr[SB_BufNum]), "c" (SB_BufferSize)
	 : "%esi", "%edi", "%ecx", "cc"
    );
    /* Wait current sample stop playing */
    while(SB_IsPlaying);
    /* Start DMA */
    outportb(0x0A, 0x05);
    outportb(0x0C, 0x00);
    outportb(0x0B, 0x59);
    outportb(0x02, SB_BufAddr[SB_BufNum]);
    outportb(0x02, SB_BufAddr[SB_BufNum] >> 8);
    outportb(0x83, SB_BufAddr[SB_BufNum] >> 16);
    outportb(0x03, Size);
    outportb(0x03, Size >> 8);
    outportb(0x0A, 0x01);
    SB_WriteDSP(0x14);
    SB_WriteDSP(Size);
    SB_WriteDSP(Size >> 8);
    SB_IsPlaying = 1;
    SB_BufNum = !SB_BufNum;
}
