📄 gus_pcm.c
字号:
/* * Copyright (c) by Jaroslav Kysela <perex@suse.cz> * Routines for control of GF1 chip (PCM things) * * InterWave chips supports interleaved DMA, but this feature isn't used in * this code. * * This code emulates autoinit DMA transfer for playback, recording by GF1 * chip doesn't support autoinit DMA. * * * 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 <sound/driver.h>#include <asm/dma.h>#include <linux/slab.h>#include <sound/core.h>#include <sound/control.h>#include <sound/gus.h>#include <sound/pcm_params.h>#include "gus_tables.h"/* maximum rate */#define SNDRV_GF1_PCM_RATE 48000#define SNDRV_GF1_PCM_PFLG_NONE 0#define SNDRV_GF1_PCM_PFLG_ACTIVE (1<<0)#define SNDRV_GF1_PCM_PFLG_NEUTRAL (2<<0)typedef struct { snd_gus_card_t * gus; snd_pcm_substream_t * substream; spinlock_t lock; unsigned int voices; snd_gus_voice_t *pvoices[2]; unsigned int memory; unsigned short flags; unsigned char voice_ctrl, ramp_ctrl; unsigned int bpos; unsigned int blocks; unsigned int block_size; unsigned int dma_size; wait_queue_head_t sleep; atomic_t dma_count; int final_volume;} gus_pcm_private_t;static int snd_gf1_pcm_use_dma = 1;static void snd_gf1_pcm_block_change_ack(snd_gus_card_t * gus, void *private_data){ gus_pcm_private_t *pcmp = private_data; if (pcmp) { atomic_dec(&pcmp->dma_count); wake_up(&pcmp->sleep); }}static int snd_gf1_pcm_block_change(snd_pcm_substream_t * substream, unsigned int offset, unsigned int addr, unsigned int count){ snd_gf1_dma_block_t block; snd_pcm_runtime_t *runtime = substream->runtime; gus_pcm_private_t *pcmp = runtime->private_data; count += offset & 31; offset &= ~31; // snd_printk("block change - offset = 0x%x, count = 0x%x\n", offset, count); memset(&block, 0, sizeof(block)); block.cmd = SNDRV_GF1_DMA_IRQ; if (snd_pcm_format_unsigned(runtime->format)) block.cmd |= SNDRV_GF1_DMA_UNSIGNED; if (snd_pcm_format_width(runtime->format) == 16) block.cmd |= SNDRV_GF1_DMA_16BIT; block.addr = addr & ~31; block.buffer = runtime->dma_area + offset; block.buf_addr = runtime->dma_addr + offset; block.count = count; block.private_data = pcmp; block.ack = snd_gf1_pcm_block_change_ack; if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0)) atomic_inc(&pcmp->dma_count); return 0;}static void snd_gf1_pcm_trigger_up(snd_pcm_substream_t * substream){ snd_pcm_runtime_t *runtime = substream->runtime; gus_pcm_private_t *pcmp = runtime->private_data; snd_gus_card_t * gus = pcmp->gus; unsigned long flags; unsigned char voice_ctrl, ramp_ctrl; unsigned short rate; unsigned int curr, begin, end; unsigned short vol; unsigned char pan; unsigned int voice; if (substream == NULL) return; spin_lock_irqsave(&pcmp->lock, flags); if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) { spin_unlock_irqrestore(&pcmp->lock, flags); return; } pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE; pcmp->final_volume = 0; spin_unlock_irqrestore(&pcmp->lock, flags); rate = snd_gf1_translate_freq(gus, runtime->rate << 4); /* enable WAVE IRQ */ voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20; /* enable RAMP IRQ + rollover */ ramp_ctrl = 0x24; if (pcmp->blocks == 1) { voice_ctrl |= 0x08; /* loop enable */ ramp_ctrl &= ~0x04; /* disable rollover */ } for (voice = 0; voice < pcmp->voices; voice++) { begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels); curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels; end = curr + (pcmp->block_size / runtime->channels); end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1; // snd_printk("init: curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x, rate=0x%x\n", curr, begin, end, voice_ctrl, ramp_ctrl, rate); pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8; vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; spin_lock_irqsave(&gus->reg_lock, flags); snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan); snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate); snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4); snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4); snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4); snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f); snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8); snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); if (!gus->gf1.enh_mode) { snd_gf1_delay(gus); snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); } spin_unlock_irqrestore(&gus->reg_lock, flags); } spin_lock_irqsave(&gus->reg_lock, flags); for (voice = 0; voice < pcmp->voices; voice++) { snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); if (gus->gf1.enh_mode) snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00); /* deactivate voice */ snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); voice_ctrl &= ~0x20; } voice_ctrl |= 0x20; if (!gus->gf1.enh_mode) { snd_gf1_delay(gus); for (voice = 0; voice < pcmp->voices; voice++) { snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); voice_ctrl &= ~0x20; /* disable IRQ for next voice */ } } spin_unlock_irqrestore(&gus->reg_lock, flags);}static void snd_gf1_pcm_interrupt_wave(snd_gus_card_t * gus, snd_gus_voice_t *pvoice){ gus_pcm_private_t * pcmp; snd_pcm_runtime_t * runtime; unsigned char voice_ctrl, ramp_ctrl; unsigned int idx; unsigned int end, step; if (!pvoice->private_data) { snd_printd("snd_gf1_pcm: unknown wave irq?\n"); snd_gf1_smart_stop_voice(gus, pvoice->number); return; } pcmp = pvoice->private_data; if (pcmp == NULL) { snd_printd("snd_gf1_pcm: unknown wave irq?\n"); snd_gf1_smart_stop_voice(gus, pvoice->number); return; } gus = pcmp->gus; runtime = pcmp->substream->runtime; spin_lock(&gus->reg_lock); snd_gf1_select_voice(gus, pvoice->number); voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b; ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03;#if 0 snd_gf1_select_voice(gus, pvoice->number); printk("position = 0x%x\n", (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); snd_gf1_select_voice(gus, pcmp->pvoices[1]->number); printk("position = 0x%x\n", (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); snd_gf1_select_voice(gus, pvoice->number);#endif pcmp->bpos++; pcmp->bpos %= pcmp->blocks; if (pcmp->bpos + 1 >= pcmp->blocks) { /* last block? */ voice_ctrl |= 0x08; /* enable loop */ } else { ramp_ctrl |= 0x04; /* enable rollover */ } end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels); end -= voice_ctrl & 4 ? 2 : 1; step = pcmp->dma_size / runtime->channels; voice_ctrl |= 0x20; if (!pcmp->final_volume) { ramp_ctrl |= 0x20; ramp_ctrl &= ~0x03; } for (idx = 0; idx < pcmp->voices; idx++, end += step) { snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); voice_ctrl &= ~0x20; } if (!gus->gf1.enh_mode) { snd_gf1_delay(gus); voice_ctrl |= 0x20; for (idx = 0; idx < pcmp->voices; idx++) { snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); voice_ctrl &= ~0x20; } } spin_unlock(&gus->reg_lock); snd_pcm_period_elapsed(pcmp->substream);#if 0 if ((runtime->flags & SNDRV_PCM_FLG_MMAP) && *runtime->state == SNDRV_PCM_STATE_RUNNING) { end = pcmp->bpos * pcmp->block_size; if (runtime->channels > 1) { snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + (end / 2), pcmp->block_size / 2); snd_gf1_pcm_block_change(pcmp->substream, end + (pcmp->block_size / 2), pcmp->memory + (pcmp->dma_size / 2) + (end / 2), pcmp->block_size / 2); } else { snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + end, pcmp->block_size); } }#endif}static void snd_gf1_pcm_interrupt_volume(snd_gus_card_t * gus, snd_gus_voice_t * pvoice){ unsigned short vol; int cvoice; gus_pcm_private_t *pcmp = pvoice->private_data; /* stop ramp, but leave rollover bit untouched */ spin_lock(&gus->reg_lock); snd_gf1_select_voice(gus, pvoice->number); snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); spin_unlock(&gus->reg_lock); if (pcmp == NULL) return; /* are we active? */ if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE)) return; /* load real volume - better precision */ cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1; if (pcmp->substream == NULL) return; vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; spin_lock(&gus->reg_lock); snd_gf1_select_voice(gus, pvoice->number); snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol); pcmp->final_volume = 1; spin_unlock(&gus->reg_lock);}static void snd_gf1_pcm_volume_change(snd_gus_card_t * gus){}static int snd_gf1_pcm_poke_block(snd_gus_card_t *gus, unsigned char *buf, unsigned int pos, unsigned int count, int w16, int invert){ unsigned int len; unsigned long flags; // printk("poke block; buf = 0x%x, pos = %i, count = %i, port = 0x%x\n", (int)buf, pos, count, gus->gf1.port); while (count > 0) { len = count; if (len > 512) /* limit, to allow IRQ */ len = 512; count -= len; if (gus->interwave) { spin_lock_irqsave(&gus->reg_lock, flags); snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01 | (invert ? 0x08 : 0x00)); snd_gf1_dram_addr(gus, pos); if (w16) { outb(SNDRV_GF1_GW_DRAM_IO16, GUSP(gus, GF1REGSEL)); outsw(GUSP(gus, GF1DATALOW), buf, len >> 1); } else { outsb(GUSP(gus, DRAM), buf, len); } spin_unlock_irqrestore(&gus->reg_lock, flags); buf += 512; pos += 512; } else { invert = invert ? 0x80 : 0x00; if (w16) { len >>= 1; while (len--) { snd_gf1_poke(gus, pos++, *buf++); snd_gf1_poke(gus, pos++, *buf++ ^ invert); } } else { while (len--) snd_gf1_poke(gus, pos++, *buf++ ^ invert); } } if (count > 0 && !in_interrupt()) { schedule_timeout_interruptible(1); if (signal_pending(current)) return -EAGAIN; } } return 0;}static int snd_gf1_pcm_playback_copy(snd_pcm_substream_t *substream, int voice, snd_pcm_uframes_t pos, void __user *src, snd_pcm_uframes_t count){ snd_pcm_runtime_t *runtime = substream->runtime; gus_pcm_private_t *pcmp = runtime->private_data; unsigned int bpos, len; bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2)); len = samples_to_bytes(runtime, count); snd_assert(bpos <= pcmp->dma_size, return -EIO); snd_assert(bpos + len <= pcmp->dma_size, return -EIO); if (copy_from_user(runtime->dma_area + bpos, src, len)) return -EFAULT; if (snd_gf1_pcm_use_dma && len > 32) { return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len); } else { snd_gus_card_t *gus = pcmp->gus; int err, w16, invert; w16 = (snd_pcm_format_width(runtime->format) == 16); invert = snd_pcm_format_unsigned(runtime->format); if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0) return err; } return 0;}static int snd_gf1_pcm_playback_silence(snd_pcm_substream_t *substream, int voice, snd_pcm_uframes_t pos, snd_pcm_uframes_t count){ snd_pcm_runtime_t *runtime = substream->runtime; gus_pcm_private_t *pcmp = runtime->private_data; unsigned int bpos, len; bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2)); len = samples_to_bytes(runtime, count); snd_assert(bpos <= pcmp->dma_size, return -EIO); snd_assert(bpos + len <= pcmp->dma_size, return -EIO); snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos, count); if (snd_gf1_pcm_use_dma && len > 32) { return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len); } else { snd_gus_card_t *gus = pcmp->gus; int err, w16, invert; w16 = (snd_pcm_format_width(runtime->format) == 16); invert = snd_pcm_format_unsigned(runtime->format); if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0) return err; } return 0;}static int snd_gf1_pcm_playback_hw_params(snd_pcm_substream_t * substream, snd_pcm_hw_params_t * hw_params){ snd_gus_card_t *gus = snd_pcm_substream_chip(substream); snd_pcm_runtime_t *runtime = substream->runtime; gus_pcm_private_t *pcmp = runtime->private_data; int err; if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) return err; if (err > 0) { /* change */ snd_gf1_mem_block_t *block; if (pcmp->memory > 0) { snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory); pcmp->memory = 0; } if ((block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc, SNDRV_GF1_MEM_OWNER_DRIVER, "GF1 PCM", runtime->dma_bytes, 1, 32, NULL)) == NULL) return -ENOMEM; pcmp->memory = block->ptr; } pcmp->voices = params_channels(hw_params); if (pcmp->pvoices[0] == NULL) { if ((pcmp->pvoices[0] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL) return -ENOMEM; pcmp->pvoices[0]->handler_wave = snd_gf1_pcm_interrupt_wave; pcmp->pvoices[0]->handler_volume = snd_gf1_pcm_interrupt_volume; pcmp->pvoices[0]->volume_change = snd_gf1_pcm_volume_change; pcmp->pvoices[0]->private_data = pcmp; } if (pcmp->voices > 1 && pcmp->pvoices[1] == NULL) { if ((pcmp->pvoices[1] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL) return -ENOMEM; pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave; pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume; pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change; pcmp->pvoices[1]->private_data = pcmp; } else if (pcmp->voices == 1) { if (pcmp->pvoices[1]) { snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]); pcmp->pvoices[1] = NULL; } } return 0;}static int snd_gf1_pcm_playback_hw_free(snd_pcm_substream_t * substream)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -