| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
|  | 2 | *  Routines for GF1 DMA control | 
|  | 3 | *  Copyright (c) by Jaroslav Kysela <perex@suse.cz> | 
|  | 4 | * | 
|  | 5 | * | 
|  | 6 | *   This program is free software; you can redistribute it and/or modify | 
|  | 7 | *   it under the terms of the GNU General Public License as published by | 
|  | 8 | *   the Free Software Foundation; either version 2 of the License, or | 
|  | 9 | *   (at your option) any later version. | 
|  | 10 | * | 
|  | 11 | *   This program is distributed in the hope that it will be useful, | 
|  | 12 | *   but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 13 | *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | 14 | *   GNU General Public License for more details. | 
|  | 15 | * | 
|  | 16 | *   You should have received a copy of the GNU General Public License | 
|  | 17 | *   along with this program; if not, write to the Free Software | 
|  | 18 | *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA | 
|  | 19 | * | 
|  | 20 | */ | 
|  | 21 |  | 
|  | 22 | #include <sound/driver.h> | 
|  | 23 | #include <asm/dma.h> | 
|  | 24 | #include <linux/slab.h> | 
|  | 25 | #include <sound/core.h> | 
|  | 26 | #include <sound/gus.h> | 
|  | 27 |  | 
|  | 28 | static void snd_gf1_dma_ack(snd_gus_card_t * gus) | 
|  | 29 | { | 
|  | 30 | unsigned long flags; | 
|  | 31 |  | 
|  | 32 | spin_lock_irqsave(&gus->reg_lock, flags); | 
|  | 33 | snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00); | 
|  | 34 | snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL); | 
|  | 35 | spin_unlock_irqrestore(&gus->reg_lock, flags); | 
|  | 36 | } | 
|  | 37 |  | 
|  | 38 | static void snd_gf1_dma_program(snd_gus_card_t * gus, | 
|  | 39 | unsigned int addr, | 
|  | 40 | unsigned long buf_addr, | 
|  | 41 | unsigned int count, | 
|  | 42 | unsigned int cmd) | 
|  | 43 | { | 
|  | 44 | unsigned long flags; | 
|  | 45 | unsigned int address; | 
|  | 46 | unsigned char dma_cmd; | 
|  | 47 | unsigned int address_high; | 
|  | 48 |  | 
|  | 49 | // snd_printk("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n", addr, (long) buf, count); | 
|  | 50 |  | 
|  | 51 | if (gus->gf1.dma1 > 3) { | 
|  | 52 | if (gus->gf1.enh_mode) { | 
|  | 53 | address = addr >> 1; | 
|  | 54 | } else { | 
|  | 55 | if (addr & 0x1f) { | 
|  | 56 | snd_printd("snd_gf1_dma_transfer: unaligned address (0x%x)?\n", addr); | 
|  | 57 | return; | 
|  | 58 | } | 
|  | 59 | address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1); | 
|  | 60 | } | 
|  | 61 | } else { | 
|  | 62 | address = addr; | 
|  | 63 | } | 
|  | 64 |  | 
|  | 65 | dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd; | 
|  | 66 | #if 0 | 
|  | 67 | dma_cmd |= 0x08; | 
|  | 68 | #endif | 
|  | 69 | if (dma_cmd & SNDRV_GF1_DMA_16BIT) { | 
|  | 70 | count++; | 
|  | 71 | count &= ~1;	/* align */ | 
|  | 72 | } | 
|  | 73 | if (gus->gf1.dma1 > 3) { | 
|  | 74 | dma_cmd |= SNDRV_GF1_DMA_WIDTH16; | 
|  | 75 | count++; | 
|  | 76 | count &= ~1;	/* align */ | 
|  | 77 | } | 
|  | 78 | snd_gf1_dma_ack(gus); | 
|  | 79 | snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE); | 
|  | 80 | #if 0 | 
|  | 81 | snd_printk("address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n", address << 1, count, dma_cmd); | 
|  | 82 | #endif | 
|  | 83 | spin_lock_irqsave(&gus->reg_lock, flags); | 
|  | 84 | if (gus->gf1.enh_mode) { | 
|  | 85 | address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f); | 
|  | 86 | snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); | 
|  | 87 | snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high); | 
|  | 88 | } else | 
|  | 89 | snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); | 
|  | 90 | snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd); | 
|  | 91 | spin_unlock_irqrestore(&gus->reg_lock, flags); | 
|  | 92 | } | 
|  | 93 |  | 
|  | 94 | static snd_gf1_dma_block_t *snd_gf1_dma_next_block(snd_gus_card_t * gus) | 
|  | 95 | { | 
|  | 96 | snd_gf1_dma_block_t *block; | 
|  | 97 |  | 
|  | 98 | /* PCM block have bigger priority than synthesizer one */ | 
|  | 99 | if (gus->gf1.dma_data_pcm) { | 
|  | 100 | block = gus->gf1.dma_data_pcm; | 
|  | 101 | if (gus->gf1.dma_data_pcm_last == block) { | 
|  | 102 | gus->gf1.dma_data_pcm = | 
|  | 103 | gus->gf1.dma_data_pcm_last = NULL; | 
|  | 104 | } else { | 
|  | 105 | gus->gf1.dma_data_pcm = block->next; | 
|  | 106 | } | 
|  | 107 | } else if (gus->gf1.dma_data_synth) { | 
|  | 108 | block = gus->gf1.dma_data_synth; | 
|  | 109 | if (gus->gf1.dma_data_synth_last == block) { | 
|  | 110 | gus->gf1.dma_data_synth = | 
|  | 111 | gus->gf1.dma_data_synth_last = NULL; | 
|  | 112 | } else { | 
|  | 113 | gus->gf1.dma_data_synth = block->next; | 
|  | 114 | } | 
|  | 115 | } else { | 
|  | 116 | block = NULL; | 
|  | 117 | } | 
|  | 118 | if (block) { | 
|  | 119 | gus->gf1.dma_ack = block->ack; | 
|  | 120 | gus->gf1.dma_private_data = block->private_data; | 
|  | 121 | } | 
|  | 122 | return block; | 
|  | 123 | } | 
|  | 124 |  | 
|  | 125 |  | 
|  | 126 | static void snd_gf1_dma_interrupt(snd_gus_card_t * gus) | 
|  | 127 | { | 
|  | 128 | snd_gf1_dma_block_t *block; | 
|  | 129 |  | 
|  | 130 | snd_gf1_dma_ack(gus); | 
|  | 131 | if (gus->gf1.dma_ack) | 
|  | 132 | gus->gf1.dma_ack(gus, gus->gf1.dma_private_data); | 
|  | 133 | spin_lock(&gus->dma_lock); | 
|  | 134 | if (gus->gf1.dma_data_pcm == NULL && | 
|  | 135 | gus->gf1.dma_data_synth == NULL) { | 
|  | 136 | gus->gf1.dma_ack = NULL; | 
|  | 137 | gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER; | 
|  | 138 | spin_unlock(&gus->dma_lock); | 
|  | 139 | return; | 
|  | 140 | } | 
|  | 141 | block = snd_gf1_dma_next_block(gus); | 
|  | 142 | spin_unlock(&gus->dma_lock); | 
|  | 143 | snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); | 
|  | 144 | kfree(block); | 
|  | 145 | #if 0 | 
|  | 146 | printk("program dma (IRQ) - addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", addr, (long) buffer, count, cmd); | 
|  | 147 | #endif | 
|  | 148 | } | 
|  | 149 |  | 
|  | 150 | int snd_gf1_dma_init(snd_gus_card_t * gus) | 
|  | 151 | { | 
|  | 152 | down(&gus->dma_mutex); | 
|  | 153 | gus->gf1.dma_shared++; | 
|  | 154 | if (gus->gf1.dma_shared > 1) { | 
|  | 155 | up(&gus->dma_mutex); | 
|  | 156 | return 0; | 
|  | 157 | } | 
|  | 158 | gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt; | 
|  | 159 | gus->gf1.dma_data_pcm = | 
|  | 160 | gus->gf1.dma_data_pcm_last = | 
|  | 161 | gus->gf1.dma_data_synth = | 
|  | 162 | gus->gf1.dma_data_synth_last = NULL; | 
|  | 163 | up(&gus->dma_mutex); | 
|  | 164 | return 0; | 
|  | 165 | } | 
|  | 166 |  | 
|  | 167 | int snd_gf1_dma_done(snd_gus_card_t * gus) | 
|  | 168 | { | 
|  | 169 | snd_gf1_dma_block_t *block; | 
|  | 170 |  | 
|  | 171 | down(&gus->dma_mutex); | 
|  | 172 | gus->gf1.dma_shared--; | 
|  | 173 | if (!gus->gf1.dma_shared) { | 
|  | 174 | snd_dma_disable(gus->gf1.dma1); | 
|  | 175 | snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE); | 
|  | 176 | snd_gf1_dma_ack(gus); | 
|  | 177 | while ((block = gus->gf1.dma_data_pcm)) { | 
|  | 178 | gus->gf1.dma_data_pcm = block->next; | 
|  | 179 | kfree(block); | 
|  | 180 | } | 
|  | 181 | while ((block = gus->gf1.dma_data_synth)) { | 
|  | 182 | gus->gf1.dma_data_synth = block->next; | 
|  | 183 | kfree(block); | 
|  | 184 | } | 
|  | 185 | gus->gf1.dma_data_pcm_last = | 
|  | 186 | gus->gf1.dma_data_synth_last = NULL; | 
|  | 187 | } | 
|  | 188 | up(&gus->dma_mutex); | 
|  | 189 | return 0; | 
|  | 190 | } | 
|  | 191 |  | 
|  | 192 | int snd_gf1_dma_transfer_block(snd_gus_card_t * gus, | 
|  | 193 | snd_gf1_dma_block_t * __block, | 
|  | 194 | int atomic, | 
|  | 195 | int synth) | 
|  | 196 | { | 
|  | 197 | unsigned long flags; | 
|  | 198 | snd_gf1_dma_block_t *block; | 
|  | 199 |  | 
|  | 200 | block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL); | 
|  | 201 | if (block == NULL) { | 
|  | 202 | snd_printk("gf1: DMA transfer failure; not enough memory\n"); | 
|  | 203 | return -ENOMEM; | 
|  | 204 | } | 
|  | 205 | *block = *__block; | 
|  | 206 | block->next = NULL; | 
|  | 207 | #if 0 | 
|  | 208 | printk("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", block->addr, (long) block->buffer, block->count, block->cmd); | 
|  | 209 | #endif | 
|  | 210 | #if 0 | 
|  | 211 | printk("gus->gf1.dma_data_pcm_last = 0x%lx\n", (long)gus->gf1.dma_data_pcm_last); | 
|  | 212 | printk("gus->gf1.dma_data_pcm = 0x%lx\n", (long)gus->gf1.dma_data_pcm); | 
|  | 213 | #endif | 
|  | 214 | spin_lock_irqsave(&gus->dma_lock, flags); | 
|  | 215 | if (synth) { | 
|  | 216 | if (gus->gf1.dma_data_synth_last) { | 
|  | 217 | gus->gf1.dma_data_synth_last->next = block; | 
|  | 218 | gus->gf1.dma_data_synth_last = block; | 
|  | 219 | } else { | 
|  | 220 | gus->gf1.dma_data_synth = | 
|  | 221 | gus->gf1.dma_data_synth_last = block; | 
|  | 222 | } | 
|  | 223 | } else { | 
|  | 224 | if (gus->gf1.dma_data_pcm_last) { | 
|  | 225 | gus->gf1.dma_data_pcm_last->next = block; | 
|  | 226 | gus->gf1.dma_data_pcm_last = block; | 
|  | 227 | } else { | 
|  | 228 | gus->gf1.dma_data_pcm = | 
|  | 229 | gus->gf1.dma_data_pcm_last = block; | 
|  | 230 | } | 
|  | 231 | } | 
|  | 232 | if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) { | 
|  | 233 | gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER; | 
|  | 234 | block = snd_gf1_dma_next_block(gus); | 
|  | 235 | spin_unlock_irqrestore(&gus->dma_lock, flags); | 
|  | 236 | if (block == NULL) | 
|  | 237 | return 0; | 
|  | 238 | snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); | 
|  | 239 | kfree(block); | 
|  | 240 | return 0; | 
|  | 241 | } | 
|  | 242 | spin_unlock_irqrestore(&gus->dma_lock, flags); | 
|  | 243 | return 0; | 
|  | 244 | } |