/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * Windows Sound System driver. * Supports DMA playback and sampling rates to up to 48khz. * * The WSS CODEC is more common than you might think. * Supported chips: AD1848, CS4231/A, CS4232 and compatibles. * * By Antti Koskipaa. * * See readme.txt for copyright information. * * The WSS chips (all of them, clones too) have a bug which causes them * to "swap" the LSB/MSB of each sample (or something like that) causing * nothing but a loud fuzz from the speakers on "rare" occasions. * An errata sheet on the CS4231 chip exists, but I haven't found it. * If you fix this problem, email me and tell how... * * Should I support recording? naah... =) * AD1848 can't do full duplex, and this driver is meant to be AD1848 compatible. * So, if you plan to support full duplex, check the chip revision first! */ #include "allegro.h" #include "allegro/internal/aintern.h" #include "allegro/platform/aintdos.h" #ifndef ALLEGRO_DOS #error something is wrong with the makefile #endif /* CODEC ports */ #define IADDR (_sound_port+4) #define IDATA (_sound_port+5) #define STATUS (_sound_port+6) #define PIO (_sound_port+7) /* IADDR bits */ #define INIT 0x80 #define MCE 0x40 #define TRD 0x20 /* STATUS bits */ #define INT 0x01 #define PRDY 0x02 #define PLR 0x04 #define PUL 0x08 #define SER 0x10 #define CRDY 0x20 #define CLR 0x40 #define CUL 0x80 /* IADDR registers */ #define LADC 0x00 #define RADC 0x01 #define LAUX1 0x02 #define RAUX1 0x03 #define LAUX2 0x04 #define RAUX2 0x05 #define LDAC 0x06 #define RDAC 0x07 #define FS 0x08 #define INTCON 0x09 #define PINCON 0x0A #define ERRSTAT 0x0B #define MODE_ID 0x0C #define LOOPBCK 0x0D #define PB_UCNT 0x0E #define PB_LCNT 0x0F #define NUMCODECRATES 16 /* DMA block size */ #define BLOCKLEN 512 #define WSSOUT(i, d) outportb(IADDR, i); \ outportb(IDATA, d); static int wss_detect(int input); static int wss_init(int input, int voices); static void wss_exit(int input); static int wss_mixer_volume(int volume); static int wss_buffer_size(void); static char wss_desc[256] = EMPTY_STRING; DIGI_DRIVER digi_wss = { DIGI_WINSOUNDSYS, empty_string, empty_string, "Windows Sound System", 0, 0, MIXER_MAX_SFX, MIXER_DEF_SFX, wss_detect, wss_init, wss_exit, wss_mixer_volume, NULL, NULL, wss_buffer_size, _mixer_init_voice, _mixer_release_voice, _mixer_start_voice, _mixer_stop_voice, _mixer_loop_voice, _mixer_get_position, _mixer_set_position, _mixer_get_volume, _mixer_set_volume, _mixer_ramp_volume, _mixer_stop_volume_ramp, _mixer_get_frequency, _mixer_set_frequency, _mixer_sweep_frequency, _mixer_stop_frequency_sweep, _mixer_get_pan, _mixer_set_pan, _mixer_sweep_pan, _mixer_stop_pan_sweep, _mixer_set_echo, _mixer_set_tremolo, _mixer_set_vibrato, 0, 0, NULL, NULL, NULL, NULL, NULL, NULL }; static int wss_usedrate = 0; static int wss_stereo = TRUE; static int wss_16bits = TRUE; static int wss_dma_sel; static unsigned long wss_dma_addr; static int wss_dma_block; static int wss_in_use = FALSE; static int wss_detected = FALSE; struct codec_rate_struct { int freq; int divider; }; /* List of supported rates */ static struct codec_rate_struct codec_rates[NUMCODECRATES] = { { 5512, 0x01 }, { 6620, 0x0F }, { 8000, 0x00 }, { 9600, 0x0E }, { 11025, 0x03 }, { 16000, 0x02 }, { 18900, 0x05 }, { 22050, 0x07 }, { 27420, 0x04 }, { 32000, 0x06 }, { 33075, 0x0D }, { 37800, 0x09 }, { 44100, 0x0B }, { 48000, 0x0C }, { 54857, 0x08 }, /* These last two values are ILLEGAL, { 64000, 0x0A } but they may work on some devices. Check 'em out! */ }; /* return size of audiostream buffers */ static int wss_buffer_size(void) { return BLOCKLEN / (wss_stereo ? 2 : 1) / (wss_16bits ? 2 : 1); } /* WSS busy wait */ static void wss_wait(void) { int i = 0xFFFF; /* Wait for INIT bit to clear */ while ((inportb(_sound_port + 4) & INIT) || (i-- > 0)) ; } /* Our IRQ handler. Kinda short... */ static int wss_irq_handler(void) { /* Ack int */ outportb(STATUS, 0); wss_dma_block = (_dma_todo(_sound_dma) > BLOCKLEN) ? 1 : 0; _mix_some_samples(wss_dma_addr + wss_dma_block * BLOCKLEN, _dos_ds, TRUE); _eoi(_sound_irq); return 0; } END_OF_STATIC_FUNCTION(wss_irq_handler); static int wss_detect(int input) { int i, diff, bestdiff, freq; char tmp[1024]; if (input) return FALSE; if (wss_detected) return TRUE; if ((_sound_port < 0) || (_sound_irq < 0) || (_sound_dma < 0)) return FALSE; /* Select best sampling rate */ if (_sound_freq < 0) freq = 44100; else freq = _sound_freq; bestdiff = 10000000; for (i=0; i 0)) ; if (i < 1) return -1; /* Enter MCE */ outportb(IADDR, MCE | FS); wss_wait(); /* Set playback format */ i = codec_rates[wss_usedrate].divider; if (wss_stereo) i |= 0x10; if (wss_16bits) i |= 0x40; outportb(IDATA, i); wss_wait(); outportb(IADDR, 0); outportb(STATUS, 0); outportb(IADDR, PINCON); wss_wait(); outportb(IDATA, 0x2); /* Enable interrupts in Pin Control reg. */ _enable_irq(_sound_irq); _install_irq(_map_irq(_sound_irq), wss_irq_handler); _mix_some_samples(wss_dma_addr, _dos_ds, TRUE); _mix_some_samples(wss_dma_addr + BLOCKLEN, _dos_ds, TRUE); _dma_start(_sound_dma, wss_dma_addr, BLOCKLEN * 2, TRUE, FALSE); LOCK_FUNCTION(wss_irq_handler); LOCK_VARIABLE(wss_dma_addr); LOCK_VARIABLE(wss_dma_block); LOCK_VARIABLE(_sound_dma); _eoi(_sound_irq); /* Set playback length */ outportb(IADDR, PB_UCNT); wss_wait(); outportb(IDATA, (nsamples - 1) >> 8); outportb(IADDR, PB_LCNT); wss_wait(); outportb(IDATA, (nsamples - 1) & 0xFF); wss_wait(); outportb(IADDR, INTCON | MCE); wss_wait(); outportb(IDATA, 0x5); /* PEN - Playback Enable! */ outportb(IADDR, 0); /* Unmute outputs */ WSSOUT(LDAC, 0); WSSOUT(RDAC, 0); wss_in_use = TRUE; return 0; } static void wss_exit(int input) { if (!wss_in_use) return; /* Mute outputs */ WSSOUT(LDAC, 0x80); WSSOUT(RDAC, 0x80); /* Stop playback */ outportb(IADDR, MCE | INTCON); wss_wait(); outportb(IDATA, 0); outportb(IADDR, 0); outportb(STATUS, 0); _remove_irq(_map_irq(_sound_irq)); _restore_irq(_sound_irq); __dpmi_free_dos_memory(wss_dma_sel); _mixer_exit(); wss_in_use = FALSE; } static int wss_mixer_volume(int volume) { if (volume > 255) volume = 255; if (volume < 0) volume = 0; /* WSS has only attenuation regs, so we totally mute outputs to get silence w/volume 0 */ DISABLE(); if (!volume) { WSSOUT(RDAC, 0x80); WSSOUT(LDAC, 0x80); } else { WSSOUT(RDAC, 0x3F-volume/4); WSSOUT(LDAC, 0x3F-volume/4); } ENABLE(); return 0; }