diff options
author | Lionel Xu <Lionel.Xu@freescale.com> | 2011-07-25 21:58:43 +0800 |
---|---|---|
committer | Jason Liu <r64343@freescale.com> | 2012-07-20 13:14:56 +0800 |
commit | fa23b2ae91b228a9a11ef3a7b2a4a04315bdead2 (patch) | |
tree | c60fdfeeb5889eea16e1e9ccea25047dee3ae612 /sound/soc/imx/imx-esai.c | |
parent | 3eb1935c944233c52799b3e9047e37fc2870888c (diff) |
ENGR00153651-2 ESAI: Add esai/cs42888 audio codec support on mx6q platform
Add ESAI and related audio codec cs42888 support on mx6q platform.
Signed-off-by: Lionel Xu <R63889@freescale.com>
Diffstat (limited to 'sound/soc/imx/imx-esai.c')
-rw-r--r-- | sound/soc/imx/imx-esai.c | 794 |
1 files changed, 794 insertions, 0 deletions
diff --git a/sound/soc/imx/imx-esai.c b/sound/soc/imx/imx-esai.c new file mode 100644 index 000000000000..03bf0744cbb0 --- /dev/null +++ b/sound/soc/imx/imx-esai.c @@ -0,0 +1,794 @@ +/* + * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + /*! + * @file imx-esai.c + * @brief this file implements the esai interface + * in according to ASoC architeture + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/fsl_devices.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <mach/dma.h> +#include <mach/clock.h> +#include <asm/mach-types.h> +#include <mach/hardware.h> +#include <mach/esai.h> + +#include "imx-esai.h" + +static struct imx_esai *local_esai; + +static int imx_esai_set_dai_sysclk(struct snd_soc_dai *cpu_dai, + int clk_id, unsigned int freq, int dir) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + u32 ecr, tccr, rccr; + + ecr = readl(esai->base + ESAI_ECR); + tccr = readl(esai->base + ESAI_TCCR); + rccr = readl(esai->base + ESAI_RCCR); + + if (dir == SND_SOC_CLOCK_IN) { + tccr &= ~(ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | ESAI_TCCR_TFSD); + rccr &= ~(ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD); + } else { + tccr |= ESAI_TCCR_THCKD | ESAI_TCCR_TCKD | ESAI_TCCR_TFSD; + rccr |= ESAI_RCCR_RHCKD | ESAI_RCCR_RCKD | ESAI_RCCR_RFSD; + + if (clk_id == ESAI_CLK_FSYS) { + ecr &= ~(ESAI_ECR_ETI | ESAI_ECR_ETO); + ecr &= ~(ESAI_ECR_ERI | ESAI_ECR_ERO); + } else if (clk_id == ESAI_CLK_EXTAL) { + ecr |= ESAI_ECR_ETI; + ecr |= ESAI_ECR_ETO; + ecr |= ESAI_ECR_ERI; + ecr |= ESAI_ECR_ERO; + } else if (clk_id == ESAI_CLK_EXTAL_DIV) { + ecr |= ESAI_ECR_ETI; + ecr &= ~ESAI_ECR_ETO; + ecr |= ESAI_ECR_ERI; + ecr |= ESAI_ECR_ERO; + } + } + + writel(ecr, esai->base + ESAI_ECR); + writel(tccr, esai->base + ESAI_TCCR); + writel(rccr, esai->base + ESAI_RCCR); + + ESAI_DUMP(); + return 0; +} + +static int imx_esai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, + int div_id, int div) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tccr, rccr; + + tccr = readl(esai->base + ESAI_TCCR); + rccr = readl(esai->base + ESAI_RCCR); + + switch (div_id) { + case ESAI_TX_DIV_PSR: + tccr &= ESAI_TCCR_TPSR_MASK; + if (div) + tccr |= ESAI_TCCR_TPSR_BYPASS; + else + tccr &= ~ESAI_TCCR_TPSR_DIV8; + break; + case ESAI_TX_DIV_PM: + tccr &= ESAI_TCCR_TPM_MASK; + tccr |= ESAI_TCCR_TPM(div); + break; + case ESAI_TX_DIV_FP: + tccr &= ESAI_TCCR_TFP_MASK; + tccr |= ESAI_TCCR_TFP(div); + break; + case ESAI_RX_DIV_PSR: + rccr &= ESAI_RCCR_RPSR_MASK; + if (div) + rccr |= ESAI_RCCR_RPSR_BYPASS; + else + rccr &= ~ESAI_RCCR_RPSR_DIV8; + break; + case ESAI_RX_DIV_PM: + rccr &= ESAI_RCCR_RPM_MASK; + rccr |= ESAI_RCCR_RPM(div); + break; + case ESAI_RX_DIV_FP: + rccr &= ESAI_RCCR_RFP_MASK; + rccr |= ESAI_RCCR_RFP(div); + break; + return -EINVAL; + } + writel(tccr, esai->base + ESAI_TCCR); + writel(rccr, esai->base + ESAI_RCCR); + return 0; +} + +/* + * ESAI Network Mode or TDM slots configuration. + */ +static int imx_esai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tccr, rccr; + + tccr = readl(esai->base + ESAI_TCCR); + + tccr &= ESAI_TCCR_TDC_MASK; + tccr |= ESAI_TCCR_TDC(slots - 1); + + writel(tccr, esai->base + ESAI_TCCR); + writel((tx_mask & 0xffff), esai->base + ESAI_TSMA); + writel(((tx_mask >> 16) & 0xffff), esai->base + ESAI_TSMB); + + rccr = readl(esai->base + ESAI_RCCR); + + rccr &= ESAI_RCCR_RDC_MASK; + rccr |= ESAI_RCCR_RDC(slots - 1); + + writel(rccr, esai->base + ESAI_RCCR); + writel((rx_mask & 0xffff), esai->base + ESAI_RSMA); + writel(((rx_mask >> 16) & 0xffff), esai->base + ESAI_RSMB); + + ESAI_DUMP(); + return 0; +} + +/* + * ESAI DAI format configuration. + */ +static int imx_esai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tcr, tccr, rcr, rccr, saicr; + + tcr = readl(esai->base + ESAI_TCR); + tccr = readl(esai->base + ESAI_TCCR); + rcr = readl(esai->base + ESAI_RCR); + rccr = readl(esai->base + ESAI_RCCR); + saicr = readl(esai->base + ESAI_SAICR); + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* data on rising edge of bclk, frame low 1clk before data */ + tcr &= ~ESAI_TCR_TFSL; + tcr |= ESAI_TCR_TFSR; + rcr &= ~ESAI_RCR_RFSL; + rcr |= ESAI_RCR_RFSR; + break; + case SND_SOC_DAIFMT_LEFT_J: + /* data on rising edge of bclk, frame high with data */ + tcr &= ~(ESAI_TCR_TFSL | ESAI_TCR_TFSR); + rcr &= ~(ESAI_RCR_RFSL | ESAI_RCR_RFSR); + break; + case SND_SOC_DAIFMT_DSP_B: + /* data on rising edge of bclk, frame high with data */ + tcr |= ESAI_TCR_TFSL; + rcr |= ESAI_RCR_RFSL; + break; + case SND_SOC_DAIFMT_DSP_A: + /* data on rising edge of bclk, frame high 1clk before data */ + tcr |= ESAI_TCR_TFSL; + rcr |= ESAI_RCR_RFSL; + break; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + tccr |= ESAI_TCCR_TFSP; + tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP); + rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP); + rccr |= ESAI_RCCR_RFSP; + break; + case SND_SOC_DAIFMT_IB_NF: + tccr &= ~(ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP); + rccr &= ~(ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP); + break; + case SND_SOC_DAIFMT_NB_IF: + tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP | ESAI_TCCR_TFSP; + rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP | ESAI_RCCR_RFSP; + break; + case SND_SOC_DAIFMT_NB_NF: + tccr &= ~ESAI_TCCR_TFSP; + tccr |= ESAI_TCCR_TCKP | ESAI_TCCR_THCKP; + rccr &= ~ESAI_RCCR_RFSP; + rccr |= ESAI_RCCR_RCKP | ESAI_RCCR_RHCKP; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + tccr &= ~(ESAI_TCCR_TFSD | ESAI_TCCR_TCKD); + rccr &= ~(ESAI_RCCR_RFSD | ESAI_RCCR_RCKD); + break; + case SND_SOC_DAIFMT_CBS_CFM: + tccr &= ~ESAI_TCCR_TFSD; + tccr |= ESAI_TCCR_TCKD; + rccr &= ~ESAI_RCCR_RFSD; + rccr |= ESAI_RCCR_RCKD; + break; + case SND_SOC_DAIFMT_CBM_CFS: + tccr &= ~ESAI_TCCR_TCKD; + tccr |= ESAI_TCCR_TFSD; + rccr &= ~ESAI_RCCR_RCKD; + rccr |= ESAI_RCCR_RFSD; + break; + case SND_SOC_DAIFMT_CBS_CFS: + tccr |= (ESAI_TCCR_TFSD | ESAI_TCCR_TCKD); + rccr |= (ESAI_RCCR_RFSD | ESAI_RCCR_RCKD); + } + + /* sync */ + if (esai->flags == IMX_ESAI_SYN) + saicr |= ESAI_SAICR_SYNC; + else + saicr &= ~ESAI_SAICR_SYNC; + + tcr &= ESAI_TCR_TMOD_MASK; + rcr &= ESAI_RCR_RMOD_MASK; + if (esai->flags == IMX_ESAI_NET) { + tcr |= ESAI_TCR_TMOD_NETWORK; + rcr |= ESAI_RCR_RMOD_NETWORK; + } else { + tcr |= ESAI_TCR_TMOD_NORMAL; + rcr |= ESAI_RCR_RMOD_NORMAL; + } + + writel(tcr, esai->base + ESAI_TCR); + writel(tccr, esai->base + ESAI_TCCR); + writel(rcr, esai->base + ESAI_RCR); + writel(rccr, esai->base + ESAI_RCCR); + + writel(saicr, esai->base + ESAI_SAICR); + + ESAI_DUMP(); + return 0; +} + +static int imx_esai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + + writel(ESAI_ECR_ERST, esai->base + ESAI_ECR); + writel(ESAI_ECR_ESAIEN, esai->base + ESAI_ECR); + + writel(ESAI_GPIO_ESAI, esai->base + ESAI_PRRC); + writel(ESAI_GPIO_ESAI, esai->base + ESAI_PCRC); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + writel(ESAI_TCR_TPR, esai->base + ESAI_TCR); + else + writel(ESAI_RCR_RPR, esai->base + ESAI_RCR); + + ESAI_DUMP(); + return 0; +} + +/* + * This function is called to initialize the TX port before enable + * the tx port. + */ +static int imx_esai_hw_tx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + u32 tcr, tfcr; + unsigned int channels; + struct imx_pcm_dma_params *dma_data; + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_data = &esai->dma_params_tx; + else + dma_data = &esai->dma_params_rx; + + snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data); + + tcr = readl(esai->base + ESAI_TCR); + tfcr = readl(esai->base + ESAI_TFCR); + + tfcr |= ESAI_TFCR_TFR; + writel(tfcr, esai->base + ESAI_TFCR); + tfcr &= ~ESAI_TFCR_TFR; + /* DAI data (word) size */ + tfcr &= ESAI_TFCR_TWA_MASK; + tcr &= ESAI_TCR_TSWS_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + tfcr |= ESAI_WORD_LEN_16; + tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + tfcr |= ESAI_WORD_LEN_20; + tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + tfcr |= ESAI_WORD_LEN_24; + tcr |= ESAI_TCR_TSHFD_MSB | ESAI_TCR_TSWS_STL32_WDL24; + break; + } + + channels = params_channels(params); + tfcr &= ESAI_TFCR_TE_MASK; + tfcr |= ESAI_TFCR_TE(channels); + + tfcr |= ESAI_TFCR_TFWM(64); + + /* Left aligned, Zero padding */ + tcr |= ESAI_TCR_PADC; + /* TDR initialized from the FIFO */ + tfcr |= ESAI_TFCR_TIEN; + + writel(tcr, esai->base + ESAI_TCR); + writel(tfcr, esai->base + ESAI_TFCR); + + ESAI_DUMP(); + return 0; +} + +/* + * This function is called to initialize the RX port before enable + * the rx port. + */ +static int imx_esai_hw_rx_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + u32 rcr, rfcr; + unsigned int channels; + + rcr = readl(esai->base + ESAI_RCR); + rfcr = readl(esai->base + ESAI_RFCR); + + rfcr |= ESAI_RFCR_RFR; + writel(rfcr, esai->base + ESAI_RFCR); + rfcr &= ~ESAI_RFCR_RFR; + + rfcr &= ESAI_RFCR_RWA_MASK; + rcr &= ESAI_RCR_RSWS_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + rfcr |= ESAI_WORD_LEN_16; + rcr |= ESAI_RCR_RSHFD_MSB | ESAI_RCR_RSWS_STL32_WDL16; + break; + } + + channels = params_channels(params); + rfcr &= ESAI_RFCR_RE_MASK; + rfcr |= ESAI_RFCR_RE(channels); + + rfcr |= ESAI_RFCR_RFWM(64); + + writel(rcr, esai->base + ESAI_RCR); + writel(rfcr, esai->base + ESAI_RFCR); + + ESAI_DUMP(); + return 0; +} + +/* + * This function is called to initialize the TX or RX port, + */ +static int imx_esai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + /* Tx/Rx config */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (readl(esai->base + ESAI_TCR) & ESAI_TCR_TE0) + return 0; + return imx_esai_hw_tx_params(substream, params, cpu_dai); + } else { + if (readl(esai->base + ESAI_RCR) & ESAI_RCR_RE1) + return 0; + return imx_esai_hw_rx_params(substream, params, cpu_dai); + } +} + +static int imx_esai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *cpu_dai) +{ + struct imx_esai *esai = snd_soc_dai_get_drvdata(cpu_dai); + u32 reg, tfcr = 0, rfcr = 0; + u32 temp; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tfcr = readl(esai->base + ESAI_TFCR); + reg = readl(esai->base + ESAI_TCR); + } else { + rfcr = readl(esai->base + ESAI_RFCR); + reg = readl(esai->base + ESAI_RCR); + } + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + tfcr |= ESAI_TFCR_TFEN; + writel(tfcr, esai->base + ESAI_TFCR); + reg &= ~ESAI_TCR_TPR; + reg |= ESAI_TCR_TE(substream->runtime->channels); + writel(reg, esai->base + ESAI_TCR); + } else { + temp = readl(esai->base + ESAI_TCR); + temp &= ~ESAI_TCR_TPR; + writel(temp, esai->base + ESAI_TCR); + rfcr |= ESAI_RFCR_RFEN; + writel(rfcr, esai->base + ESAI_RFCR); + reg &= ~ESAI_RCR_RPR; + reg |= ESAI_RCR_RE(substream->runtime->channels); + writel(reg, esai->base + ESAI_RCR); + } + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg &= ~ESAI_TCR_TE(substream->runtime->channels); + writel(reg, esai->base + ESAI_TCR); + reg |= ESAI_TCR_TPR; + writel(reg, esai->base + ESAI_TCR); + tfcr |= ESAI_TFCR_TFR; + tfcr &= ~ESAI_TFCR_TFEN; + writel(tfcr, esai->base + ESAI_TFCR); + tfcr &= ~ESAI_TFCR_TFR; + writel(tfcr, esai->base + ESAI_TFCR); + } else { + reg &= ~ESAI_RCR_RE(substream->runtime->channels); + writel(reg, esai->base + ESAI_RCR); + reg |= ESAI_RCR_RPR; + writel(reg, esai->base + ESAI_RCR); + rfcr |= ESAI_RFCR_RFR; + rfcr &= ~ESAI_RFCR_RFEN; + writel(rfcr, esai->base + ESAI_RFCR); + rfcr &= ~ESAI_RFCR_RFR; + writel(rfcr, esai->base + ESAI_RFCR); + } + break; + default: + return -EINVAL; + } + + ESAI_DUMP(); + return 0; +} + +#ifdef CONFIG_PM +static int imx_esai_suspend(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int imx_esai_resume(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +#else +#define imx_esai_suspend NULL +#define imx_esai_resume NULL +#endif + + +#define IMX_ESAI_RATES SNDRV_PCM_RATE_8000_192000 + +#define IMX_ESAI_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops imx_esai_dai_ops = { + .startup = imx_esai_startup, + .trigger = imx_esai_trigger, + .hw_params = imx_esai_hw_params, + .set_sysclk = imx_esai_set_dai_sysclk, + .set_clkdiv = imx_esai_set_dai_clkdiv, + .set_fmt = imx_esai_set_dai_fmt, + .set_tdm_slot = imx_esai_set_dai_tdm_slot, +}; + +static int imx_esai_dai_probe(struct snd_soc_dai *dai) +{ + struct imx_esai *esai = dev_get_drvdata(dai->dev); + snd_soc_dai_set_drvdata(dai, esai); + return 0; +} + +static struct snd_soc_dai_driver imx_esai_dai = { + .name = "imx-esai.0", + .probe = imx_esai_dai_probe, + .suspend = imx_esai_suspend, + .resume = imx_esai_resume, + .playback = { + .channels_min = 1, + .channels_max = 6, + .rates = IMX_ESAI_RATES, + .formats = IMX_ESAI_FORMATS, + }, + .capture = { + .channels_min = 1, + .channels_max = 4, + .rates = IMX_ESAI_RATES, + .formats = IMX_ESAI_FORMATS, + }, + .ops = &imx_esai_dai_ops, +}; + +int snd_imx_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = dma_mmap_coherent(NULL, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); + + pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); + return ret; +} +EXPORT_SYMBOL_GPL(snd_imx_pcm_mmap); + +static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = IMX_ESAI_DMABUF_SIZE; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + buf->bytes = size; + + return 0; +} + +static u64 imx_pcm_dmamask = DMA_BIT_MASK(32); + +int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &imx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + if (dai->driver->playback.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->driver->capture.channels_min) { + ret = imx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + return ret; +} +EXPORT_SYMBOL_GPL(imx_pcm_new); + +void imx_pcm_free(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; + } +} +EXPORT_SYMBOL_GPL(imx_pcm_free); + +static int imx_esai_probe(struct platform_device *pdev) +{ + struct resource *res; + struct imx_esai *esai; + struct imx_esai_platform_data *pdata = pdev->dev.platform_data; + int ret = 0; + + esai = kzalloc(sizeof(*esai), GFP_KERNEL); + if (!esai) + return -ENOMEM; + local_esai = esai; + dev_set_drvdata(&pdev->dev, esai); + + if (pdata) + esai->flags = pdata->flags; + + esai->irq = platform_get_irq(pdev, 0); + + esai->clk = clk_get(&pdev->dev, "esai_clk"); + if (IS_ERR(esai->clk)) { + ret = PTR_ERR(esai->clk); + dev_err(&pdev->dev, "Cannot get the clock: %d\n", + ret); + goto failed_clk; + } + clk_enable(esai->clk); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + ret = -ENODEV; + goto failed_get_resource; + } + + if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) { + dev_err(&pdev->dev, "request_mem_region failed\n"); + ret = -EBUSY; + goto failed_get_resource; + } + + esai->base = ioremap(res->start, resource_size(res)); + if (!esai->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENODEV; + goto failed_ioremap; + } + + esai->dma_params_rx.dma_addr = res->start + ESAI_ERDR; + esai->dma_params_tx.dma_addr = res->start + ESAI_ETDR; + + esai->dma_params_tx.peripheral_type = IMX_DMATYPE_ESAI; + esai->dma_params_rx.peripheral_type = IMX_DMATYPE_ESAI; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx"); + if (res) + esai->dma_params_tx.dma = res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx"); + if (res) + esai->dma_params_rx.dma = res->start; + + platform_set_drvdata(pdev, esai); + ret = snd_soc_register_dai(&pdev->dev, &imx_esai_dai); + if (ret) { + dev_err(&pdev->dev, "register DAI failed\n"); + goto failed_register; + } + + esai->soc_platform_pdev_fiq = \ + platform_device_alloc("imx-fiq-pcm-audio", pdev->id); + if (!esai->soc_platform_pdev_fiq) { + ret = -ENOMEM; + goto failed_pdev_fiq_alloc; + } + + platform_set_drvdata(esai->soc_platform_pdev_fiq, esai); + ret = platform_device_add(esai->soc_platform_pdev_fiq); + if (ret) { + dev_err(&pdev->dev, "failed to add platform device\n"); + goto failed_pdev_fiq_add; + } + + esai->soc_platform_pdev = \ + platform_device_alloc("imx-pcm-audio", pdev->id); + if (!esai->soc_platform_pdev) { + ret = -ENOMEM; + goto failed_pdev_alloc; + } + + platform_set_drvdata(esai->soc_platform_pdev, esai); + ret = platform_device_add(esai->soc_platform_pdev); + if (ret) { + dev_err(&pdev->dev, "failed to add platform device\n"); + goto failed_pdev_add; + } + + return 0; + +failed_pdev_add: + platform_device_put(esai->soc_platform_pdev); +failed_pdev_alloc: + platform_device_del(esai->soc_platform_pdev_fiq); +failed_pdev_fiq_add: + platform_device_put(esai->soc_platform_pdev_fiq); +failed_pdev_fiq_alloc: + snd_soc_unregister_dai(&pdev->dev); +failed_register: + iounmap(esai->base); +failed_ioremap: + release_mem_region(res->start, resource_size(res)); +failed_get_resource: + clk_disable(esai->clk); + clk_put(esai->clk); +failed_clk: + kfree(esai); + + return ret; +} +static int __devexit imx_esai_remove(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct imx_esai *esai = platform_get_drvdata(pdev); + + platform_device_unregister(esai->soc_platform_pdev); + platform_device_unregister(esai->soc_platform_pdev_fiq); + + snd_soc_unregister_dai(&pdev->dev); + + iounmap(esai->base); + release_mem_region(res->start, resource_size(res)); + clk_disable(esai->clk); + clk_put(esai->clk); + kfree(esai); + + return 0; +} + + +static struct platform_driver imx_esai_driver = { + .probe = imx_esai_probe, + .remove = __devexit_p(imx_esai_remove), + .driver = { + .name = "imx-esai", + }, +}; + +static int __init imx_esai_init(void) +{ + return platform_driver_register(&imx_esai_driver); +} + +static void __exit imx_esai_exit(void) +{ + platform_driver_unregister(&imx_esai_driver); +} + +module_init(imx_esai_init); +module_exit(imx_esai_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX ASoC ESAI driver"); +MODULE_LICENSE("GPL"); |