diff options
Diffstat (limited to 'hw/gus.c')
-rw-r--r-- | hw/gus.c | 300 |
1 files changed, 300 insertions, 0 deletions
diff --git a/hw/gus.c b/hw/gus.c new file mode 100644 index 0000000000..57753a7f5c --- /dev/null +++ b/hw/gus.c @@ -0,0 +1,300 @@ +/* + * QEMU Proxy for Gravis Ultrasound GF1 emulation by Tibor "TS" Schütz + * + * Copyright (c) 2002-2005 Vassili Karpov (malc) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw.h" +#include "audiodev.h" +#include "audio/audio.h" +#include "isa.h" +#include "gusemu.h" +#include "gustate.h" + +#define dolog(...) AUD_log ("audio", __VA_ARGS__) +#ifdef DEBUG +#define ldebug(...) dolog (__VA_ARGS__) +#else +#define ldebug(...) +#endif + +#ifdef WORDS_BIGENDIAN +#define GUS_ENDIANNESS 1 +#else +#define GUS_ENDIANNESS 0 +#endif + +#define IO_READ_PROTO(name) \ + uint32_t name (void *opaque, uint32_t nport) +#define IO_WRITE_PROTO(name) \ + void name (void *opaque, uint32_t nport, uint32_t val) + +static struct { + int port; + int irq; + int dma; + int freq; +} conf = {0x240, 7, 3, 44100}; + +typedef struct GUSState { + GUSEmuState emu; + QEMUSoundCard card; + int freq; + int pos, left, shift, irqs; + uint16_t *mixbuf; + uint8_t himem[1024 * 1024 + 32 + 4096]; + int samples; + SWVoiceOut *voice; + int64_t last_ticks; + qemu_irq *pic; +} GUSState; + +IO_READ_PROTO (gus_readb) +{ + GUSState *s = opaque; + + return gus_read (&s->emu, nport, 1); +} + +IO_READ_PROTO (gus_readw) +{ + GUSState *s = opaque; + + return gus_read (&s->emu, nport, 2); +} + +IO_WRITE_PROTO (gus_writeb) +{ + GUSState *s = opaque; + + gus_write (&s->emu, nport, 1, val); +} + +IO_WRITE_PROTO (gus_writew) +{ + GUSState *s = opaque; + + gus_write (&s->emu, nport, 2, val); +} + +static int write_audio (GUSState *s, int samples) +{ + int net = 0; + int pos = s->pos; + + while (samples) { + int nbytes, wbytes, wsampl; + + nbytes = samples << s->shift; + wbytes = AUD_write ( + s->voice, + s->mixbuf + (pos << (s->shift - 1)), + nbytes + ); + + if (wbytes) { + wsampl = wbytes >> s->shift; + + samples -= wsampl; + pos = (pos + wsampl) % s->samples; + + net += wsampl; + } + else { + break; + } + } + + return net; +} + +static void GUS_callback (void *opaque, int free) +{ + int samples, to_play, net = 0; + GUSState *s = opaque; + + samples = free >> s->shift; + to_play = audio_MIN (samples, s->left); + + while (to_play) { + int written = write_audio (s, to_play); + + if (!written) { + goto reset; + } + + s->left -= written; + to_play -= written; + samples -= written; + net += written; + } + + samples = audio_MIN (samples, s->samples); + if (samples) { + gus_mixvoices (&s->emu, s->freq, samples, s->mixbuf); + + while (samples) { + int written = write_audio (s, samples); + if (!written) { + break; + } + samples -= written; + net += written; + } + } + s->left = samples; + +reset: + gus_irqgen (&s->emu, (double) (net * 1000000) / s->freq); +} + +int GUS_irqrequest (GUSEmuState *emu, int hwirq, int n) +{ + GUSState *s = emu->opaque; + /* qemu_irq_lower (s->pic[hwirq]); */ + qemu_irq_raise (s->pic[hwirq]); + s->irqs += n; + ldebug ("irqrequest %d %d %d\n", hwirq, n, s->irqs); + return n; +} + +void GUS_irqclear (GUSEmuState *emu, int hwirq) +{ + GUSState *s = emu->opaque; + ldebug ("irqclear %d %d\n", hwirq, s->irqs); + qemu_irq_lower (s->pic[hwirq]); + s->irqs -= 1; +#ifdef IRQ_STORM + if (s->irqs > 0) { + qemu_irq_raise (s->pic[hwirq]); + } +#endif +} + +void GUS_dmarequest (GUSEmuState *der) +{ + /* GUSState *s = (GUSState *) der; */ + ldebug ("dma request %d\n", der->gusdma); + DMA_hold_DREQ (der->gusdma); +} + +int GUS_read_DMA (void *opaque, int nchan, int dma_pos, int dma_len) +{ + GUSState *s = opaque; + int8_t tmpbuf[4096]; + int pos = dma_pos, mode, left = dma_len - dma_pos; + + ldebug ("read DMA %#x %d\n", dma_pos, dma_len); + mode = DMA_get_channel_mode (s->emu.gusdma); + while (left) { + int to_copy = audio_MIN ((size_t) left, sizeof (tmpbuf)); + int copied; + + ldebug ("left=%d to_copy=%d pos=%d\n", left, to_copy, pos); + copied = DMA_read_memory (nchan, tmpbuf, pos, to_copy); + gus_dma_transferdata (&s->emu, tmpbuf, copied, left == copied); + left -= copied; + pos += copied; + } + + if (0 == ((mode >> 4) & 1)) { + DMA_release_DREQ (s->emu.gusdma); + } + return dma_len; +} + +int GUS_init (AudioState *audio, qemu_irq *pic) +{ + GUSState *s; + audsettings_t as; + + if (!audio) { + dolog ("No audio state\n"); + return -1; + } + + s = qemu_mallocz (sizeof (*s)); + if (!s) { + dolog ("Could not allocate memory for GUS (%zu bytes)\n", + sizeof (*s)); + return -1; + } + + AUD_register_card (audio, "gus", &s->card); + + as.freq = conf.freq; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = GUS_ENDIANNESS; + + s->voice = AUD_open_out ( + &s->card, + NULL, + "gus", + s, + GUS_callback, + &as + ); + + if (!s->voice) { + AUD_remove_card (&s->card); + qemu_free (s); + return -1; + } + + s->shift = 2; + s->samples = AUD_get_buffer_size_out (s->voice) >> s->shift; + s->mixbuf = qemu_mallocz (s->samples << s->shift); + if (!s->mixbuf) { + AUD_close_out (&s->card, s->voice); + AUD_remove_card (&s->card); + qemu_free (s); + return -1; + } + + register_ioport_write (conf.port, 1, 1, gus_writeb, s); + register_ioport_write (conf.port, 1, 2, gus_writew, s); + + register_ioport_read ((conf.port + 0x100) & 0xf00, 1, 1, gus_readb, s); + register_ioport_read ((conf.port + 0x100) & 0xf00, 1, 2, gus_readw, s); + + register_ioport_write (conf.port + 6, 10, 1, gus_writeb, s); + register_ioport_write (conf.port + 6, 10, 2, gus_writew, s); + register_ioport_read (conf.port + 6, 10, 1, gus_readb, s); + register_ioport_read (conf.port + 6, 10, 2, gus_readw, s); + + + register_ioport_write (conf.port + 0x100, 8, 1, gus_writeb, s); + register_ioport_write (conf.port + 0x100, 8, 2, gus_writew, s); + register_ioport_read (conf.port + 0x100, 8, 1, gus_readb, s); + register_ioport_read (conf.port + 0x100, 8, 2, gus_readw, s); + + DMA_register_channel (conf.dma, GUS_read_DMA, s); + s->emu.gusirq = conf.irq; + s->emu.gusdma = conf.dma; + s->emu.himemaddr = s->himem; + s->emu.gusdatapos = s->emu.himemaddr + 1024 * 1024 + 32; + s->emu.opaque = s; + s->freq = conf.freq; + s->pic = pic; + + AUD_set_active_out (s->voice, 1); + return 0; +} |