⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 gus_pcm.c

📁 linux-2.6.15.6
💻 C
📖 第 1 页 / 共 2 页
字号:
/* *  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 + -