diff options
author | Kumar Gala <galak@kernel.crashing.org> | 2007-02-12 23:57:21 -0600 |
---|---|---|
committer | Kumar Gala <galak@kernel.crashing.org> | 2007-02-12 23:57:21 -0600 |
commit | 54c66f6d781e03dc0b23956234963c4911e6d1c0 (patch) | |
tree | 40619a66ae6d8703a57bf681d087ffeabbffd346 /sound/soc/at91 | |
parent | 8ce0a7df6e6117d8814e976d4b7ce6a6b2c9cf93 (diff) | |
parent | 17e0e27020d028a790d97699aff85a43af5be472 (diff) |
Merge branch 'master' into 85xx
Diffstat (limited to 'sound/soc/at91')
-rw-r--r-- | sound/soc/at91/Kconfig | 32 | ||||
-rw-r--r-- | sound/soc/at91/Makefile | 11 | ||||
-rw-r--r-- | sound/soc/at91/at91-i2s.c | 720 | ||||
-rw-r--r-- | sound/soc/at91/at91-i2s.h | 27 | ||||
-rw-r--r-- | sound/soc/at91/at91-pcm.c | 432 | ||||
-rw-r--r-- | sound/soc/at91/at91-pcm.h | 72 | ||||
-rw-r--r-- | sound/soc/at91/eti_b1_wm8731.c | 375 |
7 files changed, 1669 insertions, 0 deletions
diff --git a/sound/soc/at91/Kconfig b/sound/soc/at91/Kconfig new file mode 100644 index 00000000000..5bcf08b728b --- /dev/null +++ b/sound/soc/at91/Kconfig @@ -0,0 +1,32 @@ +menu "SoC Audio for the Atmel AT91" + +config SND_AT91_SOC + tristate "SoC Audio for the Atmel AT91 System-on-Chip" + depends on ARCH_AT91 && SND + select SND_PCM + help + Say Y or M if you want to add support for codecs attached to + the AT91 SSC interface. You will also need + to select the audio interfaces to support below. + +config SND_AT91_SOC_I2S + tristate + +config SND_AT91_SOC_ETI_B1_WM8731 + tristate "SoC I2S Audio support for WM8731-based Endrelia ETI-B1 boards" + depends on SND_AT91_SOC && (MACH_ETI_B1 || MACH_ETI_C1) + select SND_AT91_SOC_I2S + select SND_SOC_WM8731 + help + Say Y if you want to add support for SoC audio on WM8731-based + Endrelia Technologies Inc ETI-B1 or ETI-C1 boards. + +config SND_AT91_SOC_ETI_SLAVE + bool "Run codec in slave Mode on Endrelia boards" + depends on SND_AT91_SOC_ETI_B1_WM8731 + default n + help + Say Y if you want to run with the AT91 SSC generating the BCLK + and LRC signals on Endrelia boards. + +endmenu diff --git a/sound/soc/at91/Makefile b/sound/soc/at91/Makefile new file mode 100644 index 00000000000..b77b01ab202 --- /dev/null +++ b/sound/soc/at91/Makefile @@ -0,0 +1,11 @@ +# AT91 Platform Support +snd-soc-at91-objs := at91-pcm.o +snd-soc-at91-i2s-objs := at91-i2s.o + +obj-$(CONFIG_SND_AT91_SOC) += snd-soc-at91.o +obj-$(CONFIG_SND_AT91_SOC_I2S) += snd-soc-at91-i2s.o + +# AT91 Machine Support +snd-soc-eti-b1-wm8731-objs := eti_b1_wm8731.o + +obj-$(CONFIG_SND_AT91_SOC_ETI_B1_WM8731) += snd-soc-eti-b1-wm8731.o diff --git a/sound/soc/at91/at91-i2s.c b/sound/soc/at91/at91-i2s.c new file mode 100644 index 00000000000..fcc544a96ba --- /dev/null +++ b/sound/soc/at91/at91-i2s.c @@ -0,0 +1,720 @@ +/* + * at91-i2s.c -- ALSA SoC I2S Audio Layer Platform driver + * + * Author: Frank Mandarino <fmandarino@endrelia.com> + * Endrelia Technologies Inc. + * + * Based on pxa2xx Platform drivers by + * Liam Girdwood <liam.girdwood@wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <asm/arch/hardware.h> +#include <asm/arch/at91_pmc.h> +#include <asm/arch/at91_ssc.h> +#include <asm/arch/at91_pdc.h> + +#include "at91-pcm.h" +#include "at91-i2s.h" + +#if 0 +#define DBG(x...) printk(KERN_DEBUG "at91-i2s:" x) +#else +#define DBG(x...) +#endif + +#if defined(CONFIG_ARCH_AT91SAM9260) +#define NUM_SSC_DEVICES 1 +#else +#define NUM_SSC_DEVICES 3 +#endif + + +/* + * SSC PDC registers required by the PCM DMA engine. + */ +static struct at91_pdc_regs pdc_tx_reg = { + .xpr = AT91_PDC_TPR, + .xcr = AT91_PDC_TCR, + .xnpr = AT91_PDC_TNPR, + .xncr = AT91_PDC_TNCR, +}; + +static struct at91_pdc_regs pdc_rx_reg = { + .xpr = AT91_PDC_RPR, + .xcr = AT91_PDC_RCR, + .xnpr = AT91_PDC_RNPR, + .xncr = AT91_PDC_RNCR, +}; + +/* + * SSC & PDC status bits for transmit and receive. + */ +static struct at91_ssc_mask ssc_tx_mask = { + .ssc_enable = AT91_SSC_TXEN, + .ssc_disable = AT91_SSC_TXDIS, + .ssc_endx = AT91_SSC_ENDTX, + .ssc_endbuf = AT91_SSC_TXBUFE, + .pdc_enable = AT91_PDC_TXTEN, + .pdc_disable = AT91_PDC_TXTDIS, +}; + +static struct at91_ssc_mask ssc_rx_mask = { + .ssc_enable = AT91_SSC_RXEN, + .ssc_disable = AT91_SSC_RXDIS, + .ssc_endx = AT91_SSC_ENDRX, + .ssc_endbuf = AT91_SSC_RXBUFF, + .pdc_enable = AT91_PDC_RXTEN, + .pdc_disable = AT91_PDC_RXTDIS, +}; + + +/* + * DMA parameters. + */ +static struct at91_pcm_dma_params ssc_dma_params[NUM_SSC_DEVICES][2] = { + {{ + .name = "SSC0/I2S PCM Stereo out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC0/I2S PCM Stereo in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + }}, +#if NUM_SSC_DEVICES == 3 + {{ + .name = "SSC1/I2S PCM Stereo out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC1/I2S PCM Stereo in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + }}, + {{ + .name = "SSC2/I2S PCM Stereo out", + .pdc = &pdc_tx_reg, + .mask = &ssc_tx_mask, + }, + { + .name = "SSC1/I2S PCM Stereo in", + .pdc = &pdc_rx_reg, + .mask = &ssc_rx_mask, + }}, +#endif +}; + +struct at91_ssc_state { + u32 ssc_cmr; + u32 ssc_rcmr; + u32 ssc_rfmr; + u32 ssc_tcmr; + u32 ssc_tfmr; + u32 ssc_sr; + u32 ssc_imr; +}; + +static struct at91_ssc_info { + char *name; + struct at91_ssc_periph ssc; + spinlock_t lock; /* lock for dir_mask */ + unsigned short dir_mask; /* 0=unused, 1=playback, 2=capture */ + unsigned short initialized; /* 1=SSC has been initialized */ + unsigned short daifmt; + unsigned short cmr_div; + unsigned short tcmr_period; + unsigned short rcmr_period; + struct at91_pcm_dma_params *dma_params[2]; + struct at91_ssc_state ssc_state; + +} ssc_info[NUM_SSC_DEVICES] = { + { + .name = "ssc0", + .lock = SPIN_LOCK_UNLOCKED, + .dir_mask = 0, + .initialized = 0, + }, +#if NUM_SSC_DEVICES == 3 + { + .name = "ssc1", + .lock = SPIN_LOCK_UNLOCKED, + .dir_mask = 0, + .initialized = 0, + }, + { + .name = "ssc2", + .lock = SPIN_LOCK_UNLOCKED, + .dir_mask = 0, + .initialized = 0, + }, +#endif +}; + +static unsigned int at91_i2s_sysclk; + +/* + * SSC interrupt handler. Passes PDC interrupts to the DMA + * interrupt handler in the PCM driver. + */ +static irqreturn_t at91_i2s_interrupt(int irq, void *dev_id) +{ + struct at91_ssc_info *ssc_p = dev_id; + struct at91_pcm_dma_params *dma_params; + u32 ssc_sr; + int i; + + ssc_sr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR) + & at91_ssc_read(ssc_p->ssc.base + AT91_SSC_IMR); + + /* + * Loop through the substreams attached to this SSC. If + * a DMA-related interrupt occurred on that substream, call + * the DMA interrupt handler function, if one has been + * registered in the dma_params structure by the PCM driver. + */ + for (i = 0; i < ARRAY_SIZE(ssc_p->dma_params); i++) { + dma_params = ssc_p->dma_params[i]; + + if (dma_params != NULL && dma_params->dma_intr_handler != NULL && + (ssc_sr & + (dma_params->mask->ssc_endx | dma_params->mask->ssc_endbuf))) + + dma_params->dma_intr_handler(ssc_sr, dma_params->substream); + } + + return IRQ_HANDLED; +} + +/* + * Startup. Only that one substream allowed in each direction. + */ +static int at91_i2s_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; + int dir_mask; + + DBG("i2s_startup: SSC_SR=0x%08lx\n", + at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR)); + dir_mask = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0x1 : 0x2; + + spin_lock_irq(&ssc_p->lock); + if (ssc_p->dir_mask & dir_mask) { + spin_unlock_irq(&ssc_p->lock); + return -EBUSY; + } + ssc_p->dir_mask |= dir_mask; + spin_unlock_irq(&ssc_p->lock); + + return 0; +} + +/* + * Shutdown. Clear DMA parameters and shutdown the SSC if there + * are no other substreams open. + */ +static void at91_i2s_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; + struct at91_pcm_dma_params *dma_params; + int dir, dir_mask; + + dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + dma_params = ssc_p->dma_params[dir]; + + if (dma_params != NULL) { + at91_ssc_write(dma_params->ssc_base + AT91_SSC_CR, + dma_params->mask->ssc_disable); + DBG("%s disabled SSC_SR=0x%08lx\n", (dir ? "receive" : "transmit"), + at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR)); + + dma_params->ssc_base = NULL; + dma_params->substream = NULL; + ssc_p->dma_params[dir] = NULL; + } + + dir_mask = 1 << dir; + + spin_lock_irq(&ssc_p->lock); + ssc_p->dir_mask &= ~dir_mask; + if (!ssc_p->dir_mask) { + /* Shutdown the SSC clock. */ + DBG("Stopping pid %d clock\n", ssc_p->ssc.pid); + at91_sys_write(AT91_PMC_PCDR, 1<<ssc_p->ssc.pid); + + if (ssc_p->initialized) { + free_irq(ssc_p->ssc.pid, ssc_p); + ssc_p->initialized = 0; + } + + /* Reset the SSC */ + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, AT91_SSC_SWRST); + + /* Clear the SSC dividers */ + ssc_p->cmr_div = ssc_p->tcmr_period = ssc_p->rcmr_period = 0; + } + spin_unlock_irq(&ssc_p->lock); +} + +/* + * Record the SSC system clock rate. + */ +static int at91_i2s_set_dai_sysclk(struct snd_soc_cpu_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + /* + * The only clock supplied to the SSC is the AT91 master clock, + * which is only used if the SSC is generating BCLK and/or + * LRC clocks. + */ + switch (clk_id) { + case AT91_SYSCLK_MCK: + at91_i2s_sysclk = freq; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Record the DAI format for use in hw_params(). + */ +static int at91_i2s_set_dai_fmt(struct snd_soc_cpu_dai *cpu_dai, + unsigned int fmt) +{ + struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; + + if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_I2S) + return -EINVAL; + + ssc_p->daifmt = fmt; + return 0; +} + +/* + * Record SSC clock dividers for use in hw_params(). + */ +static int at91_i2s_set_dai_clkdiv(struct snd_soc_cpu_dai *cpu_dai, + int div_id, int div) +{ + struct at91_ssc_info *ssc_p = &ssc_info[cpu_dai->id]; + + switch (div_id) { + case AT91SSC_CMR_DIV: + /* + * The same master clock divider is used for both + * transmit and receive, so if a value has already + * been set, it must match this value. + */ + if (ssc_p->cmr_div == 0) + ssc_p->cmr_div = div; + else + if (div != ssc_p->cmr_div) + return -EBUSY; + break; + + case AT91SSC_TCMR_PERIOD: + ssc_p->tcmr_period = div; + break; + + case AT91SSC_RCMR_PERIOD: + ssc_p->rcmr_period = div; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* + * Configure the SSC. + */ +static int at91_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + int id = rtd->dai->cpu_dai->id; + struct at91_ssc_info *ssc_p = &ssc_info[id]; + struct at91_pcm_dma_params *dma_params; + int dir, channels, bits; + u32 tfmr, rfmr, tcmr, rcmr; + int start_event; + int ret; + + /* + * Currently, there is only one set of dma params for + * each direction. If more are added, this code will + * have to be changed to select the proper set. + */ + dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + + dma_params = &ssc_dma_params[id][dir]; + dma_params->ssc_base = ssc_p->ssc.base; + dma_params->substream = substream; + + ssc_p->dma_params[dir] = dma_params; + + /* + * The cpu_dai->dma_data field is only used to communicate the + * appropriate DMA parameters to the pcm driver hw_params() + * function. It should not be used for other purposes + * as it is common to all substreams. + */ + rtd->dai->cpu_dai->dma_data = dma_params; + + channels = params_channels(params); + + /* + * The SSC only supports up to 16-bit samples in I2S format, due + * to the size of the Frame Mode Register FSLEN field. Also, I2S + * implies signed data. + */ + bits = 16; + dma_params->pdc_xfer_size = 2; + + /* + * Compute SSC register settings. + */ + switch (ssc_p->daifmt) { + case SND_SOC_DAIFMT_CBS_CFS: + /* + * SSC provides BCLK and LRC clocks. + * + * The SSC transmit and receive clocks are generated from the + * MCK divider, and the BCLK signal is output on the SSC TK line. + */ + rcmr = (( ssc_p->rcmr_period << 24) & AT91_SSC_PERIOD) + | (( 1 << 16) & AT91_SSC_STTDLY) + | (( AT91_SSC_START_FALLING_RF ) & AT91_SSC_START) + | (( AT91_SSC_CK_RISING ) & AT91_SSC_CKI) + | (( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO) + | (( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS); + + rfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE) + | (( AT91_SSC_FSOS_NEGATIVE ) & AT91_SSC_FSOS) + | (((bits - 1) << 16) & AT91_SSC_FSLEN) + | (((channels - 1) << 8) & AT91_SSC_DATNB) + | (( 1 << 7) & AT91_SSC_MSBF) + | (( 0 << 5) & AT91_SSC_LOOP) + | (((bits - 1) << 0) & AT91_SSC_DATALEN); + + tcmr = (( ssc_p->tcmr_period << 24) & AT91_SSC_PERIOD) + | (( 1 << 16) & AT91_SSC_STTDLY) + | (( AT91_SSC_START_FALLING_RF ) & AT91_SSC_START) + | (( AT91_SSC_CKI_FALLING ) & AT91_SSC_CKI) + | (( AT91_SSC_CKO_CONTINUOUS ) & AT91_SSC_CKO) + | (( AT91_SSC_CKS_DIV ) & AT91_SSC_CKS); + + tfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE) + | (( 0 << 23) & AT91_SSC_FSDEN) + | (( AT91_SSC_FSOS_NEGATIVE ) & AT91_SSC_FSOS) + | (((bits - 1) << 16) & AT91_SSC_FSLEN) + | (((channels - 1) << 8) & AT91_SSC_DATNB) + | (( 1 << 7) & AT91_SSC_MSBF) + | (( 0 << 5) & AT91_SSC_DATDEF) + | (((bits - 1) << 0) & AT91_SSC_DATALEN); + break; + + case SND_SOC_DAIFMT_CBM_CFM: + + /* + * CODEC supplies BCLK and LRC clocks. + * + * The SSC transmit clock is obtained from the BCLK signal on + * on the TK line, and the SSC receive clock is generated from the + * transmit clock. + * + * For single channel data, one sample is transferred on the falling + * edge of the LRC clock. For two channel data, one sample is + * transferred on both edges of the LRC clock. + */ + start_event = channels == 1 + ? AT91_SSC_START_FALLING_RF + : AT91_SSC_START_EDGE_RF; + + rcmr = (( 0 << 24) & AT91_SSC_PERIOD) + | (( 1 << 16) & AT91_SSC_STTDLY) + | (( start_event ) & AT91_SSC_START) + | (( AT91_SSC_CK_RISING ) & AT91_SSC_CKI) + | (( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO) + | (( AT91_SSC_CKS_CLOCK ) & AT91_SSC_CKS); + + rfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE) + | (( AT91_SSC_FSOS_NONE ) & AT91_SSC_FSOS) + | (( 0 << 16) & AT91_SSC_FSLEN) + | (( 0 << 8) & AT91_SSC_DATNB) + | (( 1 << 7) & AT91_SSC_MSBF) + | (( 0 << 5) & AT91_SSC_LOOP) + | (((bits - 1) << 0) & AT91_SSC_DATALEN); + + tcmr = (( 0 << 24) & AT91_SSC_PERIOD) + | (( 1 << 16) & AT91_SSC_STTDLY) + | (( start_event ) & AT91_SSC_START) + | (( AT91_SSC_CKI_FALLING ) & AT91_SSC_CKI) + | (( AT91_SSC_CKO_NONE ) & AT91_SSC_CKO) + | (( AT91_SSC_CKS_PIN ) & AT91_SSC_CKS); + + tfmr = (( AT91_SSC_FSEDGE_POSITIVE ) & AT91_SSC_FSEDGE) + | (( 0 << 23) & AT91_SSC_FSDEN) + | (( AT91_SSC_FSOS_NONE ) & AT91_SSC_FSOS) + | (( 0 << 16) & AT91_SSC_FSLEN) + | (( 0 << 8) & AT91_SSC_DATNB) + | (( 1 << 7) & AT91_SSC_MSBF) + | (( 0 << 5) & AT91_SSC_DATDEF) + | (((bits - 1) << 0) & AT91_SSC_DATALEN); + break; + + case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFS: + default: + printk(KERN_WARNING "at91-i2s: unsupported DAI format 0x%x.\n", + ssc_p->daifmt); + return -EINVAL; + break; + } + DBG("RCMR=%08x RFMR=%08x TCMR=%08x TFMR=%08x\n", rcmr, rfmr, tcmr, tfmr); + + if (!ssc_p->initialized) { + + /* Enable PMC peripheral clock for this SSC */ + DBG("Starting pid %d clock\n", ssc_p->ssc.pid); + at91_sys_write(AT91_PMC_PCER, 1<<ssc_p->ssc.pid); + + /* Reset the SSC and its PDC registers */ + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, AT91_SSC_SWRST); + + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_RPR, 0); + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_RCR, 0); + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_RNPR, 0); + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_RNCR, 0); + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_TPR, 0); + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_TCR, 0); + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_TNPR, 0); + at91_ssc_write(ssc_p->ssc.base + AT91_PDC_TNCR, 0); + + if ((ret = request_irq(ssc_p->ssc.pid, at91_i2s_interrupt, + 0, ssc_p->name, ssc_p)) < 0) { + printk(KERN_WARNING "at91-i2s: request_irq failure\n"); + + DBG("Stopping pid %d clock\n", ssc_p->ssc.pid); + at91_sys_write(AT91_PMC_PCER, 1<<ssc_p->ssc.pid); + return ret; + } + + ssc_p->initialized = 1; + } + + /* set SSC clock mode register */ + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CMR, ssc_p->cmr_div); + + /* set receive clock mode and format */ + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RCMR, rcmr); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RFMR, rfmr); + + /* set transmit clock mode and format */ + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TCMR, tcmr); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TFMR, tfmr); + + DBG("hw_params: SSC initialized\n"); + return 0; +} + + +static int at91_i2s_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct at91_ssc_info *ssc_p = &ssc_info[rtd->dai->cpu_dai->id]; + struct at91_pcm_dma_params *dma_params; + int dir; + + dir = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1; + dma_params = ssc_p->dma_params[dir]; + + at91_ssc_write(dma_params->ssc_base + AT91_SSC_CR, + dma_params->mask->ssc_enable); + + DBG("%s enabled SSC_SR=0x%08lx\n", dir ? "receive" : "transmit", + at91_ssc_read(dma_params->ssc_base + AT91_SSC_SR)); + return 0; +} + + +#ifdef CONFIG_PM +static int at91_i2s_suspend(struct platform_device *pdev, + struct snd_soc_cpu_dai *cpu_dai) +{ + struct at91_ssc_info *ssc_p; + + if(!cpu_dai->active) + return 0; + + ssc_p = &ssc_info[cpu_dai->id]; + + /* Save the status register before disabling transmit and receive. */ + ssc_p->ssc_state.ssc_sr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_SR); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, + AT91_SSC_TXDIS | AT91_SSC_RXDIS); + + /* Save the current interrupt mask, then disable unmasked interrupts. */ + ssc_p->ssc_state.ssc_imr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_IMR); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_IDR, ssc_p->ssc_state.ssc_imr); + + ssc_p->ssc_state.ssc_cmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_CMR); + ssc_p->ssc_state.ssc_rcmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_RCMR); + ssc_p->ssc_state.ssc_rfmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_RFMR); + ssc_p->ssc_state.ssc_tcmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_TCMR); + ssc_p->ssc_state.ssc_tfmr = at91_ssc_read(ssc_p->ssc.base + AT91_SSC_TFMR); + + return 0; +} + +static int at91_i2s_resume(struct platform_device *pdev, + struct snd_soc_cpu_dai *cpu_dai) +{ + struct at91_ssc_info *ssc_p; + + if(!cpu_dai->active) + return 0; + + ssc_p = &ssc_info[cpu_dai->id]; + + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TFMR, ssc_p->ssc_state.ssc_tfmr); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_TCMR, ssc_p->ssc_state.ssc_tcmr); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RFMR, ssc_p->ssc_state.ssc_rfmr); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_RCMR, ssc_p->ssc_state.ssc_rcmr); + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CMR, ssc_p->ssc_state.ssc_cmr); + + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_IER, ssc_p->ssc_state.ssc_imr); + + at91_ssc_write(ssc_p->ssc.base + AT91_SSC_CR, + ((ssc_p->ssc_state.ssc_sr & AT91_SSC_RXENA) ? AT91_SSC_RXEN : 0) | + ((ssc_p->ssc_state.ssc_sr & AT91_SSC_TXENA) ? AT91_SSC_TXEN : 0)); + + return 0; +} + +#else +#define at91_i2s_suspend NULL +#define at91_i2s_resume NULL +#endif + +#define AT91_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + +struct snd_soc_cpu_dai at91_i2s_dai[NUM_SSC_DEVICES] = { + { .name = "at91_ssc0/i2s", + .id = 0, + .type = SND_SOC_DAI_I2S, + .suspend = at91_i2s_suspend, + .resume = at91_i2s_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = AT91_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = AT91_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .startup = at91_i2s_startup, + .shutdown = at91_i2s_shutdown, + .prepare = at91_i2s_prepare, + .hw_params = at91_i2s_hw_params,}, + .dai_ops = { + .set_sysclk = at91_i2s_set_dai_sysclk, + .set_fmt = at91_i2s_set_dai_fmt, + .set_clkdiv = at91_i2s_set_dai_clkdiv,}, + .private_data = &ssc_info[0].ssc, + }, +#if NUM_SSC_DEVICES == 3 + { .name = "at91_ssc1/i2s", + .id = 1, + .type = SND_SOC_DAI_I2S, + .suspend = at91_i2s_suspend, + .resume = at91_i2s_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = AT91_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = AT91_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .startup = at91_i2s_startup, + .shutdown = at91_i2s_shutdown, + .prepare = at91_i2s_prepare, + .hw_params = at91_i2s_hw_params,}, + .dai_ops = { + .set_sysclk = at91_i2s_set_dai_sysclk, + .set_fmt = at91_i2s_set_dai_fmt, + .set_clkdiv = at91_i2s_set_dai_clkdiv,}, + .private_data = &ssc_info[1].ssc, + }, + { .name = "at91_ssc2/i2s", + .id = 2, + .type = SND_SOC_DAI_I2S, + .suspend = at91_i2s_suspend, + .resume = at91_i2s_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = AT91_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = AT91_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = { + .startup = at91_i2s_startup, + .shutdown = at91_i2s_shutdown, + .prepare = at91_i2s_prepare, + .hw_params = at91_i2s_hw_params,}, + .dai_ops = { + .set_sysclk = at91_i2s_set_dai_sysclk, + .set_fmt = at91_i2s_set_dai_fmt, + .set_clkdiv = at91_i2s_set_dai_clkdiv,}, + .private_data = &ssc_info[2].ssc, + }, +#endif +}; + +EXPORT_SYMBOL_GPL(at91_i2s_dai); + +/* Module information */ +MODULE_AUTHOR("Frank Mandarino, fmandarino@endrelia.com, www.endrelia.com"); +MODULE_DESCRIPTION("AT91 I2S ASoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/at91/at91-i2s.h b/sound/soc/at91/at91-i2s.h new file mode 100644 index 00000000000..f8a875ba0cc --- /dev/null +++ b/sound/soc/at91/at91-i2s.h @@ -0,0 +1,27 @@ +/* + * at91-i2s.h - ALSA I2S interface for the Atmel AT91 SoC + * + * Author: Frank Mandarino <fmandarino@endrelia.com> + * Endrelia Technologies Inc. + * Created: Jan 9, 2007 + * + * 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. + */ + +#ifndef _AT91_I2S_H +#define _AT91_I2S_H + +/* I2S system clock ids */ +#define AT91_SYSCLK_MCK 0 /* SSC uses AT91 MCK as system clock */ + +/* I2S divider ids */ +#define AT91SSC_CMR_DIV 0 /* MCK divider for BCLK */ +#define AT91SSC_TCMR_PERIOD 1 /* BCLK divider for transmit FS */ +#define AT91SSC_RCMR_PERIOD 2 /* BCLK divider for receive FS */ + +extern struct snd_soc_cpu_dai at91_i2s_dai[]; + +#endif /* _AT91_I2S_H */ + diff --git a/sound/soc/at91/at91-pcm.c b/sound/soc/at91/at91-pcm.c new file mode 100644 index 00000000000..e88b12e7cc4 --- /dev/null +++ b/sound/soc/at91/at91-pcm.c @@ -0,0 +1,432 @@ +/* + * at91-pcm.c -- ALSA PCM interface for the Atmel AT91 SoC + * + * Author: Frank Mandarino <fmandarino@endrelia.com> + * Endrelia Technologies Inc. + * Created: Mar 3, 2006 + * + * Based on pxa2xx-pcm.c by: + * + * Author: Nicolas Pitre + * Created: Nov 30, 2004 + * Copyright: (C) 2004 MontaVista Software, Inc. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <sound/driver.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/arch/hardware.h> +#include <asm/arch/at91_ssc.h> +#include <asm/arch/at91_pdc.h> + +#include "at91-pcm.h" + +#if 0 +#define DBG(x...) printk(KERN_INFO "at91-pcm: " x) +#else +#define DBG(x...) +#endif + +static const struct snd_pcm_hardware at91_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .period_bytes_min = 32, + .period_bytes_max = 8192, + .periods_min = 2, + .periods_max = 1024, + .buffer_bytes_max = 32 * 1024, +}; + +struct at91_runtime_data { + struct at91_pcm_dma_params *params; + dma_addr_t dma_buffer; /* physical address of dma buffer */ + dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ + size_t period_size; + dma_addr_t period_ptr; /* physical address of next period */ + u32 pdc_xpr_save; /* PDC register save */ + u32 pdc_xcr_save; + u32 pdc_xnpr_save; + u32 pdc_xncr_save; +}; + +static void at91_pcm_dma_irq(u32 ssc_sr, + struct snd_pcm_substream *substream) +{ + struct at91_runtime_data *prtd = substream->runtime->private_data; + struct at91_pcm_dma_params *params = prtd->params; + static int count = 0; + + count++; + + if (ssc_sr & params->mask->ssc_endbuf) { + + printk(KERN_WARNING + "at91-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n", + substream->stream == SNDRV_PCM_STREAM_PLAYBACK + ? "underrun" : "overrun", + params->name, ssc_sr, count); + + /* re-start the PDC */ + at91_ssc_write(params->ssc_base + AT91_PDC_PTCR, params->mask->pdc_disable); + + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) { + prtd->period_ptr = prtd->dma_buffer; + } + + at91_ssc_write(params->ssc_base + params->pdc->xpr, prtd->period_ptr); + at91_ssc_write(params->ssc_base + params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + + at91_ssc_write(params->ssc_base + AT91_PDC_PTCR, params->mask->pdc_enable); + } + + if (ssc_sr & params->mask->ssc_endx) { + + /* Load the PDC next pointer and counter registers */ + prtd->period_ptr += prtd->period_size; + if (prtd->period_ptr >= prtd->dma_buffer_end) { + prtd->period_ptr = prtd->dma_buffer; + } + at91_ssc_write(params->ssc_base + params->pdc->xnpr, prtd->period_ptr); + at91_ssc_write(params->ssc_base + params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + } + + snd_pcm_period_elapsed(substream); +} + +static int at91_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct at91_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* this may get called several times by oss emulation + * with different params */ + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + + prtd->params = rtd->dai->cpu_dai->dma_data; + prtd->params->dma_intr_handler = at91_pcm_dma_irq; + + prtd->dma_buffer = runtime->dma_addr; + prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes; + prtd->period_size = params_period_bytes(params); + + DBG("hw_params: DMA for %s initialized (dma_bytes=%d, period_size=%d)\n", + prtd->params->name, runtime->dma_bytes, prtd->period_size); + return 0; +} + +static int at91_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct at91_runtime_data *prtd = substream->runtime->private_data; + struct at91_pcm_dma_params *params = prtd->params; + + if (params != NULL) { + at91_ssc_write(params->ssc_base + AT91_PDC_PTCR, params->mask->pdc_disable); + prtd->params->dma_intr_handler = NULL; + } + + return 0; +} + +static int at91_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct at91_runtime_data *prtd = substream->runtime->private_data; + struct at91_pcm_dma_params *params = prtd->params; + + at91_ssc_write(params->ssc_base + AT91_SSC_IDR, + params->mask->ssc_endx | params->mask->ssc_endbuf); + + at91_ssc_write(params->ssc_base + AT91_PDC_PTCR, params->mask->pdc_disable); + return 0; +} + +static int at91_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct at91_runtime_data *prtd = substream->runtime->private_data; + struct at91_pcm_dma_params *params = prtd->params; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->period_ptr = prtd->dma_buffer; + + at91_ssc_write(params->ssc_base + params->pdc->xpr, prtd->period_ptr); + at91_ssc_write(params->ssc_base + params->pdc->xcr, + prtd->period_size / params->pdc_xfer_size); + + prtd->period_ptr += prtd->period_size; + at91_ssc_write(params->ssc_base + params->pdc->xnpr, prtd->period_ptr); + at91_ssc_write(params->ssc_base + params->pdc->xncr, + prtd->period_size / params->pdc_xfer_size); + + DBG("trigger: period_ptr=%lx, xpr=%lx, xcr=%ld, xnpr=%lx, xncr=%ld\n", + (unsigned long) prtd->period_ptr, + at91_ssc_read(params->ssc_base + params->pdc->xpr), + at91_ssc_read(params->ssc_base + params->pdc->xcr), + at91_ssc_read(params->ssc_base + params->pdc->xnpr), + at91_ssc_read(params->ssc_base + params->pdc->xncr)); + + at91_ssc_write(params->ssc_base + AT91_SSC_IER, + params->mask->ssc_endx | params->mask->ssc_endbuf); + + at91_ssc_write(params->ssc_base + AT91_PDC_PTCR, params->mask->pdc_enable); + + DBG("sr=%lx imr=%lx\n", at91_ssc_read(params->ssc_base + AT91_SSC_SR), + at91_ssc_read(params->ssc_base + AT91_SSC_IER)); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + at91_ssc_write(params->ssc_base + AT91_PDC_PTCR, params->mask->pdc_disable); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + at91_ssc_write(params->ssc_base + AT91_PDC_PTCR, params->mask->pdc_enable); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t at91_pcm_pointer( + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct at91_runtime_data *prtd = runtime->private_data; + struct at91_pcm_dma_params *params = prtd->params; + dma_addr_t ptr; + snd_pcm_uframes_t x; + + ptr = (dma_addr_t) at91_ssc_read(params->ssc_base + params->pdc->xpr); + x = bytes_to_frames(runtime, ptr - prtd->dma_buffer); + + if (x == runtime->buffer_size) + x = 0; + return x; +} + +static int at91_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct at91_runtime_data *prtd; + int ret = 0; + |