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

📄 aaci.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* *  linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver * *  Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * *  Documentation: ARM DDI 0173B */#include <linux/module.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/ioport.h>#include <linux/device.h>#include <linux/spinlock.h>#include <linux/interrupt.h>#include <linux/err.h>#include <linux/amba/bus.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/sizes.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/initval.h>#include <sound/ac97_codec.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include "aaci.h"#include "devdma.h"#define DRIVER_NAME	"aaci-pl041"/* * PM support is not complete.  Turn it off. */#undef CONFIG_PMstatic void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97){	u32 v, maincr = aaci->maincr | MAINCR_SCRA(ac97->num);	/*	 * Ensure that the slot 1/2 RX registers are empty.	 */	v = readl(aaci->base + AACI_SLFR);	if (v & SLFR_2RXV)		readl(aaci->base + AACI_SL2RX);	if (v & SLFR_1RXV)		readl(aaci->base + AACI_SL1RX);	writel(maincr, aaci->base + AACI_MAINCR);}/* * P29: *  The recommended use of programming the external codec through slot 1 *  and slot 2 data is to use the channels during setup routines and the *  slot register at any other time.  The data written into slot 1, slot 2 *  and slot 12 registers is transmitted only when their corresponding *  SI1TxEn, SI2TxEn and SI12TxEn bits are set in the AACI_MAINCR *  register. */static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg,			    unsigned short val){	struct aaci *aaci = ac97->private_data;	u32 v;	int timeout = 5000;	if (ac97->num >= 4)		return;	mutex_lock(&aaci->ac97_sem);	aaci_ac97_select_codec(aaci, ac97);	/*	 * P54: You must ensure that AACI_SL2TX is always written	 * to, if required, before data is written to AACI_SL1TX.	 */	writel(val << 4, aaci->base + AACI_SL2TX);	writel(reg << 12, aaci->base + AACI_SL1TX);	/*	 * Wait for the transmission of both slots to complete.	 */	do {		v = readl(aaci->base + AACI_SLFR);	} while ((v & (SLFR_1TXB|SLFR_2TXB)) && timeout--);	if (!timeout)		dev_err(&aaci->dev->dev,			"timeout waiting for write to complete\n");	mutex_unlock(&aaci->ac97_sem);}/* * Read an AC'97 register. */static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg){	struct aaci *aaci = ac97->private_data;	u32 v;	int timeout = 5000;	int retries = 10;	if (ac97->num >= 4)		return ~0;	mutex_lock(&aaci->ac97_sem);	aaci_ac97_select_codec(aaci, ac97);	/*	 * Write the register address to slot 1.	 */	writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX);	/*	 * Wait for the transmission to complete.	 */	do {		v = readl(aaci->base + AACI_SLFR);	} while ((v & SLFR_1TXB) && timeout--);	if (!timeout) {		dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n");		v = ~0;		goto out;	}	/*	 * Give the AC'97 codec more than enough time	 * to respond. (42us = ~2 frames at 48kHz.)	 */	udelay(42);	/*	 * Wait for slot 2 to indicate data.	 */	timeout = 5000;	do {		cond_resched();		v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV);	} while ((v != (SLFR_1RXV|SLFR_2RXV)) && timeout--);	if (!timeout) {		dev_err(&aaci->dev->dev, "timeout on RX valid\n");		v = ~0;		goto out;	}	do {		v = readl(aaci->base + AACI_SL1RX) >> 12;		if (v == reg) {			v = readl(aaci->base + AACI_SL2RX) >> 4;			break;		} else if (--retries) {			dev_warn(&aaci->dev->dev,				 "ac97 read back fail.  retry\n");			continue;		} else {			dev_warn(&aaci->dev->dev,				"wrong ac97 register read back (%x != %x)\n",				v, reg);			v = ~0;		}	} while (retries); out:	mutex_unlock(&aaci->ac97_sem);	return v;}static inline void aaci_chan_wait_ready(struct aaci_runtime *aacirun){	u32 val;	int timeout = 5000;	do {		val = readl(aacirun->base + AACI_SR);	} while (val & (SR_TXB|SR_RXB) && timeout--);}/* * Interrupt support. */static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask){	if (mask & ISR_ORINTR) {		dev_warn(&aaci->dev->dev, "RX overrun on chan %d\n", channel);		writel(ICLR_RXOEC1 << channel, aaci->base + AACI_INTCLR);	}	if (mask & ISR_RXTOINTR) {		dev_warn(&aaci->dev->dev, "RX timeout on chan %d\n", channel);		writel(ICLR_RXTOFEC1 << channel, aaci->base + AACI_INTCLR);	}	if (mask & ISR_RXINTR) {		struct aaci_runtime *aacirun = &aaci->capture;		void *ptr;		if (!aacirun->substream || !aacirun->start) {			dev_warn(&aaci->dev->dev, "RX interrupt???\n");			writel(0, aacirun->base + AACI_IE);			return;		}		ptr = aacirun->ptr;		do {			unsigned int len = aacirun->fifosz;			u32 val;			if (aacirun->bytes <= 0) {				aacirun->bytes += aacirun->period;				aacirun->ptr = ptr;				spin_unlock(&aaci->lock);				snd_pcm_period_elapsed(aacirun->substream);				spin_lock(&aaci->lock);			}			if (!(aacirun->cr & CR_EN))				break;			val = readl(aacirun->base + AACI_SR);			if (!(val & SR_RXHF))				break;			if (!(val & SR_RXFF))				len >>= 1;			aacirun->bytes -= len;			/* reading 16 bytes at a time */			for( ; len > 0; len -= 16) {				asm(					"ldmia	%1, {r0, r1, r2, r3}\n\t"					"stmia	%0!, {r0, r1, r2, r3}"					: "+r" (ptr)					: "r" (aacirun->fifo)					: "r0", "r1", "r2", "r3", "cc");				if (ptr >= aacirun->end)					ptr = aacirun->start;			}		} while(1);		aacirun->ptr = ptr;	}	if (mask & ISR_URINTR) {		dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel);		writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR);	}	if (mask & ISR_TXINTR) {		struct aaci_runtime *aacirun = &aaci->playback;		void *ptr;		if (!aacirun->substream || !aacirun->start) {			dev_warn(&aaci->dev->dev, "TX interrupt???\n");			writel(0, aacirun->base + AACI_IE);			return;		}		ptr = aacirun->ptr;		do {			unsigned int len = aacirun->fifosz;			u32 val;			if (aacirun->bytes <= 0) {				aacirun->bytes += aacirun->period;				aacirun->ptr = ptr;				spin_unlock(&aaci->lock);				snd_pcm_period_elapsed(aacirun->substream);				spin_lock(&aaci->lock);			}			if (!(aacirun->cr & CR_EN))				break;			val = readl(aacirun->base + AACI_SR);			if (!(val & SR_TXHE))				break;			if (!(val & SR_TXFE))				len >>= 1;			aacirun->bytes -= len;			/* writing 16 bytes at a time */			for ( ; len > 0; len -= 16) {				asm(					"ldmia	%0!, {r0, r1, r2, r3}\n\t"					"stmia	%1, {r0, r1, r2, r3}"					: "+r" (ptr)					: "r" (aacirun->fifo)					: "r0", "r1", "r2", "r3", "cc");				if (ptr >= aacirun->end)					ptr = aacirun->start;			}		} while (1);		aacirun->ptr = ptr;	}}static irqreturn_t aaci_irq(int irq, void *devid){	struct aaci *aaci = devid;	u32 mask;	int i;	spin_lock(&aaci->lock);	mask = readl(aaci->base + AACI_ALLINTS);	if (mask) {		u32 m = mask;		for (i = 0; i < 4; i++, m >>= 7) {			if (m & 0x7f) {				aaci_fifo_irq(aaci, i, m);			}		}	}	spin_unlock(&aaci->lock);	return mask ? IRQ_HANDLED : IRQ_NONE;}/* * ALSA support. */struct aaci_stream {	unsigned char codec_idx;	unsigned char rate_idx;};static struct aaci_stream aaci_streams[] = {	[ACSTREAM_FRONT] = {		.codec_idx	= 0,		.rate_idx	= AC97_RATES_FRONT_DAC,	},	[ACSTREAM_SURROUND] = {		.codec_idx	= 0,		.rate_idx	= AC97_RATES_SURR_DAC,	},	[ACSTREAM_LFE] = {		.codec_idx	= 0,		.rate_idx	= AC97_RATES_LFE_DAC,	},};static inline unsigned int aaci_rate_mask(struct aaci *aaci, int streamid){	struct aaci_stream *s = aaci_streams + streamid;	return aaci->ac97_bus->codec[s->codec_idx]->rates[s->rate_idx];}static unsigned int rate_list[] = {	5512, 8000, 11025, 16000, 22050, 32000, 44100,	48000, 64000, 88200, 96000, 176400, 192000};/* * Double-rate rule: we can support double rate iff channels == 2 *  (unimplemented) */static intaaci_rule_rate_by_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule){	struct aaci *aaci = rule->private;	unsigned int rate_mask = SNDRV_PCM_RATE_8000_48000|SNDRV_PCM_RATE_5512;	struct snd_interval *c = hw_param_interval(p, SNDRV_PCM_HW_PARAM_CHANNELS);	switch (c->max) {	case 6:		rate_mask &= aaci_rate_mask(aaci, ACSTREAM_LFE);	case 4:		rate_mask &= aaci_rate_mask(aaci, ACSTREAM_SURROUND);	case 2:		rate_mask &= aaci_rate_mask(aaci, ACSTREAM_FRONT);	}	return snd_interval_list(hw_param_interval(p, rule->var),				 ARRAY_SIZE(rate_list), rate_list,				 rate_mask);}static struct snd_pcm_hardware aaci_hw_info = {	.info			= SNDRV_PCM_INFO_MMAP |				  SNDRV_PCM_INFO_MMAP_VALID |				  SNDRV_PCM_INFO_INTERLEAVED |				  SNDRV_PCM_INFO_BLOCK_TRANSFER |				  SNDRV_PCM_INFO_RESUME,	/*	 * ALSA doesn't support 18-bit or 20-bit packed into 32-bit	 * words.  It also doesn't support 12-bit at all.	 */	.formats		= SNDRV_PCM_FMTBIT_S16_LE,	/* should this be continuous or knot? */	.rates			= SNDRV_PCM_RATE_CONTINUOUS,	.rate_max		= 48000,	.rate_min		= 4000,	.channels_min		= 2,	.channels_max		= 6,	.buffer_bytes_max	= 64 * 1024,	.period_bytes_min	= 256,	.period_bytes_max	= PAGE_SIZE,	.periods_min		= 4,	.periods_max		= PAGE_SIZE / 16,};static int __aaci_pcm_open(struct aaci *aaci,			   struct snd_pcm_substream *substream,			   struct aaci_runtime *aacirun){	struct snd_pcm_runtime *runtime = substream->runtime;	int ret;	aacirun->substream = substream;	runtime->private_data = aacirun;	runtime->hw = aaci_hw_info;	/*	 * FIXME: ALSA specifies fifo_size in bytes.  If we're in normal	 * mode, each 32-bit word contains one sample.  If we're in	 * compact mode, each 32-bit word contains two samples, effectively	 * halving the FIFO size.  However, we don't know for sure which	 * we'll be using at this point.  We set this to the lower limit.	 */	runtime->hw.fifo_size = aaci->fifosize * 2;	/*	 * Add rule describing hardware rate dependency	 * on the number of channels.	 */	ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,				  aaci_rule_rate_by_channels, aaci,				  SNDRV_PCM_HW_PARAM_CHANNELS,				  SNDRV_PCM_HW_PARAM_RATE, -1);	if (ret)		goto out;	ret = request_irq(aaci->dev->irq[0], aaci_irq, IRQF_SHARED|IRQF_DISABLED,			  DRIVER_NAME, aaci);	if (ret)		goto out;	return 0; out:	return ret;}/* * Common ALSA stuff */static int aaci_pcm_close(struct snd_pcm_substream *substream){	struct aaci *aaci = substream->private_data;	struct aaci_runtime *aacirun = substream->runtime->private_data;	WARN_ON(aacirun->cr & CR_EN);	aacirun->substream = NULL;	free_irq(aaci->dev->irq[0], aaci);	return 0;}static int aaci_pcm_hw_free(struct snd_pcm_substream *substream){	struct aaci_runtime *aacirun = substream->runtime->private_data;	/*	 * This must not be called with the device enabled.	 */	WARN_ON(aacirun->cr & CR_EN);	if (aacirun->pcm_open)		snd_ac97_pcm_close(aacirun->pcm);	aacirun->pcm_open = 0;	/*	 * Clear out the DMA and any allocated buffers.	 */	devdma_hw_free(NULL, substream);	return 0;}static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,			      struct aaci_runtime *aacirun,			      struct snd_pcm_hw_params *params){	int err;	aaci_pcm_hw_free(substream);	err = devdma_hw_alloc(NULL, substream,			      params_buffer_bytes(params));	if (err < 0)		goto out;	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)		err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params),					params_channels(params),					aacirun->pcm->r[0].slots);	else		err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params),					params_channels(params),					aacirun->pcm->r[1].slots);	if (err)		goto out;	aacirun->pcm_open = 1; out:	return err;}static int aaci_pcm_prepare(struct snd_pcm_substream *substream){	struct snd_pcm_runtime *runtime = substream->runtime;	struct aaci_runtime *aacirun = runtime->private_data;	aacirun->start	= (void *)runtime->dma_area;	aacirun->end	= aacirun->start + runtime->dma_bytes;	aacirun->ptr	= aacirun->start;	aacirun->period	=	aacirun->bytes	= frames_to_bytes(runtime, runtime->period_size);	return 0;}static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream){	struct snd_pcm_runtime *runtime = substream->runtime;	struct aaci_runtime *aacirun = runtime->private_data;	ssize_t bytes = aacirun->ptr - aacirun->start;	return bytes_to_frames(runtime, bytes);}static int aaci_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma){	return devdma_mmap(NULL, substream, vma);}/* * Playback specific ALSA stuff */static const u32 channels_to_txmask[] = {	[2] = CR_SL3 | CR_SL4,	[4] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8,	[6] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8 | CR_SL6 | CR_SL9,};/* * We can support two and four channel audio.  Unfortunately * six channel audio requires a non-standard channel ordering: *   2 -> FL(3), FR(4) *   4 -> FL(3), FR(4), SL(7), SR(8) *   6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required) *        FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual) * This requires an ALSA configuration file to correct. */static unsigned int channel_list[] = { 2, 4, 6 };static intaaci_rule_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule){	struct aaci *aaci = rule->private;	unsigned int chan_mask = 1 << 0, slots;	/*	 * pcms[0] is the our 5.1 PCM instance.	 */	slots = aaci->ac97_bus->pcms[0].r[0].slots;	if (slots & (1 << AC97_SLOT_PCM_SLEFT)) {		chan_mask |= 1 << 1;		if (slots & (1 << AC97_SLOT_LFE))			chan_mask |= 1 << 2;	}	return snd_interval_list(hw_param_interval(p, rule->var),				 ARRAY_SIZE(channel_list), channel_list,				 chan_mask);}static int aaci_pcm_open(struct snd_pcm_substream *substream){	struct aaci *aaci = substream->private_data;

⌨️ 快捷键说明

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