Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 41762b8c authored by Kevin Hilman's avatar Kevin Hilman Committed by Russell King
Browse files

[ARM] 4139/1: AACI record support



Add PCM audio capture support for AACI audio on Versatile platform.

Signed-off-by: default avatarKevin Hilman <khilman@mvista.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent 62578cbf
Loading
Loading
Loading
Loading
+232 −26
Original line number Original line Diff line number Diff line
@@ -166,6 +166,65 @@ static inline void aaci_chan_wait_ready(struct aaci_runtime *aacirun)
 */
 */
static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
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???");
			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) {
	if (mask & ISR_URINTR) {
		dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel);
		dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel);
		writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR);
		writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR);
@@ -193,7 +252,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
				snd_pcm_period_elapsed(aacirun->substream);
				snd_pcm_period_elapsed(aacirun->substream);
				spin_lock(&aaci->lock);
				spin_lock(&aaci->lock);
			}
			}
			if (!(aacirun->cr & TXCR_TXEN))
			if (!(aacirun->cr & CR_EN))
				break;
				break;


			val = readl(aacirun->base + AACI_SR);
			val = readl(aacirun->base + AACI_SR);
@@ -331,7 +390,8 @@ static struct snd_pcm_hardware aaci_hw_info = {
	.periods_max		= PAGE_SIZE / 16,
	.periods_max		= PAGE_SIZE / 16,
};
};


static int aaci_pcm_open(struct aaci *aaci, struct snd_pcm_substream *substream,
static int __aaci_pcm_open(struct aaci *aaci,
			   struct snd_pcm_substream *substream,
			   struct aaci_runtime *aacirun)
			   struct aaci_runtime *aacirun)
{
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -381,7 +441,7 @@ static int aaci_pcm_close(struct snd_pcm_substream *substream)
	struct aaci *aaci = substream->private_data;
	struct aaci *aaci = substream->private_data;
	struct aaci_runtime *aacirun = substream->runtime->private_data;
	struct aaci_runtime *aacirun = substream->runtime->private_data;


	WARN_ON(aacirun->cr & TXCR_TXEN);
	WARN_ON(aacirun->cr & CR_EN);


	aacirun->substream = NULL;
	aacirun->substream = NULL;
	free_irq(aaci->dev->irq[0], aaci);
	free_irq(aaci->dev->irq[0], aaci);
@@ -396,7 +456,7 @@ static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
	/*
	/*
	 * This must not be called with the device enabled.
	 * This must not be called with the device enabled.
	 */
	 */
	WARN_ON(aacirun->cr & TXCR_TXEN);
	WARN_ON(aacirun->cr & CR_EN);


	if (aacirun->pcm_open)
	if (aacirun->pcm_open)
		snd_ac97_pcm_close(aacirun->pcm);
		snd_ac97_pcm_close(aacirun->pcm);
@@ -423,9 +483,15 @@ static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
	if (err < 0)
	if (err < 0)
		goto out;
		goto out;


	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params),
		err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params),
					params_channels(params),
					params_channels(params),
					aacirun->pcm->r[0].slots);
					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)
	if (err)
		goto out;
		goto out;


@@ -468,9 +534,9 @@ static int aaci_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_str
 * Playback specific ALSA stuff
 * Playback specific ALSA stuff
 */
 */
static const u32 channels_to_txmask[] = {
static const u32 channels_to_txmask[] = {
	[2] = TXCR_TX3 | TXCR_TX4,
	[2] = CR_SL3 | CR_SL4,
	[4] = TXCR_TX3 | TXCR_TX4 | TXCR_TX7 | TXCR_TX8,
	[4] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8,
	[6] = TXCR_TX3 | TXCR_TX4 | TXCR_TX7 | TXCR_TX8 | TXCR_TX6 | TXCR_TX9,
	[6] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8 | CR_SL6 | CR_SL9,
};
};


/*
/*
@@ -505,7 +571,7 @@ aaci_rule_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule)
				 chan_mask);
				 chan_mask);
}
}


static int aaci_pcm_playback_open(struct snd_pcm_substream *substream)
static int aaci_pcm_open(struct snd_pcm_substream *substream)
{
{
	struct aaci *aaci = substream->private_data;
	struct aaci *aaci = substream->private_data;
	int ret;
	int ret;
@@ -520,7 +586,12 @@ static int aaci_pcm_playback_open(struct snd_pcm_substream *substream)
	if (ret)
	if (ret)
		return ret;
		return ret;


	return aaci_pcm_open(aaci, substream, &aaci->playback);
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		ret = __aaci_pcm_open(aaci, substream, &aaci->playback);
	} else {
		ret = __aaci_pcm_open(aaci, substream, &aaci->capture);
	}
	return ret;
}
}


static int aaci_pcm_playback_hw_params(struct snd_pcm_substream *substream,
static int aaci_pcm_playback_hw_params(struct snd_pcm_substream *substream,
@@ -541,11 +612,11 @@ static int aaci_pcm_playback_hw_params(struct snd_pcm_substream *substream,
	 * FIXME: double rate slots?
	 * FIXME: double rate slots?
	 */
	 */
	if (ret >= 0) {
	if (ret >= 0) {
		aacirun->cr = TXCR_FEN | TXCR_COMPACT | TXCR_TSZ16;
		aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16;
		aacirun->cr |= channels_to_txmask[channels];
		aacirun->cr |= channels_to_txmask[channels];


		aacirun->fifosz	= aaci->fifosize * 4;
		aacirun->fifosz	= aaci->fifosize * 4;
		if (aacirun->cr & TXCR_COMPACT)
		if (aacirun->cr & CR_COMPACT)
			aacirun->fifosz >>= 1;
			aacirun->fifosz >>= 1;
	}
	}
	return ret;
	return ret;
@@ -558,7 +629,7 @@ static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun)
	ie = readl(aacirun->base + AACI_IE);
	ie = readl(aacirun->base + AACI_IE);
	ie &= ~(IE_URIE|IE_TXIE);
	ie &= ~(IE_URIE|IE_TXIE);
	writel(ie, aacirun->base + AACI_IE);
	writel(ie, aacirun->base + AACI_IE);
	aacirun->cr &= ~TXCR_TXEN;
	aacirun->cr &= ~CR_EN;
	aaci_chan_wait_ready(aacirun);
	aaci_chan_wait_ready(aacirun);
	writel(aacirun->cr, aacirun->base + AACI_TXCR);
	writel(aacirun->cr, aacirun->base + AACI_TXCR);
}
}
@@ -568,7 +639,7 @@ static void aaci_pcm_playback_start(struct aaci_runtime *aacirun)
	u32 ie;
	u32 ie;


	aaci_chan_wait_ready(aacirun);
	aaci_chan_wait_ready(aacirun);
	aacirun->cr |= TXCR_TXEN;
	aacirun->cr |= CR_EN;


	ie = readl(aacirun->base + AACI_IE);
	ie = readl(aacirun->base + AACI_IE);
	ie |= IE_URIE | IE_TXIE;
	ie |= IE_URIE | IE_TXIE;
@@ -616,7 +687,7 @@ static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cm
}
}


static struct snd_pcm_ops aaci_playback_ops = {
static struct snd_pcm_ops aaci_playback_ops = {
	.open		= aaci_pcm_playback_open,
	.open		= aaci_pcm_open,
	.close		= aaci_pcm_close,
	.close		= aaci_pcm_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= aaci_pcm_playback_hw_params,
	.hw_params	= aaci_pcm_playback_hw_params,
@@ -627,7 +698,133 @@ static struct snd_pcm_ops aaci_playback_ops = {
	.mmap		= aaci_pcm_mmap,
	.mmap		= aaci_pcm_mmap,
};
};


static int aaci_pcm_capture_hw_params(snd_pcm_substream_t *substream,
				      snd_pcm_hw_params_t *params)
{
	struct aaci *aaci = substream->private_data;
	struct aaci_runtime *aacirun = substream->runtime->private_data;
	int ret;

	ret = aaci_pcm_hw_params(substream, aacirun, params);

	if (ret >= 0) {
		aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16;

		/* Line in record: slot 3 and 4 */
		aacirun->cr |= CR_SL3 | CR_SL4;

		aacirun->fifosz = aaci->fifosize * 4;

		if (aacirun->cr & CR_COMPACT)
			aacirun->fifosz >>= 1;
	}
	return ret;
}

static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun)
{
	u32 ie;

	aaci_chan_wait_ready(aacirun);

	ie = readl(aacirun->base + AACI_IE);
	ie &= ~(IE_ORIE | IE_RXIE);
	writel(ie, aacirun->base+AACI_IE);

	aacirun->cr &= ~CR_EN;

	writel(aacirun->cr, aacirun->base + AACI_RXCR);
}

static void aaci_pcm_capture_start(struct aaci_runtime *aacirun)
{
	u32 ie;


	aaci_chan_wait_ready(aacirun);

#ifdef DEBUG
	/* RX Timeout value: bits 28:17 in RXCR */
	aacirun->cr |= 0xf << 17;
#endif

	aacirun->cr |= CR_EN;
	writel(aacirun->cr, aacirun->base + AACI_RXCR);

	ie = readl(aacirun->base + AACI_IE);
	ie |= IE_ORIE |IE_RXIE; // overrun and rx interrupt -- half full
	writel(ie, aacirun->base + AACI_IE);
}

static int aaci_pcm_capture_trigger(snd_pcm_substream_t *substream, int cmd){

	struct aaci *aaci = substream->private_data;
	struct aaci_runtime *aacirun = substream->runtime->private_data;
	unsigned long flags;
	int ret = 0;

	spin_lock_irqsave(&aaci->lock, flags);

	switch (cmd) {
	case SNDRV_PCM_TRIGGER_START:
		aaci_pcm_capture_start(aacirun);
		break;

	case SNDRV_PCM_TRIGGER_RESUME:
		aaci_pcm_capture_start(aacirun);
		break;

	case SNDRV_PCM_TRIGGER_STOP:
		aaci_pcm_capture_stop(aacirun);
		break;

	case SNDRV_PCM_TRIGGER_SUSPEND:
		aaci_pcm_capture_stop(aacirun);
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
		break;

	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
		break;

	default:
		ret = -EINVAL;
	}

	spin_unlock_irqrestore(&aaci->lock, flags);

	return ret;
}

static int aaci_pcm_capture_prepare(snd_pcm_substream_t *substream)
{
	struct snd_pcm_runtime *runtime = substream->runtime;
	struct aaci *aaci = substream->private_data;

	aaci_pcm_prepare(substream);

	/* allow changing of sample rate */
	aaci_ac97_write(aaci->ac97, AC97_EXTENDED_STATUS, 0x0001); /* VRA */
	aaci_ac97_write(aaci->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate);
	aaci_ac97_write(aaci->ac97, AC97_PCM_MIC_ADC_RATE, runtime->rate);

	/* Record select: Mic: 0, Aux: 3, Line: 4 */
	aaci_ac97_write(aaci->ac97, AC97_REC_SEL, 0x0404);

	return 0;
}

static snd_pcm_ops_t aaci_capture_ops = {
	.open		= aaci_pcm_open,
	.close		= aaci_pcm_close,
	.ioctl		= snd_pcm_lib_ioctl,
	.hw_params	= aaci_pcm_capture_hw_params,
	.hw_free	= aaci_pcm_hw_free,
	.prepare	= aaci_pcm_capture_prepare,
	.trigger	= aaci_pcm_capture_trigger,
	.pointer	= aaci_pcm_pointer,
	.mmap		= aaci_pcm_mmap,
};


/*
/*
 * Power Management.
 * Power Management.
@@ -741,6 +938,7 @@ static int __devinit aaci_probe_ac97(struct aaci *aaci)
	ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97);
	ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97);
	if (ret)
	if (ret)
		goto out;
		goto out;
	aaci->ac97 = ac97;


	/*
	/*
	 * Disable AC97 PC Beep input on audio codecs.
	 * Disable AC97 PC Beep input on audio codecs.
@@ -753,6 +951,7 @@ static int __devinit aaci_probe_ac97(struct aaci *aaci)
		goto out;
		goto out;


	aaci->playback.pcm = &ac97_bus->pcms[0];
	aaci->playback.pcm = &ac97_bus->pcms[0];
	aaci->capture.pcm  = &ac97_bus->pcms[1];


 out:
 out:
	return ret;
	return ret;
@@ -802,7 +1001,7 @@ static int __devinit aaci_init_pcm(struct aaci *aaci)
	struct snd_pcm *pcm;
	struct snd_pcm *pcm;
	int ret;
	int ret;


	ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 0, &pcm);
	ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 1, &pcm);
	if (ret == 0) {
	if (ret == 0) {
		aaci->pcm = pcm;
		aaci->pcm = pcm;
		pcm->private_data = aaci;
		pcm->private_data = aaci;
@@ -811,6 +1010,7 @@ static int __devinit aaci_init_pcm(struct aaci *aaci)
		strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name));
		strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name));


		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aaci_playback_ops);
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aaci_playback_ops);
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aaci_capture_ops);
	}
	}


	return ret;
	return ret;
@@ -818,15 +1018,15 @@ static int __devinit aaci_init_pcm(struct aaci *aaci)


static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
{
{
	void __iomem *base = aaci->base + AACI_CSCH1;
	struct aaci_runtime *aacirun = &aaci->playback;
	int i;
	int i;


	writel(TXCR_FEN | TXCR_TSZ16 | TXCR_TXEN, base + AACI_TXCR);
	writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR);


	for (i = 0; !(readl(base + AACI_SR) & SR_TXFF) && i < 4096; i++)
	for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++)
		writel(0, aaci->base + AACI_DR1);
		writel(0, aacirun->fifo);


	writel(0, base + AACI_TXCR);
	writel(0, aacirun->base + AACI_TXCR);


	/*
	/*
	 * Re-initialise the AACI after the FIFO depth test, to
	 * Re-initialise the AACI after the FIFO depth test, to
@@ -873,6 +1073,12 @@ static int __devinit aaci_probe(struct amba_device *dev, void *id)
	aaci->playback.base = aaci->base + AACI_CSCH1;
	aaci->playback.base = aaci->base + AACI_CSCH1;
	aaci->playback.fifo = aaci->base + AACI_DR1;
	aaci->playback.fifo = aaci->base + AACI_DR1;


	/*
	 * Capture uses AACI channel 0
	 */
	aaci->capture.base = aaci->base + AACI_CSCH1;
	aaci->capture.fifo = aaci->base + AACI_DR1;

	for (i = 0; i < 4; i++) {
	for (i = 0; i < 4; i++) {
		void __iomem *base = aaci->base + i * 0x14;
		void __iomem *base = aaci->base + i * 0x14;


+21 −20
Original line number Original line Diff line number Diff line
@@ -49,27 +49,27 @@
#define AACI_DR4	0x0f0	/* data read/written fifo 4 */
#define AACI_DR4	0x0f0	/* data read/written fifo 4 */


/*
/*
 * transmit fifo control register. P48
 * TX/RX fifo control register (CR). P48
 */
 */
#define TXCR_FEN	(1 << 16)	/* fifo enable */
#define CR_FEN		(1 << 16)	/* fifo enable */
#define TXCR_COMPACT	(1 << 15)	/* compact mode */
#define CR_COMPACT	(1 << 15)	/* compact mode */
#define TXCR_TSZ16	(0 << 13)	/* 16 bits */
#define CR_SZ16		(0 << 13)	/* 16 bits */
#define TXCR_TSZ18	(1 << 13)	/* 18 bits */
#define CR_SZ18		(1 << 13)	/* 18 bits */
#define TXCR_TSZ20	(2 << 13)	/* 20 bits */
#define CR_SZ20		(2 << 13)	/* 20 bits */
#define TXCR_TSZ12	(3 << 13)	/* 12 bits */
#define CR_SZ12		(3 << 13)	/* 12 bits */
#define TXCR_TX12	(1 << 12)	/* transmits slot 12 */
#define CR_SL12		(1 << 12)
#define TXCR_TX11	(1 << 11)	/* transmits slot 12 */
#define CR_SL11		(1 << 11)
#define TXCR_TX10	(1 << 10)	/* transmits slot 12 */
#define CR_SL10		(1 << 10)
#define TXCR_TX9	(1 << 9)	/* transmits slot 12 */
#define CR_SL9		(1 << 9)
#define TXCR_TX8	(1 << 8)	/* transmits slot 12 */
#define CR_SL8		(1 << 8)
#define TXCR_TX7	(1 << 7)	/* transmits slot 12 */
#define CR_SL7		(1 << 7)
#define TXCR_TX6	(1 << 6)	/* transmits slot 12 */
#define CR_SL6		(1 << 6)
#define TXCR_TX5	(1 << 5)	/* transmits slot 12 */
#define CR_SL5		(1 << 5)
#define TXCR_TX4	(1 << 4)	/* transmits slot 12 */
#define CR_SL4		(1 << 4)
#define TXCR_TX3	(1 << 3)	/* transmits slot 12 */
#define CR_SL3		(1 << 3)
#define TXCR_TX2	(1 << 2)	/* transmits slot 12 */
#define CR_SL2		(1 << 2)
#define TXCR_TX1	(1 << 1)	/* transmits slot 12 */
#define CR_SL1		(1 << 1)
#define TXCR_TXEN	(1 << 0)	/* transmit enable */
#define CR_EN		(1 << 0)	/* transmit enable */


/*
/*
 * status register bits. P49
 * status register bits. P49
@@ -229,6 +229,7 @@ struct aaci {
	/* AC'97 */
	/* AC'97 */
	struct mutex		ac97_sem;
	struct mutex		ac97_sem;
	ac97_bus_t		*ac97_bus;
	ac97_bus_t		*ac97_bus;
	ac97_t                  *ac97;


	u32			maincr;
	u32			maincr;
	spinlock_t		lock;
	spinlock_t		lock;