au1x00.c

来自「linux 内核源代码」· C语言 代码 · 共 694 行 · 第 1/2 页

C
694
字号
/* * BRIEF MODULE DESCRIPTION *  Driver for AMD Au1000 MIPS Processor, AC'97 Sound Port * * Copyright 2004 Cooper Street Innovations Inc. * Author: Charles Eidsness	<charles@cooper-street.com> * *  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  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT, *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * *  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., *  675 Mass Ave, Cambridge, MA 02139, USA. * * History: * * 2004-09-09 Charles Eidsness	-- Original verion -- based on * 				  sa11xx-uda1341.c ALSA driver and the *				  au1000.c OSS driver. * 2004-09-09 Matt Porter	-- Added support for ALSA 1.0.6 * */#include <linux/ioport.h>#include <linux/interrupt.h>#include <sound/driver.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/version.h>#include <sound/core.h>#include <sound/initval.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/ac97_codec.h>#include <asm/mach-au1x00/au1000.h>#include <asm/mach-au1x00/au1000_dma.h>MODULE_AUTHOR("Charles Eidsness <charles@cooper-street.com>");MODULE_DESCRIPTION("Au1000 AC'97 ALSA Driver");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{AMD,Au1000 AC'97}}");#define PLAYBACK 0#define CAPTURE 1#define AC97_SLOT_3 0x01#define AC97_SLOT_4 0x02#define AC97_SLOT_6 0x08#define AC97_CMD_IRQ 31#define READ 0#define WRITE 1#define READ_WAIT 2#define RW_DONE 3struct au1000_period{	u32 start;	u32 relative_end;	/*realtive to start of buffer*/	struct au1000_period * next;};/*Au1000 AC97 Port Control Reisters*/struct au1000_ac97_reg {	u32 volatile config;	u32 volatile status;	u32 volatile data;	u32 volatile cmd;	u32 volatile cntrl;};struct audio_stream {	struct snd_pcm_substream *substream;	int dma;	spinlock_t dma_lock;	struct au1000_period * buffer;	unsigned int period_size;	unsigned int periods;};struct snd_au1000 {	struct snd_card *card;	struct au1000_ac97_reg volatile *ac97_ioport;	struct resource *ac97_res_port;	spinlock_t ac97_lock;	struct snd_ac97 *ac97;	struct snd_pcm *pcm;	struct audio_stream *stream[2];	/* playback & capture */};/*--------------------------- Local Functions --------------------------------*/static voidau1000_set_ac97_xmit_slots(struct snd_au1000 *au1000, long xmit_slots){	u32 volatile ac97_config;	spin_lock(&au1000->ac97_lock);	ac97_config = au1000->ac97_ioport->config;	ac97_config = ac97_config & ~AC97C_XMIT_SLOTS_MASK;	ac97_config |= (xmit_slots << AC97C_XMIT_SLOTS_BIT);	au1000->ac97_ioport->config = ac97_config;	spin_unlock(&au1000->ac97_lock);}static voidau1000_set_ac97_recv_slots(struct snd_au1000 *au1000, long recv_slots){	u32 volatile ac97_config;	spin_lock(&au1000->ac97_lock);	ac97_config = au1000->ac97_ioport->config;	ac97_config = ac97_config & ~AC97C_RECV_SLOTS_MASK;	ac97_config |= (recv_slots << AC97C_RECV_SLOTS_BIT);	au1000->ac97_ioport->config = ac97_config;	spin_unlock(&au1000->ac97_lock);}static voidau1000_release_dma_link(struct audio_stream *stream){	struct au1000_period * pointer;	struct au1000_period * pointer_next;	stream->period_size = 0;	stream->periods = 0;	pointer = stream->buffer;	if (! pointer)		return;	do {		pointer_next = pointer->next;		kfree(pointer);		pointer = pointer_next;	} while (pointer != stream->buffer);	stream->buffer = NULL;}static intau1000_setup_dma_link(struct audio_stream *stream, unsigned int period_bytes,		      unsigned int periods){	struct snd_pcm_substream *substream = stream->substream;	struct snd_pcm_runtime *runtime = substream->runtime;	struct au1000_period *pointer;	unsigned long dma_start;	int i;	dma_start = virt_to_phys(runtime->dma_area);	if (stream->period_size == period_bytes &&	    stream->periods == periods)		return 0; /* not changed */	au1000_release_dma_link(stream);	stream->period_size = period_bytes;	stream->periods = periods;	stream->buffer = kmalloc(sizeof(struct au1000_period), GFP_KERNEL);	if (! stream->buffer)		return -ENOMEM;	pointer = stream->buffer;	for (i = 0; i < periods; i++) {		pointer->start = (u32)(dma_start + (i * period_bytes));		pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);		if (i < periods - 1) {			pointer->next = kmalloc(sizeof(struct au1000_period), GFP_KERNEL);			if (! pointer->next) {				au1000_release_dma_link(stream);				return -ENOMEM;			}			pointer = pointer->next;		}	}	pointer->next = stream->buffer;	return 0;}static voidau1000_dma_stop(struct audio_stream *stream){	snd_assert(stream->buffer, return);	disable_dma(stream->dma);}static voidau1000_dma_start(struct audio_stream *stream){	snd_assert(stream->buffer, return);	init_dma(stream->dma);	if (get_dma_active_buffer(stream->dma) == 0) {		clear_dma_done0(stream->dma);		set_dma_addr0(stream->dma, stream->buffer->start);		set_dma_count0(stream->dma, stream->period_size >> 1);		set_dma_addr1(stream->dma, stream->buffer->next->start);		set_dma_count1(stream->dma, stream->period_size >> 1);	} else {		clear_dma_done1(stream->dma);		set_dma_addr1(stream->dma, stream->buffer->start);		set_dma_count1(stream->dma, stream->period_size >> 1);		set_dma_addr0(stream->dma, stream->buffer->next->start);		set_dma_count0(stream->dma, stream->period_size >> 1);	}	enable_dma_buffers(stream->dma);	start_dma(stream->dma);}static irqreturn_tau1000_dma_interrupt(int irq, void *dev_id){	struct audio_stream *stream = (struct audio_stream *) dev_id;	struct snd_pcm_substream *substream = stream->substream;	spin_lock(&stream->dma_lock);	switch (get_dma_buffer_done(stream->dma)) {	case DMA_D0:		stream->buffer = stream->buffer->next;		clear_dma_done0(stream->dma);		set_dma_addr0(stream->dma, stream->buffer->next->start);		set_dma_count0(stream->dma, stream->period_size >> 1);		enable_dma_buffer0(stream->dma);		break;	case DMA_D1:		stream->buffer = stream->buffer->next;		clear_dma_done1(stream->dma);		set_dma_addr1(stream->dma, stream->buffer->next->start);		set_dma_count1(stream->dma, stream->period_size >> 1);		enable_dma_buffer1(stream->dma);		break;	case (DMA_D0 | DMA_D1):		printk(KERN_ERR "DMA %d missed interrupt.\n",stream->dma);		au1000_dma_stop(stream);		au1000_dma_start(stream);		break;	case (~DMA_D0 & ~DMA_D1):		printk(KERN_ERR "DMA %d empty irq.\n",stream->dma);	}	spin_unlock(&stream->dma_lock);	snd_pcm_period_elapsed(substream);	return IRQ_HANDLED;}/*-------------------------- PCM Audio Streams -------------------------------*/static unsigned int rates[] = {8000, 11025, 16000, 22050};static struct snd_pcm_hw_constraint_list hw_constraints_rates = {	.count	= ARRAY_SIZE(rates),	.list	= rates,	.mask	= 0,};static struct snd_pcm_hardware snd_au1000_hw ={	.info			= (SNDRV_PCM_INFO_INTERLEAVED | \				SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID),	.formats		= SNDRV_PCM_FMTBIT_S16_LE,	.rates			= (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |				SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050),	.rate_min		= 8000,	.rate_max		= 22050,	.channels_min		= 1,	.channels_max		= 2,	.buffer_bytes_max	= 128*1024,	.period_bytes_min	= 32,	.period_bytes_max	= 16*1024,	.periods_min		= 8,	.periods_max		= 255,	.fifo_size		= 16,};static intsnd_au1000_playback_open(struct snd_pcm_substream *substream){	struct snd_au1000 *au1000 = substream->pcm->private_data;	au1000->stream[PLAYBACK]->substream = substream;	au1000->stream[PLAYBACK]->buffer = NULL;	substream->private_data = au1000->stream[PLAYBACK];	substream->runtime->hw = snd_au1000_hw;	return (snd_pcm_hw_constraint_list(substream->runtime, 0,		SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0);}static intsnd_au1000_capture_open(struct snd_pcm_substream *substream){	struct snd_au1000 *au1000 = substream->pcm->private_data;	au1000->stream[CAPTURE]->substream = substream;	au1000->stream[CAPTURE]->buffer = NULL;	substream->private_data = au1000->stream[CAPTURE];	substream->runtime->hw = snd_au1000_hw;	return (snd_pcm_hw_constraint_list(substream->runtime, 0,		SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates) < 0);}static intsnd_au1000_playback_close(struct snd_pcm_substream *substream){	struct snd_au1000 *au1000 = substream->pcm->private_data;	au1000->stream[PLAYBACK]->substream = NULL;	return 0;}static intsnd_au1000_capture_close(struct snd_pcm_substream *substream){	struct snd_au1000 *au1000 = substream->pcm->private_data;	au1000->stream[CAPTURE]->substream = NULL;	return 0;}static intsnd_au1000_hw_params(struct snd_pcm_substream *substream,					struct snd_pcm_hw_params *hw_params){	struct audio_stream *stream = substream->private_data;	int err;	err = snd_pcm_lib_malloc_pages(substream,				       params_buffer_bytes(hw_params));	if (err < 0)		return err;	return au1000_setup_dma_link(stream,				     params_period_bytes(hw_params),				     params_periods(hw_params));}static intsnd_au1000_hw_free(struct snd_pcm_substream *substream){

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?