diff options
Diffstat (limited to 'sound/arm')
-rw-r--r-- | sound/arm/Kconfig | 38 | ||||
-rw-r--r-- | sound/arm/Makefile | 14 | ||||
-rw-r--r-- | sound/arm/aaci.c | 1094 | ||||
-rw-r--r-- | sound/arm/aaci.h | 247 | ||||
-rw-r--r-- | sound/arm/pxa2xx-ac97-lib.c | 468 | ||||
-rw-r--r-- | sound/arm/pxa2xx-ac97-regs.h | 100 | ||||
-rw-r--r-- | sound/arm/pxa2xx-ac97.c | 293 | ||||
-rw-r--r-- | sound/arm/pxa2xx-pcm-lib.c | 195 |
8 files changed, 2449 insertions, 0 deletions
diff --git a/sound/arm/Kconfig b/sound/arm/Kconfig new file mode 100644 index 000000000..dea2c661b --- /dev/null +++ b/sound/arm/Kconfig @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-2.0-only +# ALSA ARM drivers + +menuconfig SND_ARM + bool "ARM sound devices" + depends on ARM + default y + help + Support for sound devices specific to ARM architectures. + Drivers that are implemented on ASoC can be found in + "ALSA for SoC audio support" section. + +if SND_ARM + +config SND_ARMAACI + tristate "ARM PrimeCell PL041 AC Link support" + depends on ARM_AMBA + select SND_PCM + select SND_AC97_CODEC + +config SND_PXA2XX_AC97 + tristate "AC97 driver for the Intel PXA2xx chip" + depends on ARCH_PXA + select SND_AC97_CODEC + select SND_PXA2XX_LIB + select SND_PXA2XX_LIB_AC97 + help + Say Y or M if you want to support any AC97 codec attached to + the PXA2xx AC97 interface. + +endif # SND_ARM + +config SND_PXA2XX_LIB + tristate + select SND_DMAENGINE_PCM + +config SND_PXA2XX_LIB_AC97 + bool diff --git a/sound/arm/Makefile b/sound/arm/Makefile new file mode 100644 index 000000000..34c769489 --- /dev/null +++ b/sound/arm/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for ALSA +# + +obj-$(CONFIG_SND_ARMAACI) += snd-aaci.o +snd-aaci-objs := aaci.o + +obj-$(CONFIG_SND_PXA2XX_LIB) += snd-pxa2xx-lib.o +snd-pxa2xx-lib-y := pxa2xx-pcm-lib.o +snd-pxa2xx-lib-$(CONFIG_SND_PXA2XX_LIB_AC97) += pxa2xx-ac97-lib.o + +obj-$(CONFIG_SND_PXA2XX_AC97) += snd-pxa2xx-ac97.o +snd-pxa2xx-ac97-objs := pxa2xx-ac97.o diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c new file mode 100644 index 000000000..0817ad21a --- /dev/null +++ b/sound/arm/aaci.c @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver + * + * Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved. + * + * 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 <linux/io.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" + +#define DRIVER_NAME "aaci-pl041" + +#define FRAME_PERIOD_US 21 + +/* + * PM support is not complete. Turn it off. + */ +#undef CONFIG_PM + +static 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); + + if (maincr != readl(aaci->base + AACI_MAINCR)) { + writel(maincr, aaci->base + AACI_MAINCR); + readl(aaci->base + AACI_MAINCR); + udelay(1); + } +} + +/* + * 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; + int timeout; + u32 v; + + 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); + + /* Initially, wait one frame period */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for it to be sent */ + timeout = FRAME_PERIOD_US * 8; + do { + udelay(1); + v = readl(aaci->base + AACI_SLFR); + } while ((v & (SLFR_1TXB|SLFR_2TXB)) && --timeout); + + if (v & (SLFR_1TXB|SLFR_2TXB)) + 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; + int timeout, retries = 10; + u32 v; + + 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); + + /* Initially, wait one frame period */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for it to be sent */ + timeout = FRAME_PERIOD_US * 8; + do { + udelay(1); + v = readl(aaci->base + AACI_SLFR); + } while ((v & SLFR_1TXB) && --timeout); + + if (v & SLFR_1TXB) { + dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n"); + v = ~0; + goto out; + } + + /* Now wait for the response frame */ + udelay(FRAME_PERIOD_US); + + /* And then wait an additional eight frame periods for data */ + timeout = FRAME_PERIOD_US * 8; + do { + udelay(1); + cond_resched(); + v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV); + } while ((v != (SLFR_1RXV|SLFR_2RXV)) && --timeout); + + if (v != (SLFR_1RXV|SLFR_2RXV)) { + 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, unsigned long mask) +{ + u32 val; + int timeout = 5000; + + do { + udelay(1); + val = readl(aacirun->base + AACI_SR); + } while (val & mask && 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; + bool period_elapsed = false; + void *ptr; + + if (!aacirun->substream || !aacirun->start) { + dev_warn(&aaci->dev->dev, "RX interrupt???\n"); + writel(0, aacirun->base + AACI_IE); + return; + } + + spin_lock(&aacirun->lock); + + ptr = aacirun->ptr; + do { + unsigned int len = aacirun->fifo_bytes; + u32 val; + + if (aacirun->bytes <= 0) { + aacirun->bytes += aacirun->period; + period_elapsed = true; + } + 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; + + spin_unlock(&aacirun->lock); + + if (period_elapsed) + snd_pcm_period_elapsed(aacirun->substream); + } + + 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; + bool period_elapsed = false; + void *ptr; + + if (!aacirun->substream || !aacirun->start) { + dev_warn(&aaci->dev->dev, "TX interrupt???\n"); + writel(0, aacirun->base + AACI_IE); + return; + } + + spin_lock(&aacirun->lock); + + ptr = aacirun->ptr; + do { + unsigned int len = aacirun->fifo_bytes; + u32 val; + + if (aacirun->bytes <= 0) { + aacirun->bytes += aacirun->period; + period_elapsed = true; + } + 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; + + spin_unlock(&aacirun->lock); + + if (period_elapsed) + snd_pcm_period_elapsed(aacirun->substream); + } +} + +static irqreturn_t aaci_irq(int irq, void *devid) +{ + struct aaci *aaci = devid; + u32 mask; + int i; + + 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); + } + } + } + + return mask ? IRQ_HANDLED : IRQ_NONE; +} + + + +/* + * ALSA support. + */ +static const 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, + + /* rates are setup from the AC'97 codec */ + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 64 * 1024, + .period_bytes_min = 256, + .period_bytes_max = PAGE_SIZE, + .periods_min = 4, + .periods_max = PAGE_SIZE / 16, +}; + +/* + * 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 int aaci_rule_channels(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_rule *rule) +{ + static const unsigned int channel_list[] = { 2, 4, 6 }; + struct aaci *aaci = rule->private; + unsigned int 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)) { + mask |= 1 << 1; + if (slots & (1 << AC97_SLOT_LFE)) + mask |= 1 << 2; + } + + return snd_interval_list(hw_param_interval(p, rule->var), + ARRAY_SIZE(channel_list), channel_list, mask); +} + +static int aaci_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aaci *aaci = substream->private_data; + struct aaci_runtime *aacirun; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aacirun = &aaci->playback; + } else { + aacirun = &aaci->capture; + } + + aacirun->substream = substream; + runtime->private_data = aacirun; + runtime->hw = aaci_hw_info; + runtime->hw.rates = aacirun->pcm->rates; + snd_pcm_limit_hw_rates(runtime); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.channels_max = 6; + + /* Add rule describing channel dependency. */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + aaci_rule_channels, aaci, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) + return ret; + + if (aacirun->pcm->r[1].slots) + snd_ac97_pcm_double_rate_rules(runtime); + } + + /* + * ALSA wants the byte-size of the FIFOs. As we only support + * 16-bit samples, this is twice the FIFO depth irrespective + * of whether it's in compact mode or not. + */ + runtime->hw.fifo_size = aaci->fifo_depth * 2; + + mutex_lock(&aaci->irq_lock); + if (!aaci->users++) { + ret = request_irq(aaci->dev->irq[0], aaci_irq, + IRQF_SHARED, DRIVER_NAME, aaci); + if (ret != 0) + aaci->users--; + } + mutex_unlock(&aaci->irq_lock); + + 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; + + mutex_lock(&aaci->irq_lock); + if (!--aaci->users) + free_irq(aaci->dev->irq[0], aaci); + mutex_unlock(&aaci->irq_lock); + + 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; + + return 0; +} + +/* Channel to slot mask */ +static const u32 channels_to_slotmask[] = { + [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, +}; + +static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct aaci_runtime *aacirun = substream->runtime->private_data; + struct aaci *aaci = substream->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + int dbl = rate > 48000; + int err; + + aaci_pcm_hw_free(substream); + if (aacirun->pcm_open) { + snd_ac97_pcm_close(aacirun->pcm); + aacirun->pcm_open = 0; + } + + /* channels is already limited to 2, 4, or 6 by aaci_rule_channels */ + if (dbl && channels != 2) + return -EINVAL; + + err = snd_ac97_pcm_open(aacirun->pcm, rate, channels, + aacirun->pcm->r[dbl].slots); + + aacirun->pcm_open = err == 0; + aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16; + aacirun->cr |= channels_to_slotmask[channels + dbl * 2]; + + /* + * fifo_bytes is the number of bytes we transfer to/from + * the FIFO, including padding. So that's x4. As we're + * in compact mode, the FIFO is half the size. + */ + aacirun->fifo_bytes = aaci->fifo_depth * 4 / 2; + + 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->period = snd_pcm_lib_period_bytes(substream); + aacirun->start = runtime->dma_area; + aacirun->end = aacirun->start + snd_pcm_lib_buffer_bytes(substream); + aacirun->ptr = aacirun->start; + aacirun->bytes = aacirun->period; + + 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); +} + + +/* + * Playback specific ALSA stuff + */ +static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun) +{ + u32 ie; + + ie = readl(aacirun->base + AACI_IE); + ie &= ~(IE_URIE|IE_TXIE); + writel(ie, aacirun->base + AACI_IE); + aacirun->cr &= ~CR_EN; + aaci_chan_wait_ready(aacirun, SR_TXB); + writel(aacirun->cr, aacirun->base + AACI_TXCR); +} + +static void aaci_pcm_playback_start(struct aaci_runtime *aacirun) +{ + u32 ie; + + aaci_chan_wait_ready(aacirun, SR_TXB); + aacirun->cr |= CR_EN; + + ie = readl(aacirun->base + AACI_IE); + ie |= IE_URIE | IE_TXIE; + writel(ie, aacirun->base + AACI_IE); + writel(aacirun->cr, aacirun->base + AACI_TXCR); +} + +static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct aaci_runtime *aacirun = substream->runtime->private_data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&aacirun->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aaci_pcm_playback_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + aaci_pcm_playback_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_STOP: + aaci_pcm_playback_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + aaci_pcm_playback_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + default: + ret = -EINVAL; + } + + spin_unlock_irqrestore(&aacirun->lock, flags); + + return ret; +} + +static const struct snd_pcm_ops aaci_playback_ops = { + .open = aaci_pcm_open, + .close = aaci_pcm_close, + .hw_params = aaci_pcm_hw_params, + .hw_free = aaci_pcm_hw_free, + .prepare = aaci_pcm_prepare, + .trigger = aaci_pcm_playback_trigger, + .pointer = aaci_pcm_pointer, +}; + +static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun) +{ + u32 ie; + + aaci_chan_wait_ready(aacirun, SR_RXB); + + 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, SR_RXB); + +#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(struct snd_pcm_substream *substream, int cmd) +{ + struct aaci_runtime *aacirun = substream->runtime->private_data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&aacirun->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(&aacirun->lock, flags); + + return ret; +} + +static int aaci_pcm_capture_prepare(struct snd_pcm_substream *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 const struct snd_pcm_ops aaci_capture_ops = { + .open = aaci_pcm_open, + .close = aaci_pcm_close, + .hw_params = aaci_pcm_hw_params, + .hw_free = aaci_pcm_hw_free, + .prepare = aaci_pcm_capture_prepare, + .trigger = aaci_pcm_capture_trigger, + .pointer = aaci_pcm_pointer, +}; + +/* + * Power Management. + */ +#ifdef CONFIG_PM +static int aaci_do_suspend(struct snd_card *card) +{ + struct aaci *aaci = card->private_data; + snd_power_change_state(card, SNDRV_CTL_POWER_D3cold); + return 0; +} + +static int aaci_do_resume(struct snd_card *card) +{ + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} + +static int aaci_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + return card ? aaci_do_suspend(card) : 0; +} + +static int aaci_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + return card ? aaci_do_resume(card) : 0; +} + +static SIMPLE_DEV_PM_OPS(aaci_dev_pm_ops, aaci_suspend, aaci_resume); +#define AACI_DEV_PM_OPS (&aaci_dev_pm_ops) +#else +#define AACI_DEV_PM_OPS NULL +#endif + + +static const struct ac97_pcm ac97_defs[] = { + [0] = { /* Front PCM */ + .exclusive = 1, + .r = { + [0] = { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) | + (1 << AC97_SLOT_PCM_CENTER) | + (1 << AC97_SLOT_PCM_SLEFT) | + (1 << AC97_SLOT_PCM_SRIGHT) | + (1 << AC97_SLOT_LFE), + }, + [1] = { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT) | + (1 << AC97_SLOT_PCM_LEFT_0) | + (1 << AC97_SLOT_PCM_RIGHT_0), + }, + }, + }, + [1] = { /* PCM in */ + .stream = 1, + .exclusive = 1, + .r = { + [0] = { + .slots = (1 << AC97_SLOT_PCM_LEFT) | + (1 << AC97_SLOT_PCM_RIGHT), + }, + }, + }, + [2] = { /* Mic in */ + .stream = 1, + .exclusive = 1, + .r = { + [0] = { + .slots = (1 << AC97_SLOT_MIC), + }, + }, + } +}; + +static const struct snd_ac97_bus_ops aaci_bus_ops = { + .write = aaci_ac97_write, + .read = aaci_ac97_read, +}; + +static int aaci_probe_ac97(struct aaci *aaci) +{ + struct snd_ac97_template ac97_template; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + int ret; + + /* + * Assert AACIRESET for 2us + */ + writel(0, aaci->base + AACI_RESET); + udelay(2); + writel(RESET_NRST, aaci->base + AACI_RESET); + + /* + * Give the AC'97 codec more than enough time + * to wake up. (42us = ~2 frames at 48kHz.) + */ + udelay(FRAME_PERIOD_US * 2); + + ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus); + if (ret) + goto out; + + ac97_bus->clock = 48000; + aaci->ac97_bus = ac97_bus; + + memset(&ac97_template, 0, sizeof(struct snd_ac97_template)); + ac97_template.private_data = aaci; + ac97_template.num = 0; + ac97_template.scaps = AC97_SCAP_SKIP_MODEM; + + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97); + if (ret) + goto out; + aaci->ac97 = ac97; + + /* + * Disable AC97 PC Beep input on audio codecs. + */ + if (ac97_is_audio(ac97)) + snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x801e); + + ret = snd_ac97_pcm_assign(ac97_bus, ARRAY_SIZE(ac97_defs), ac97_defs); + if (ret) + goto out; + + aaci->playback.pcm = &ac97_bus->pcms[0]; + aaci->capture.pcm = &ac97_bus->pcms[1]; + + out: + return ret; +} + +static void aaci_free_card(struct snd_card *card) +{ + struct aaci *aaci = card->private_data; + + iounmap(aaci->base); +} + +static struct aaci *aaci_init_card(struct amba_device *dev) +{ + struct aaci *aaci; + struct snd_card *card; + int err; + + err = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, sizeof(struct aaci), &card); + if (err < 0) + return NULL; + + card->private_free = aaci_free_card; + + strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); + strscpy(card->shortname, "ARM AC'97 Interface", sizeof(card->shortname)); + snprintf(card->longname, sizeof(card->longname), + "%s PL%03x rev%u at 0x%08llx, irq %d", + card->shortname, amba_part(dev), amba_rev(dev), + (unsigned long long)dev->res.start, dev->irq[0]); + + aaci = card->private_data; + mutex_init(&aaci->ac97_sem); + mutex_init(&aaci->irq_lock); + aaci->card = card; + aaci->dev = dev; + + /* Set MAINCR to allow slot 1 and 2 data IO */ + aaci->maincr = MAINCR_IE | MAINCR_SL1RXEN | MAINCR_SL1TXEN | + MAINCR_SL2RXEN | MAINCR_SL2TXEN; + + return aaci; +} + +static int aaci_init_pcm(struct aaci *aaci) +{ + struct snd_pcm *pcm; + int ret; + + ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 1, &pcm); + if (ret == 0) { + aaci->pcm = pcm; + pcm->private_data = aaci; + pcm->info_flags = 0; + + strscpy(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_CAPTURE, &aaci_capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, + aaci->card->dev, + 0, 64 * 1024); + } + + return ret; +} + +static unsigned int aaci_size_fifo(struct aaci *aaci) +{ + struct aaci_runtime *aacirun = &aaci->playback; + int i; + + /* + * Enable the channel, but don't assign it to any slots, so + * it won't empty onto the AC'97 link. + */ + writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR); + + for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++) + writel(0, aacirun->fifo); + + writel(0, aacirun->base + AACI_TXCR); + + /* + * Re-initialise the AACI after the FIFO depth test, to + * ensure that the FIFOs are empty. Unfortunately, merely + * disabling the channel doesn't clear the FIFO. + */ + writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR); + readl(aaci->base + AACI_MAINCR); + udelay(1); + writel(aaci->maincr, aaci->base + AACI_MAINCR); + + /* + * If we hit 4096 entries, we failed. Go back to the specified + * fifo depth. + */ + if (i == 4096) + i = 8; + + return i; +} + +static int aaci_probe(struct amba_device *dev, + const struct amba_id *id) +{ + struct aaci *aaci; + int ret, i; + + ret = amba_request_regions(dev, NULL); + if (ret) + return ret; + + aaci = aaci_init_card(dev); + if (!aaci) { + ret = -ENOMEM; + goto out; + } + + aaci->base = ioremap(dev->res.start, resource_size(&dev->res)); + if (!aaci->base) { + ret = -ENOMEM; + goto out; + } + + /* + * Playback uses AACI channel 0 + */ + spin_lock_init(&aaci->playback.lock); + aaci->playback.base = aaci->base + AACI_CSCH1; + aaci->playback.fifo = aaci->base + AACI_DR1; + + /* + * Capture uses AACI channel 0 + */ + spin_lock_init(&aaci->capture.lock); + aaci->capture.base = aaci->base + AACI_CSCH1; + aaci->capture.fifo = aaci->base + AACI_DR1; + + for (i = 0; i < 4; i++) { + void __iomem *base = aaci->base + i * 0x14; + + writel(0, base + AACI_IE); + writel(0, base + AACI_TXCR); + writel(0, base + AACI_RXCR); + } + + writel(0x1fff, aaci->base + AACI_INTCLR); + writel(aaci->maincr, aaci->base + AACI_MAINCR); + /* + * Fix: ac97 read back fail errors by reading + * from any arbitrary aaci register. + */ + readl(aaci->base + AACI_CSCH1); + ret = aaci_probe_ac97(aaci); + if (ret) + goto out; + + /* + * Size the FIFOs (must be multiple of 16). + * This is the number of entries in the FIFO. + */ + aaci->fifo_depth = aaci_size_fifo(aaci); + if (aaci->fifo_depth & 15) { + printk(KERN_WARNING "AACI: FIFO depth %d not supported\n", + aaci->fifo_depth); + ret = -ENODEV; + goto out; + } + + ret = aaci_init_pcm(aaci); + if (ret) + goto out; + + ret = snd_card_register(aaci->card); + if (ret == 0) { + dev_info(&dev->dev, "%s\n", aaci->card->longname); + dev_info(&dev->dev, "FIFO %u entries\n", aaci->fifo_depth); + amba_set_drvdata(dev, aaci->card); + return ret; + } + + out: + if (aaci) + snd_card_free(aaci->card); + amba_release_regions(dev); + return ret; +} + +static void aaci_remove(struct amba_device *dev) +{ + struct snd_card *card = amba_get_drvdata(dev); + + if (card) { + struct aaci *aaci = card->private_data; + writel(0, aaci->base + AACI_MAINCR); + + snd_card_free(card); + amba_release_regions(dev); + } +} + +static struct amba_id aaci_ids[] = { + { + .id = 0x00041041, + .mask = 0x000fffff, + }, + { 0, 0 }, +}; + +MODULE_DEVICE_TABLE(amba, aaci_ids); + +static struct amba_driver aaci_driver = { + .drv = { + .name = DRIVER_NAME, + .pm = AACI_DEV_PM_OPS, + }, + .probe = aaci_probe, + .remove = aaci_remove, + .id_table = aaci_ids, +}; + +module_amba_driver(aaci_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ARM PrimeCell PL041 Advanced Audio CODEC Interface driver"); diff --git a/sound/arm/aaci.h b/sound/arm/aaci.h new file mode 100644 index 000000000..18680e7f8 --- /dev/null +++ b/sound/arm/aaci.h @@ -0,0 +1,247 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver + * + * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. + */ +#ifndef AACI_H +#define AACI_H + +/* + * Control and status register offsets + * P39. + */ +#define AACI_CSCH1 0x000 +#define AACI_CSCH2 0x014 +#define AACI_CSCH3 0x028 +#define AACI_CSCH4 0x03c + +#define AACI_RXCR 0x000 /* 29 bits Control Rx FIFO */ +#define AACI_TXCR 0x004 /* 17 bits Control Tx FIFO */ +#define AACI_SR 0x008 /* 12 bits Status */ +#define AACI_ISR 0x00c /* 7 bits Int Status */ +#define AACI_IE 0x010 /* 7 bits Int Enable */ + +/* + * Other registers + */ +#define AACI_SL1RX 0x050 +#define AACI_SL1TX 0x054 +#define AACI_SL2RX 0x058 +#define AACI_SL2TX 0x05c +#define AACI_SL12RX 0x060 +#define AACI_SL12TX 0x064 +#define AACI_SLFR 0x068 /* slot flags */ +#define AACI_SLISTAT 0x06c /* slot interrupt status */ +#define AACI_SLIEN 0x070 /* slot interrupt enable */ +#define AACI_INTCLR 0x074 /* interrupt clear */ +#define AACI_MAINCR 0x078 /* main control */ +#define AACI_RESET 0x07c /* reset control */ +#define AACI_SYNC 0x080 /* sync control */ +#define AACI_ALLINTS 0x084 /* all fifo interrupt status */ +#define AACI_MAINFR 0x088 /* main flag register */ +#define AACI_DR1 0x090 /* data read/written fifo 1 */ +#define AACI_DR2 0x0b0 /* data read/written fifo 2 */ +#define AACI_DR3 0x0d0 /* data read/written fifo 3 */ +#define AACI_DR4 0x0f0 /* data read/written fifo 4 */ + +/* + * TX/RX fifo control register (CR). P48 + */ +#define CR_FEN (1 << 16) /* fifo enable */ +#define CR_COMPACT (1 << 15) /* compact mode */ +#define CR_SZ16 (0 << 13) /* 16 bits */ +#define CR_SZ18 (1 << 13) /* 18 bits */ +#define CR_SZ20 (2 << 13) /* 20 bits */ +#define CR_SZ12 (3 << 13) /* 12 bits */ +#define CR_SL12 (1 << 12) +#define CR_SL11 (1 << 11) +#define CR_SL10 (1 << 10) +#define CR_SL9 (1 << 9) +#define CR_SL8 (1 << 8) +#define CR_SL7 (1 << 7) +#define CR_SL6 (1 << 6) +#define CR_SL5 (1 << 5) +#define CR_SL4 (1 << 4) +#define CR_SL3 (1 << 3) +#define CR_SL2 (1 << 2) +#define CR_SL1 (1 << 1) +#define CR_EN (1 << 0) /* transmit enable */ + +/* + * status register bits. P49 + */ +#define SR_RXTOFE (1 << 11) /* rx timeout fifo empty */ +#define SR_TXTO (1 << 10) /* rx timeout fifo nonempty */ +#define SR_TXU (1 << 9) /* tx underrun */ +#define SR_RXO (1 << 8) /* rx overrun */ +#define SR_TXB (1 << 7) /* tx busy */ +#define SR_RXB (1 << 6) /* rx busy */ +#define SR_TXFF (1 << 5) /* tx fifo full */ +#define SR_RXFF (1 << 4) /* rx fifo full */ +#define SR_TXHE (1 << 3) /* tx fifo half empty */ +#define SR_RXHF (1 << 2) /* rx fifo half full */ +#define SR_TXFE (1 << 1) /* tx fifo empty */ +#define SR_RXFE (1 << 0) /* rx fifo empty */ + +/* + * interrupt status register bits. + */ +#define ISR_RXTOFEINTR (1 << 6) /* rx fifo empty */ +#define ISR_URINTR (1 << 5) /* tx underflow */ +#define ISR_ORINTR (1 << 4) /* rx overflow */ +#define ISR_RXINTR (1 << 3) /* rx fifo */ +#define ISR_TXINTR (1 << 2) /* tx fifo intr */ +#define ISR_RXTOINTR (1 << 1) /* tx timeout */ +#define ISR_TXCINTR (1 << 0) /* tx complete */ + +/* + * interrupt enable register bits. + */ +#define IE_RXTOIE (1 << 6) +#define IE_URIE (1 << 5) +#define IE_ORIE (1 << 4) +#define IE_RXIE (1 << 3) +#define IE_TXIE (1 << 2) +#define IE_RXTIE (1 << 1) +#define IE_TXCIE (1 << 0) + +/* + * interrupt status. P51 + */ +#define ISR_RXTOFE (1 << 6) /* rx timeout fifo empty */ +#define ISR_UR (1 << 5) /* tx fifo underrun */ +#define ISR_OR (1 << 4) /* rx fifo overrun */ +#define ISR_RX (1 << 3) /* rx interrupt status */ +#define ISR_TX (1 << 2) /* tx interrupt status */ +#define ISR_RXTO (1 << 1) /* rx timeout */ +#define ISR_TXC (1 << 0) /* tx complete */ + +/* + * interrupt enable. P52 + */ +#define IE_RXTOFE (1 << 6) /* rx timeout fifo empty */ +#define IE_UR (1 << 5) /* tx fifo underrun */ +#define IE_OR (1 << 4) /* rx fifo overrun */ +#define IE_RX (1 << 3) /* rx interrupt status */ +#define IE_TX (1 << 2) /* tx interrupt status */ +#define IE_RXTO (1 << 1) /* rx timeout */ +#define IE_TXC (1 << 0) /* tx complete */ + +/* + * slot flag register bits. P56 + */ +#define SLFR_RWIS (1 << 13) /* raw wake-up interrupt status */ +#define SLFR_RGPIOINTR (1 << 12) /* raw gpio interrupt */ +#define SLFR_12TXE (1 << 11) /* slot 12 tx empty */ +#define SLFR_12RXV (1 << 10) /* slot 12 rx valid */ +#define SLFR_2TXE (1 << 9) /* slot 2 tx empty */ +#define SLFR_2RXV (1 << 8) /* slot 2 rx valid */ +#define SLFR_1TXE (1 << 7) /* slot 1 tx empty */ +#define SLFR_1RXV (1 << 6) /* slot 1 rx valid */ +#define SLFR_12TXB (1 << 5) /* slot 12 tx busy */ +#define SLFR_12RXB (1 << 4) /* slot 12 rx busy */ +#define SLFR_2TXB (1 << 3) /* slot 2 tx busy */ +#define SLFR_2RXB (1 << 2) /* slot 2 rx busy */ +#define SLFR_1TXB (1 << 1) /* slot 1 tx busy */ +#define SLFR_1RXB (1 << 0) /* slot 1 rx busy */ + +/* + * Interrupt clear register. + */ +#define ICLR_RXTOFEC4 (1 << 12) +#define ICLR_RXTOFEC3 (1 << 11) +#define ICLR_RXTOFEC2 (1 << 10) +#define ICLR_RXTOFEC1 (1 << 9) +#define ICLR_TXUEC4 (1 << 8) +#define ICLR_TXUEC3 (1 << 7) +#define ICLR_TXUEC2 (1 << 6) +#define ICLR_TXUEC1 (1 << 5) +#define ICLR_RXOEC4 (1 << 4) +#define ICLR_RXOEC3 (1 << 3) +#define ICLR_RXOEC2 (1 << 2) +#define ICLR_RXOEC1 (1 << 1) +#define ICLR_WISC (1 << 0) + +/* + * Main control register bits. P62 + */ +#define MAINCR_SCRA(x) ((x) << 10) /* secondary codec reg access */ +#define MAINCR_DMAEN (1 << 9) /* dma enable */ +#define MAINCR_SL12TXEN (1 << 8) /* slot 12 transmit enable */ +#define MAINCR_SL12RXEN (1 << 7) /* slot 12 receive enable */ +#define MAINCR_SL2TXEN (1 << 6) /* slot 2 transmit enable */ +#define MAINCR_SL2RXEN (1 << 5) /* slot 2 receive enable */ +#define MAINCR_SL1TXEN (1 << 4) /* slot 1 transmit enable */ +#define MAINCR_SL1RXEN (1 << 3) /* slot 1 receive enable */ +#define MAINCR_LPM (1 << 2) /* low power mode */ +#define MAINCR_LOOPBK (1 << 1) /* loopback */ +#define MAINCR_IE (1 << 0) /* aaci interface enable */ + +/* + * Reset register bits. P65 + */ +#define RESET_NRST (1 << 0) + +/* + * Sync register bits. P65 + */ +#define SYNC_FORCE (1 << 0) + +/* + * Main flag register bits. P66 + */ +#define MAINFR_TXB (1 << 1) /* transmit busy */ +#define MAINFR_RXB (1 << 0) /* receive busy */ + + + +struct aaci_runtime { + void __iomem *base; + void __iomem *fifo; + spinlock_t lock; + + struct ac97_pcm *pcm; + int pcm_open; + + u32 cr; + struct snd_pcm_substream *substream; + + unsigned int period; /* byte size of a "period" */ + + /* + * PIO support + */ + void *start; + void *end; + void *ptr; + int bytes; + unsigned int fifo_bytes; +}; + +struct aaci { + struct amba_device *dev; + struct snd_card *card; + void __iomem *base; + unsigned int fifo_depth; + unsigned int users; + struct mutex irq_lock; + + /* AC'97 */ + struct mutex ac97_sem; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97 *ac97; + + u32 maincr; + + struct aaci_runtime playback; + struct aaci_runtime capture; + + struct snd_pcm *pcm; +}; + +#define ACSTREAM_FRONT 0 +#define ACSTREAM_SURROUND 1 +#define ACSTREAM_LFE 2 + +#endif diff --git a/sound/arm/pxa2xx-ac97-lib.c b/sound/arm/pxa2xx-ac97-lib.c new file mode 100644 index 000000000..2ca33fd5a --- /dev/null +++ b/sound/arm/pxa2xx-ac97-lib.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Based on sound/arm/pxa2xx-ac97.c and sound/soc/pxa/pxa2xx-ac97.c + * which contain: + * + * Author: Nicolas Pitre + * Created: Dec 02, 2004 + * Copyright: MontaVista Software Inc. + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> +#include <linux/soc/pxa/cpu.h> + +#include <sound/pxa2xx-lib.h> + +#include <linux/platform_data/asoc-pxa.h> + +#include "pxa2xx-ac97-regs.h" + +static DEFINE_MUTEX(car_mutex); +static DECLARE_WAIT_QUEUE_HEAD(gsr_wq); +static volatile long gsr_bits; +static struct clk *ac97_clk; +static struct clk *ac97conf_clk; +static int reset_gpio; +static void __iomem *ac97_reg_base; + +extern void pxa27x_configure_ac97reset(int reset_gpio, bool to_gpio); + +/* + * Beware PXA27x bugs: + * + * o Slot 12 read from modem space will hang controller. + * o CDONE, SDONE interrupt fails after any slot 12 IO. + * + * We therefore have an hybrid approach for waiting on SDONE (interrupt or + * 1 jiffy timeout if interrupt never comes). + */ + +int pxa2xx_ac97_read(int slot, unsigned short reg) +{ + int val = -ENODEV; + u32 __iomem *reg_addr; + + if (slot > 0) + return -ENODEV; + + mutex_lock(&car_mutex); + + /* set up primary or secondary codec space */ + if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS) + reg_addr = ac97_reg_base + + (slot ? SMC_REG_BASE : PMC_REG_BASE); + else + reg_addr = ac97_reg_base + + (slot ? SAC_REG_BASE : PAC_REG_BASE); + reg_addr += (reg >> 1); + + /* start read access across the ac97 link */ + writel(GSR_CDONE | GSR_SDONE, ac97_reg_base + GSR); + gsr_bits = 0; + val = (readl(reg_addr) & 0xffff); + if (reg == AC97_GPIO_STATUS) + goto out; + if (wait_event_timeout(gsr_wq, (readl(ac97_reg_base + GSR) | gsr_bits) & GSR_SDONE, 1) <= 0 && + !((readl(ac97_reg_base + GSR) | gsr_bits) & GSR_SDONE)) { + printk(KERN_ERR "%s: read error (ac97_reg=%d GSR=%#lx)\n", + __func__, reg, readl(ac97_reg_base + GSR) | gsr_bits); + val = -ETIMEDOUT; + goto out; + } + + /* valid data now */ + writel(GSR_CDONE | GSR_SDONE, ac97_reg_base + GSR); + gsr_bits = 0; + val = (readl(reg_addr) & 0xffff); + /* but we've just started another cycle... */ + wait_event_timeout(gsr_wq, (readl(ac97_reg_base + GSR) | gsr_bits) & GSR_SDONE, 1); + +out: mutex_unlock(&car_mutex); + return val; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_read); + +int pxa2xx_ac97_write(int slot, unsigned short reg, unsigned short val) +{ + u32 __iomem *reg_addr; + int ret = 0; + + mutex_lock(&car_mutex); + + /* set up primary or secondary codec space */ + if (cpu_is_pxa25x() && reg == AC97_GPIO_STATUS) + reg_addr = ac97_reg_base + + (slot ? SMC_REG_BASE : PMC_REG_BASE); + else + reg_addr = ac97_reg_base + + (slot ? SAC_REG_BASE : PAC_REG_BASE); + reg_addr += (reg >> 1); + + writel(GSR_CDONE | GSR_SDONE, ac97_reg_base + GSR); + gsr_bits = 0; + writel(val, reg_addr); + if (wait_event_timeout(gsr_wq, (readl(ac97_reg_base + GSR) | gsr_bits) & GSR_CDONE, 1) <= 0 && + !((readl(ac97_reg_base + GSR) | gsr_bits) & GSR_CDONE)) { + printk(KERN_ERR "%s: write error (ac97_reg=%d GSR=%#lx)\n", + __func__, reg, readl(ac97_reg_base + GSR) | gsr_bits); + ret = -EIO; + } + + mutex_unlock(&car_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_write); + +#ifdef CONFIG_PXA25x +static inline void pxa_ac97_warm_pxa25x(void) +{ + gsr_bits = 0; + + writel(readl(ac97_reg_base + GCR) | (GCR_WARM_RST), ac97_reg_base + GCR); +} + +static inline void pxa_ac97_cold_pxa25x(void) +{ + writel(readl(ac97_reg_base + GCR) & ( GCR_COLD_RST), ac97_reg_base + GCR); /* clear everything but nCRST */ + writel(readl(ac97_reg_base + GCR) & (~GCR_COLD_RST), ac97_reg_base + GCR); /* then assert nCRST */ + + gsr_bits = 0; + + writel(GCR_COLD_RST, ac97_reg_base + GCR); +} +#endif + +#ifdef CONFIG_PXA27x +static inline void pxa_ac97_warm_pxa27x(void) +{ + gsr_bits = 0; + + /* warm reset broken on Bulverde, so manually keep AC97 reset high */ + pxa27x_configure_ac97reset(reset_gpio, true); + udelay(10); + writel(readl(ac97_reg_base + GCR) | (GCR_WARM_RST), ac97_reg_base + GCR); + pxa27x_configure_ac97reset(reset_gpio, false); + udelay(500); +} + +static inline void pxa_ac97_cold_pxa27x(void) +{ + writel(readl(ac97_reg_base + GCR) & ( GCR_COLD_RST), ac97_reg_base + GCR); /* clear everything but nCRST */ + writel(readl(ac97_reg_base + GCR) & (~GCR_COLD_RST), ac97_reg_base + GCR); /* then assert nCRST */ + + gsr_bits = 0; + + /* PXA27x Developers Manual section 13.5.2.2.1 */ + clk_prepare_enable(ac97conf_clk); + udelay(5); + clk_disable_unprepare(ac97conf_clk); + writel(GCR_COLD_RST | GCR_WARM_RST, ac97_reg_base + GCR); +} +#endif + +#ifdef CONFIG_PXA3xx +static inline void pxa_ac97_warm_pxa3xx(void) +{ + gsr_bits = 0; + + /* Can't use interrupts */ + writel(readl(ac97_reg_base + GCR) | (GCR_WARM_RST), ac97_reg_base + GCR); +} + +static inline void pxa_ac97_cold_pxa3xx(void) +{ + /* Hold CLKBPB for 100us */ + writel(0, ac97_reg_base + GCR); + writel(GCR_CLKBPB, ac97_reg_base + GCR); + udelay(100); + writel(0, ac97_reg_base + GCR); + + writel(readl(ac97_reg_base + GCR) & ( GCR_COLD_RST), ac97_reg_base + GCR); /* clear everything but nCRST */ + writel(readl(ac97_reg_base + GCR) & (~GCR_COLD_RST), ac97_reg_base + GCR); /* then assert nCRST */ + + gsr_bits = 0; + + /* Can't use interrupts on PXA3xx */ + writel(readl(ac97_reg_base + GCR) & (~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN)), ac97_reg_base + GCR); + + writel(GCR_WARM_RST | GCR_COLD_RST, ac97_reg_base + GCR); +} +#endif + +bool pxa2xx_ac97_try_warm_reset(void) +{ + unsigned long gsr; + unsigned int timeout = 100; + +#ifdef CONFIG_PXA25x + if (cpu_is_pxa25x()) + pxa_ac97_warm_pxa25x(); + else +#endif +#ifdef CONFIG_PXA27x + if (cpu_is_pxa27x()) + pxa_ac97_warm_pxa27x(); + else +#endif +#ifdef CONFIG_PXA3xx + if (cpu_is_pxa3xx()) + pxa_ac97_warm_pxa3xx(); + else +#endif + snd_BUG(); + + while (!((readl(ac97_reg_base + GSR) | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--) + mdelay(1); + + gsr = readl(ac97_reg_base + GSR) | gsr_bits; + if (!(gsr & (GSR_PCR | GSR_SCR))) { + printk(KERN_INFO "%s: warm reset timeout (GSR=%#lx)\n", + __func__, gsr); + + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_warm_reset); + +bool pxa2xx_ac97_try_cold_reset(void) +{ + unsigned long gsr; + unsigned int timeout = 1000; + +#ifdef CONFIG_PXA25x + if (cpu_is_pxa25x()) + pxa_ac97_cold_pxa25x(); + else +#endif +#ifdef CONFIG_PXA27x + if (cpu_is_pxa27x()) + pxa_ac97_cold_pxa27x(); + else +#endif +#ifdef CONFIG_PXA3xx + if (cpu_is_pxa3xx()) + pxa_ac97_cold_pxa3xx(); + else +#endif + snd_BUG(); + + while (!((readl(ac97_reg_base + GSR) | gsr_bits) & (GSR_PCR | GSR_SCR)) && timeout--) + mdelay(1); + + gsr = readl(ac97_reg_base + GSR) | gsr_bits; + if (!(gsr & (GSR_PCR | GSR_SCR))) { + printk(KERN_INFO "%s: cold reset timeout (GSR=%#lx)\n", + __func__, gsr); + + return false; + } + + return true; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_try_cold_reset); + + +void pxa2xx_ac97_finish_reset(void) +{ + u32 gcr = readl(ac97_reg_base + GCR); + gcr &= ~(GCR_PRIRDY_IEN|GCR_SECRDY_IEN); + gcr |= GCR_SDONE_IE|GCR_CDONE_IE; + writel(gcr, ac97_reg_base + GCR); +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_finish_reset); + +static irqreturn_t pxa2xx_ac97_irq(int irq, void *dev_id) +{ + long status; + + status = readl(ac97_reg_base + GSR); + if (status) { + writel(status, ac97_reg_base + GSR); + gsr_bits |= status; + wake_up(&gsr_wq); + + /* Although we don't use those we still need to clear them + since they tend to spuriously trigger when MMC is used + (hardware bug? go figure)... */ + if (cpu_is_pxa27x()) { + writel(MISR_EOC, ac97_reg_base + MISR); + writel(PISR_EOC, ac97_reg_base + PISR); + writel(MCSR_EOC, ac97_reg_base + MCSR); + } + + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +#ifdef CONFIG_PM +int pxa2xx_ac97_hw_suspend(void) +{ + writel(readl(ac97_reg_base + GCR) | (GCR_ACLINK_OFF), ac97_reg_base + GCR); + clk_disable_unprepare(ac97_clk); + return 0; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_suspend); + +int pxa2xx_ac97_hw_resume(void) +{ + clk_prepare_enable(ac97_clk); + return 0; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_resume); +#endif + +int pxa2xx_ac97_hw_probe(struct platform_device *dev) +{ + int ret; + int irq; + pxa2xx_audio_ops_t *pdata = dev->dev.platform_data; + + ac97_reg_base = devm_platform_ioremap_resource(dev, 0); + if (IS_ERR(ac97_reg_base)) { + dev_err(&dev->dev, "Missing MMIO resource\n"); + return PTR_ERR(ac97_reg_base); + } + + if (pdata) { + switch (pdata->reset_gpio) { + case 95: + case 113: + reset_gpio = pdata->reset_gpio; + break; + case 0: + reset_gpio = 113; + break; + case -1: + break; + default: + dev_err(&dev->dev, "Invalid reset GPIO %d\n", + pdata->reset_gpio); + } + } else if (!pdata && dev->dev.of_node) { + pdata = devm_kzalloc(&dev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + pdata->reset_gpio = of_get_named_gpio(dev->dev.of_node, + "reset-gpios", 0); + if (pdata->reset_gpio == -ENOENT) + pdata->reset_gpio = -1; + else if (pdata->reset_gpio < 0) + return pdata->reset_gpio; + reset_gpio = pdata->reset_gpio; + } else { + if (cpu_is_pxa27x()) + reset_gpio = 113; + } + + if (cpu_is_pxa27x()) { + /* + * This gpio is needed for a work-around to a bug in the ac97 + * controller during warm reset. The direction and level is set + * here so that it is an output driven high when switching from + * AC97_nRESET alt function to generic gpio. + */ + ret = gpio_request_one(reset_gpio, GPIOF_OUT_INIT_HIGH, + "pxa27x ac97 reset"); + if (ret < 0) { + pr_err("%s: gpio_request_one() failed: %d\n", + __func__, ret); + goto err_conf; + } + pxa27x_configure_ac97reset(reset_gpio, false); + + ac97conf_clk = clk_get(&dev->dev, "AC97CONFCLK"); + if (IS_ERR(ac97conf_clk)) { + ret = PTR_ERR(ac97conf_clk); + ac97conf_clk = NULL; + goto err_conf; + } + } + + ac97_clk = clk_get(&dev->dev, "AC97CLK"); + if (IS_ERR(ac97_clk)) { + ret = PTR_ERR(ac97_clk); + ac97_clk = NULL; + goto err_clk; + } + + ret = clk_prepare_enable(ac97_clk); + if (ret) + goto err_clk2; + + irq = platform_get_irq(dev, 0); + if (irq < 0) { + ret = irq; + goto err_irq; + } + + ret = request_irq(irq, pxa2xx_ac97_irq, 0, "AC97", NULL); + if (ret < 0) + goto err_irq; + + return 0; + +err_irq: + writel(readl(ac97_reg_base + GCR) | (GCR_ACLINK_OFF), ac97_reg_base + GCR); +err_clk2: + clk_put(ac97_clk); + ac97_clk = NULL; +err_clk: + if (ac97conf_clk) { + clk_put(ac97conf_clk); + ac97conf_clk = NULL; + } +err_conf: + return ret; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_probe); + +void pxa2xx_ac97_hw_remove(struct platform_device *dev) +{ + if (cpu_is_pxa27x()) + gpio_free(reset_gpio); + writel(readl(ac97_reg_base + GCR) | (GCR_ACLINK_OFF), ac97_reg_base + GCR); + free_irq(platform_get_irq(dev, 0), NULL); + if (ac97conf_clk) { + clk_put(ac97conf_clk); + ac97conf_clk = NULL; + } + clk_disable_unprepare(ac97_clk); + clk_put(ac97_clk); + ac97_clk = NULL; +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_hw_remove); + +u32 pxa2xx_ac97_read_modr(void) +{ + if (!ac97_reg_base) + return 0; + + return readl(ac97_reg_base + MODR); +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_read_modr); + +u32 pxa2xx_ac97_read_misr(void) +{ + if (!ac97_reg_base) + return 0; + + return readl(ac97_reg_base + MISR); +} +EXPORT_SYMBOL_GPL(pxa2xx_ac97_read_misr); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("Intel/Marvell PXA sound library"); +MODULE_LICENSE("GPL"); + diff --git a/sound/arm/pxa2xx-ac97-regs.h b/sound/arm/pxa2xx-ac97-regs.h new file mode 100644 index 000000000..ae638a1b9 --- /dev/null +++ b/sound/arm/pxa2xx-ac97-regs.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ASM_ARCH_REGS_AC97_H +#define __ASM_ARCH_REGS_AC97_H + +/* + * AC97 Controller registers + */ + +#define POCR (0x0000) /* PCM Out Control Register */ +#define POCR_FEIE (1 << 3) /* FIFO Error Interrupt Enable */ +#define POCR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */ + +#define PICR (0x0004) /* PCM In Control Register */ +#define PICR_FEIE (1 << 3) /* FIFO Error Interrupt Enable */ +#define PICR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */ + +#define MCCR (0x0008) /* Mic In Control Register */ +#define MCCR_FEIE (1 << 3) /* FIFO Error Interrupt Enable */ +#define MCCR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */ + +#define GCR (0x000C) /* Global Control Register */ +#ifdef CONFIG_PXA3xx +#define GCR_CLKBPB (1 << 31) /* Internal clock enable */ +#endif +#define GCR_nDMAEN (1 << 24) /* non DMA Enable */ +#define GCR_CDONE_IE (1 << 19) /* Command Done Interrupt Enable */ +#define GCR_SDONE_IE (1 << 18) /* Status Done Interrupt Enable */ +#define GCR_SECRDY_IEN (1 << 9) /* Secondary Ready Interrupt Enable */ +#define GCR_PRIRDY_IEN (1 << 8) /* Primary Ready Interrupt Enable */ +#define GCR_SECRES_IEN (1 << 5) /* Secondary Resume Interrupt Enable */ +#define GCR_PRIRES_IEN (1 << 4) /* Primary Resume Interrupt Enable */ +#define GCR_ACLINK_OFF (1 << 3) /* AC-link Shut Off */ +#define GCR_WARM_RST (1 << 2) /* AC97 Warm Reset */ +#define GCR_COLD_RST (1 << 1) /* AC'97 Cold Reset (0 = active) */ +#define GCR_GIE (1 << 0) /* Codec GPI Interrupt Enable */ + +#define POSR (0x0010) /* PCM Out Status Register */ +#define POSR_FIFOE (1 << 4) /* FIFO error */ +#define POSR_FSR (1 << 2) /* FIFO Service Request */ + +#define PISR (0x0014) /* PCM In Status Register */ +#define PISR_FIFOE (1 << 4) /* FIFO error */ +#define PISR_EOC (1 << 3) /* DMA End-of-Chain (exclusive clear) */ +#define PISR_FSR (1 << 2) /* FIFO Service Request */ + +#define MCSR (0x0018) /* Mic In Status Register */ +#define MCSR_FIFOE (1 << 4) /* FIFO error */ +#define MCSR_EOC (1 << 3) /* DMA End-of-Chain (exclusive clear) */ +#define MCSR_FSR (1 << 2) /* FIFO Service Request */ + +#define GSR (0x001C) /* Global Status Register */ +#define GSR_CDONE (1 << 19) /* Command Done */ +#define GSR_SDONE (1 << 18) /* Status Done */ +#define GSR_RDCS (1 << 15) /* Read Completion Status */ +#define GSR_BIT3SLT12 (1 << 14) /* Bit 3 of slot 12 */ +#define GSR_BIT2SLT12 (1 << 13) /* Bit 2 of slot 12 */ +#define GSR_BIT1SLT12 (1 << 12) /* Bit 1 of slot 12 */ +#define GSR_SECRES (1 << 11) /* Secondary Resume Interrupt */ +#define GSR_PRIRES (1 << 10) /* Primary Resume Interrupt */ +#define GSR_SCR (1 << 9) /* Secondary Codec Ready */ +#define GSR_PCR (1 << 8) /* Primary Codec Ready */ +#define GSR_MCINT (1 << 7) /* Mic In Interrupt */ +#define GSR_POINT (1 << 6) /* PCM Out Interrupt */ +#define GSR_PIINT (1 << 5) /* PCM In Interrupt */ +#define GSR_ACOFFD (1 << 3) /* AC-link Shut Off Done */ +#define GSR_MOINT (1 << 2) /* Modem Out Interrupt */ +#define GSR_MIINT (1 << 1) /* Modem In Interrupt */ +#define GSR_GSCI (1 << 0) /* Codec GPI Status Change Interrupt */ + +#define CAR (0x0020) /* CODEC Access Register */ +#define CAR_CAIP (1 << 0) /* Codec Access In Progress */ + +#define PCDR (0x0040) /* PCM FIFO Data Register */ +#define MCDR (0x0060) /* Mic-in FIFO Data Register */ + +#define MOCR (0x0100) /* Modem Out Control Register */ +#define MOCR_FEIE (1 << 3) /* FIFO Error */ +#define MOCR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */ + +#define MICR (0x0108) /* Modem In Control Register */ +#define MICR_FEIE (1 << 3) /* FIFO Error */ +#define MICR_FSRIE (1 << 1) /* FIFO Service Request Interrupt Enable */ + +#define MOSR (0x0110) /* Modem Out Status Register */ +#define MOSR_FIFOE (1 << 4) /* FIFO error */ +#define MOSR_FSR (1 << 2) /* FIFO Service Request */ + +#define MISR (0x0118) /* Modem In Status Register */ +#define MISR_FIFOE (1 << 4) /* FIFO error */ +#define MISR_EOC (1 << 3) /* DMA End-of-Chain (exclusive clear) */ +#define MISR_FSR (1 << 2) /* FIFO Service Request */ + +#define MODR (0x0140) /* Modem FIFO Data Register */ + +#define PAC_REG_BASE (0x0200) /* Primary Audio Codec */ +#define SAC_REG_BASE (0x0300) /* Secondary Audio Codec */ +#define PMC_REG_BASE (0x0400) /* Primary Modem Codec */ +#define SMC_REG_BASE (0x0500) /* Secondary Modem Codec */ + +#endif /* __ASM_ARCH_REGS_AC97_H */ diff --git a/sound/arm/pxa2xx-ac97.c b/sound/arm/pxa2xx-ac97.c new file mode 100644 index 000000000..c16208645 --- /dev/null +++ b/sound/arm/pxa2xx-ac97.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/pxa2xx-ac97.c -- AC97 support for the Intel PXA2xx chip. + * + * Author: Nicolas Pitre + * Created: Dec 02, 2004 + * Copyright: MontaVista Software Inc. + */ + +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/pxa2xx-lib.h> +#include <sound/dmaengine_pcm.h> + +#include <linux/platform_data/asoc-pxa.h> + +static void pxa2xx_ac97_legacy_reset(struct snd_ac97 *ac97) +{ + if (!pxa2xx_ac97_try_cold_reset()) + pxa2xx_ac97_try_warm_reset(); + + pxa2xx_ac97_finish_reset(); +} + +static unsigned short pxa2xx_ac97_legacy_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + int ret; + + ret = pxa2xx_ac97_read(ac97->num, reg); + if (ret < 0) + return 0; + else + return (unsigned short)(ret & 0xffff); +} + +static void pxa2xx_ac97_legacy_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + pxa2xx_ac97_write(ac97->num, reg, val); +} + +static const struct snd_ac97_bus_ops pxa2xx_ac97_ops = { + .read = pxa2xx_ac97_legacy_read, + .write = pxa2xx_ac97_legacy_write, + .reset = pxa2xx_ac97_legacy_reset, +}; + +static struct snd_pcm *pxa2xx_ac97_pcm; +static struct snd_ac97 *pxa2xx_ac97_ac97; + +static int pxa2xx_ac97_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + pxa2xx_audio_ops_t *platform_ops; + int ret, i; + + ret = pxa2xx_pcm_open(substream); + if (ret) + return ret; + + runtime->hw.channels_min = 2; + runtime->hw.channels_max = 2; + + i = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_RATES_FRONT_DAC : AC97_RATES_ADC; + runtime->hw.rates = pxa2xx_ac97_ac97->rates[i]; + snd_pcm_limit_hw_rates(runtime); + + platform_ops = substream->pcm->card->dev->platform_data; + if (platform_ops && platform_ops->startup) { + ret = platform_ops->startup(substream, platform_ops->priv); + if (ret < 0) + pxa2xx_pcm_close(substream); + } + + return ret; +} + +static int pxa2xx_ac97_pcm_close(struct snd_pcm_substream *substream) +{ + pxa2xx_audio_ops_t *platform_ops; + + platform_ops = substream->pcm->card->dev->platform_data; + if (platform_ops && platform_ops->shutdown) + platform_ops->shutdown(substream, platform_ops->priv); + + return 0; +} + +static int pxa2xx_ac97_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE; + int ret; + + ret = pxa2xx_pcm_prepare(substream); + if (ret < 0) + return ret; + + return snd_ac97_set_rate(pxa2xx_ac97_ac97, reg, runtime->rate); +} + +#ifdef CONFIG_PM_SLEEP + +static int pxa2xx_ac97_do_suspend(struct snd_card *card) +{ + pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3cold); + snd_ac97_suspend(pxa2xx_ac97_ac97); + if (platform_ops && platform_ops->suspend) + platform_ops->suspend(platform_ops->priv); + + return pxa2xx_ac97_hw_suspend(); +} + +static int pxa2xx_ac97_do_resume(struct snd_card *card) +{ + pxa2xx_audio_ops_t *platform_ops = card->dev->platform_data; + int rc; + + rc = pxa2xx_ac97_hw_resume(); + if (rc) + return rc; + + if (platform_ops && platform_ops->resume) + platform_ops->resume(platform_ops->priv); + snd_ac97_resume(pxa2xx_ac97_ac97); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + + return 0; +} + +static int pxa2xx_ac97_suspend(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int ret = 0; + + if (card) + ret = pxa2xx_ac97_do_suspend(card); + + return ret; +} + +static int pxa2xx_ac97_resume(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + int ret = 0; + + if (card) + ret = pxa2xx_ac97_do_resume(card); + + return ret; +} + +static SIMPLE_DEV_PM_OPS(pxa2xx_ac97_pm_ops, pxa2xx_ac97_suspend, pxa2xx_ac97_resume); +#endif + +static const struct snd_pcm_ops pxa2xx_ac97_pcm_ops = { + .open = pxa2xx_ac97_pcm_open, + .close = pxa2xx_ac97_pcm_close, + .hw_params = pxa2xx_pcm_hw_params, + .prepare = pxa2xx_ac97_pcm_prepare, + .trigger = pxa2xx_pcm_trigger, + .pointer = pxa2xx_pcm_pointer, +}; + + +static int pxa2xx_ac97_pcm_new(struct snd_card *card) +{ + struct snd_pcm *pcm; + int ret; + + ret = snd_pcm_new(card, "PXA2xx-PCM", 0, 1, 1, &pcm); + if (ret) + goto out; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + goto out; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pxa2xx_ac97_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pxa2xx_ac97_pcm_ops); + ret = pxa2xx_pcm_preallocate_dma_buffer(pcm); + if (ret) + goto out; + + pxa2xx_ac97_pcm = pcm; + ret = 0; + + out: + return ret; +} + +static int pxa2xx_ac97_probe(struct platform_device *dev) +{ + struct snd_card *card; + struct snd_ac97_bus *ac97_bus; + struct snd_ac97_template ac97_template; + int ret; + pxa2xx_audio_ops_t *pdata = dev->dev.platform_data; + + if (dev->id >= 0) { + dev_err(&dev->dev, "PXA2xx has only one AC97 port.\n"); + ret = -ENXIO; + goto err_dev; + } + + ret = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, + THIS_MODULE, 0, &card); + if (ret < 0) + goto err; + + strscpy(card->driver, dev->dev.driver->name, sizeof(card->driver)); + + ret = pxa2xx_ac97_pcm_new(card); + if (ret) + goto err; + + ret = pxa2xx_ac97_hw_probe(dev); + if (ret) + goto err; + + ret = snd_ac97_bus(card, 0, &pxa2xx_ac97_ops, NULL, &ac97_bus); + if (ret) + goto err_remove; + memset(&ac97_template, 0, sizeof(ac97_template)); + ret = snd_ac97_mixer(ac97_bus, &ac97_template, &pxa2xx_ac97_ac97); + if (ret) + goto err_remove; + + snprintf(card->shortname, sizeof(card->shortname), + "%s", snd_ac97_get_short_name(pxa2xx_ac97_ac97)); + snprintf(card->longname, sizeof(card->longname), + "%s (%s)", dev->dev.driver->name, card->mixername); + + if (pdata && pdata->codec_pdata[0]) + snd_ac97_dev_add_pdata(ac97_bus->codec[0], pdata->codec_pdata[0]); + ret = snd_card_register(card); + if (ret == 0) { + platform_set_drvdata(dev, card); + return 0; + } + +err_remove: + pxa2xx_ac97_hw_remove(dev); +err: + if (card) + snd_card_free(card); +err_dev: + return ret; +} + +static int pxa2xx_ac97_remove(struct platform_device *dev) +{ + struct snd_card *card = platform_get_drvdata(dev); + + if (card) { + snd_card_free(card); + pxa2xx_ac97_hw_remove(dev); + } + + return 0; +} + +static struct platform_driver pxa2xx_ac97_driver = { + .probe = pxa2xx_ac97_probe, + .remove = pxa2xx_ac97_remove, + .driver = { + .name = "pxa2xx-ac97", +#ifdef CONFIG_PM_SLEEP + .pm = &pxa2xx_ac97_pm_ops, +#endif + }, +}; + +module_platform_driver(pxa2xx_ac97_driver); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("AC97 driver for the Intel PXA2xx chip"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pxa2xx-ac97"); diff --git a/sound/arm/pxa2xx-pcm-lib.c b/sound/arm/pxa2xx-pcm-lib.c new file mode 100644 index 000000000..0a48805e5 --- /dev/null +++ b/sound/arm/pxa2xx-pcm-lib.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/dma/pxa-dma.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/pxa2xx-lib.h> +#include <sound/dmaengine_pcm.h> + +static const struct snd_pcm_hardware pxa2xx_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE, + .period_bytes_min = 32, + .period_bytes_max = 8192 - 32, + .periods_min = 1, + .periods_max = 256, + .buffer_bytes_max = 128 * 1024, + .fifo_size = 32, +}; + +int pxa2xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_dmaengine_dai_dma_data *dma_params; + struct dma_slave_config config; + int ret; + + dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + if (!dma_params) + return 0; + + ret = snd_hwparams_to_dma_slave_config(substream, params, &config); + if (ret) + return ret; + + snd_dmaengine_pcm_set_config_from_dai_data(substream, + snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream), + &config); + + ret = dmaengine_slave_config(chan, &config); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(pxa2xx_pcm_hw_params); + +int pxa2xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_dmaengine_pcm_trigger(substream, cmd); +} +EXPORT_SYMBOL(pxa2xx_pcm_trigger); + +snd_pcm_uframes_t +pxa2xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_pointer(substream); +} +EXPORT_SYMBOL(pxa2xx_pcm_pointer); + +int pxa2xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} +EXPORT_SYMBOL(pxa2xx_pcm_prepare); + +int pxa2xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_dmaengine_dai_dma_data *dma_params; + int ret; + + runtime->hw = pxa2xx_pcm_hardware; + + dma_params = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + if (!dma_params) + return 0; + + /* + * For mysterious reasons (and despite what the manual says) + * playback samples are lost if the DMA count is not a multiple + * of the DMA burst size. Let's add a rule to enforce that. + */ + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + return snd_dmaengine_pcm_open( + substream, dma_request_slave_channel(asoc_rtd_to_cpu(rtd, 0)->dev, + dma_params->chan_name)); +} +EXPORT_SYMBOL(pxa2xx_pcm_open); + +int pxa2xx_pcm_close(struct snd_pcm_substream *substream) +{ + return snd_dmaengine_pcm_close_release_chan(substream); +} +EXPORT_SYMBOL(pxa2xx_pcm_close); + +int pxa2xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm) +{ + size_t size = pxa2xx_pcm_hardware.buffer_bytes_max; + + return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC, + pcm->card->dev, size); +} +EXPORT_SYMBOL(pxa2xx_pcm_preallocate_dma_buffer); + +int pxa2xx_soc_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret; + + ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + return pxa2xx_pcm_preallocate_dma_buffer(pcm); +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_new); + +int pxa2xx_soc_pcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return pxa2xx_pcm_open(substream); +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_open); + +int pxa2xx_soc_pcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return pxa2xx_pcm_close(substream); +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_close); + +int pxa2xx_soc_pcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + return pxa2xx_pcm_hw_params(substream, params); +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_hw_params); + +int pxa2xx_soc_pcm_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return pxa2xx_pcm_prepare(substream); +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_prepare); + +int pxa2xx_soc_pcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + return pxa2xx_pcm_trigger(substream, cmd); +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_trigger); + +snd_pcm_uframes_t +pxa2xx_soc_pcm_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + return pxa2xx_pcm_pointer(substream); +} +EXPORT_SYMBOL(pxa2xx_soc_pcm_pointer); + +MODULE_AUTHOR("Nicolas Pitre"); +MODULE_DESCRIPTION("Intel PXA2xx sound library"); +MODULE_LICENSE("GPL"); |