summaryrefslogtreecommitdiff
path: root/sound/isa/gus/gus_dma.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/isa/gus/gus_dma.c')
-rw-r--r--sound/isa/gus/gus_dma.c250
1 files changed, 250 insertions, 0 deletions
diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c
new file mode 100644
index 000000000..36c27c832
--- /dev/null
+++ b/sound/isa/gus/gus_dma.c
@@ -0,0 +1,250 @@
+/*
+ * Routines for GF1 DMA control
+ * Copyright (c) by Jaroslav Kysela <perex@perex.cz>
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <asm/dma.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/gus.h>
+
+static void snd_gf1_dma_ack(struct snd_gus_card * gus)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00);
+ snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static void snd_gf1_dma_program(struct snd_gus_card * gus,
+ unsigned int addr,
+ unsigned long buf_addr,
+ unsigned int count,
+ unsigned int cmd)
+{
+ unsigned long flags;
+ unsigned int address;
+ unsigned char dma_cmd;
+ unsigned int address_high;
+
+ snd_printdd("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n",
+ addr, buf_addr, count);
+
+ if (gus->gf1.dma1 > 3) {
+ if (gus->gf1.enh_mode) {
+ address = addr >> 1;
+ } else {
+ if (addr & 0x1f) {
+ snd_printd("snd_gf1_dma_transfer: unaligned address (0x%x)?\n", addr);
+ return;
+ }
+ address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1);
+ }
+ } else {
+ address = addr;
+ }
+
+ dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd;
+#if 0
+ dma_cmd |= 0x08;
+#endif
+ if (dma_cmd & SNDRV_GF1_DMA_16BIT) {
+ count++;
+ count &= ~1; /* align */
+ }
+ if (gus->gf1.dma1 > 3) {
+ dma_cmd |= SNDRV_GF1_DMA_WIDTH16;
+ count++;
+ count &= ~1; /* align */
+ }
+ snd_gf1_dma_ack(gus);
+ snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE);
+#if 0
+ snd_printk(KERN_DEBUG "address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n",
+ address << 1, count, dma_cmd);
+#endif
+ spin_lock_irqsave(&gus->reg_lock, flags);
+ if (gus->gf1.enh_mode) {
+ address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f);
+ snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high);
+ } else
+ snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4));
+ snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd);
+ spin_unlock_irqrestore(&gus->reg_lock, flags);
+}
+
+static struct snd_gf1_dma_block *snd_gf1_dma_next_block(struct snd_gus_card * gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ /* PCM block have bigger priority than synthesizer one */
+ if (gus->gf1.dma_data_pcm) {
+ block = gus->gf1.dma_data_pcm;
+ if (gus->gf1.dma_data_pcm_last == block) {
+ gus->gf1.dma_data_pcm =
+ gus->gf1.dma_data_pcm_last = NULL;
+ } else {
+ gus->gf1.dma_data_pcm = block->next;
+ }
+ } else if (gus->gf1.dma_data_synth) {
+ block = gus->gf1.dma_data_synth;
+ if (gus->gf1.dma_data_synth_last == block) {
+ gus->gf1.dma_data_synth =
+ gus->gf1.dma_data_synth_last = NULL;
+ } else {
+ gus->gf1.dma_data_synth = block->next;
+ }
+ } else {
+ block = NULL;
+ }
+ if (block) {
+ gus->gf1.dma_ack = block->ack;
+ gus->gf1.dma_private_data = block->private_data;
+ }
+ return block;
+}
+
+
+static void snd_gf1_dma_interrupt(struct snd_gus_card * gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ snd_gf1_dma_ack(gus);
+ if (gus->gf1.dma_ack)
+ gus->gf1.dma_ack(gus, gus->gf1.dma_private_data);
+ spin_lock(&gus->dma_lock);
+ if (gus->gf1.dma_data_pcm == NULL &&
+ gus->gf1.dma_data_synth == NULL) {
+ gus->gf1.dma_ack = NULL;
+ gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER;
+ spin_unlock(&gus->dma_lock);
+ return;
+ }
+ block = snd_gf1_dma_next_block(gus);
+ spin_unlock(&gus->dma_lock);
+ snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+ kfree(block);
+#if 0
+ snd_printd(KERN_DEBUG "program dma (IRQ) - "
+ "addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+ block->addr, block->buf_addr, block->count, block->cmd);
+#endif
+}
+
+int snd_gf1_dma_init(struct snd_gus_card * gus)
+{
+ mutex_lock(&gus->dma_mutex);
+ gus->gf1.dma_shared++;
+ if (gus->gf1.dma_shared > 1) {
+ mutex_unlock(&gus->dma_mutex);
+ return 0;
+ }
+ gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt;
+ gus->gf1.dma_data_pcm =
+ gus->gf1.dma_data_pcm_last =
+ gus->gf1.dma_data_synth =
+ gus->gf1.dma_data_synth_last = NULL;
+ mutex_unlock(&gus->dma_mutex);
+ return 0;
+}
+
+int snd_gf1_dma_done(struct snd_gus_card * gus)
+{
+ struct snd_gf1_dma_block *block;
+
+ mutex_lock(&gus->dma_mutex);
+ gus->gf1.dma_shared--;
+ if (!gus->gf1.dma_shared) {
+ snd_dma_disable(gus->gf1.dma1);
+ snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE);
+ snd_gf1_dma_ack(gus);
+ while ((block = gus->gf1.dma_data_pcm)) {
+ gus->gf1.dma_data_pcm = block->next;
+ kfree(block);
+ }
+ while ((block = gus->gf1.dma_data_synth)) {
+ gus->gf1.dma_data_synth = block->next;
+ kfree(block);
+ }
+ gus->gf1.dma_data_pcm_last =
+ gus->gf1.dma_data_synth_last = NULL;
+ }
+ mutex_unlock(&gus->dma_mutex);
+ return 0;
+}
+
+int snd_gf1_dma_transfer_block(struct snd_gus_card * gus,
+ struct snd_gf1_dma_block * __block,
+ int atomic,
+ int synth)
+{
+ unsigned long flags;
+ struct snd_gf1_dma_block *block;
+
+ block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL);
+ if (block == NULL) {
+ snd_printk(KERN_ERR "gf1: DMA transfer failure; not enough memory\n");
+ return -ENOMEM;
+ }
+ *block = *__block;
+ block->next = NULL;
+
+ snd_printdd("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n",
+ block->addr, (long) block->buffer, block->count,
+ block->cmd);
+
+ snd_printdd("gus->gf1.dma_data_pcm_last = 0x%lx\n",
+ (long)gus->gf1.dma_data_pcm_last);
+ snd_printdd("gus->gf1.dma_data_pcm = 0x%lx\n",
+ (long)gus->gf1.dma_data_pcm);
+
+ spin_lock_irqsave(&gus->dma_lock, flags);
+ if (synth) {
+ if (gus->gf1.dma_data_synth_last) {
+ gus->gf1.dma_data_synth_last->next = block;
+ gus->gf1.dma_data_synth_last = block;
+ } else {
+ gus->gf1.dma_data_synth =
+ gus->gf1.dma_data_synth_last = block;
+ }
+ } else {
+ if (gus->gf1.dma_data_pcm_last) {
+ gus->gf1.dma_data_pcm_last->next = block;
+ gus->gf1.dma_data_pcm_last = block;
+ } else {
+ gus->gf1.dma_data_pcm =
+ gus->gf1.dma_data_pcm_last = block;
+ }
+ }
+ if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) {
+ gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER;
+ block = snd_gf1_dma_next_block(gus);
+ spin_unlock_irqrestore(&gus->dma_lock, flags);
+ if (block == NULL)
+ return 0;
+ snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd);
+ kfree(block);
+ return 0;
+ }
+ spin_unlock_irqrestore(&gus->dma_lock, flags);
+ return 0;
+}