| /* |
| * 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; |
| } |