ENGR00212251-2: sai: add SAI driver support for Faraday
authorAlison Wang <b18965@freescale.com>
Fri, 27 Jul 2012 03:10:54 +0000 (11:10 +0800)
committerJason Jin <Jason.jin@freescale.com>
Fri, 10 Aug 2012 10:19:11 +0000 (18:19 +0800)
Add SAI driver support for Faraday.

Signed-off-by: Alison Wang <b18965@freescale.com>
Signed-off-by: Xiaochun Li <b41219@freescale.com>
sound/soc/Kconfig
sound/soc/Makefile
sound/soc/codecs/sgtl5000.c
sound/soc/mvf/Kconfig [new file with mode: 0644]
sound/soc/mvf/Makefile [new file with mode: 0644]
sound/soc/mvf/mvf-pcm-dma-twr.c [new file with mode: 0644]
sound/soc/mvf/mvf-sai.c [new file with mode: 0644]
sound/soc/mvf/mvf-sai.h [new file with mode: 0644]
sound/soc/mvf/mvf-sgtl5000.c [new file with mode: 0644]

index 8224db5f0434ffa7c16b93899f06401ae7c9ed3b..1dacae842f74b010cb9dc9ab209db6c22a589fda 100644 (file)
@@ -46,6 +46,7 @@ source "sound/soc/davinci/Kconfig"
 source "sound/soc/ep93xx/Kconfig"
 source "sound/soc/fsl/Kconfig"
 source "sound/soc/imx/Kconfig"
+source "sound/soc/mvf/Kconfig"
 source "sound/soc/jz4740/Kconfig"
 source "sound/soc/nuc900/Kconfig"
 source "sound/soc/omap/Kconfig"
index 1ed61c5df2c583f655d5dedb1c4041f840c8cae1..96471b431b3be162e77798abb4c0e86f5e6f13b3 100644 (file)
@@ -9,6 +9,7 @@ obj-$(CONFIG_SND_SOC)   += davinci/
 obj-$(CONFIG_SND_SOC)  += ep93xx/
 obj-$(CONFIG_SND_SOC)  += fsl/
 obj-$(CONFIG_SND_SOC)   += imx/
+obj-$(CONFIG_SND_SOC)   += mvf/
 obj-$(CONFIG_SND_SOC)  += jz4740/
 obj-$(CONFIG_SND_SOC)  += mid-x86/
 obj-$(CONFIG_SND_SOC)  += nuc900/
index fd055146192d1bc1990b8a02eb1e852a3b0315c0..687b62895bb282fc35b12c24f4ac376d2e71c001 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * sgtl5000.c  --  SGTL5000 ALSA SoC Audio driver
  *
- * Copyright 2010-2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2010-2012 Freescale Semiconductor, 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
@@ -811,6 +811,7 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate)
         * factor of freq =96k can only be 256, since mclk in range (12m,27m)
         */
        switch (sgtl5000->sysclk / sys_fs) {
+#ifndef CONFIG_IMX_HAVE_PLATFORM_MVF_SAI
        case 256:
                clk_ctl |= SGTL5000_MCLK_FREQ_256FS <<
                        SGTL5000_MCLK_FREQ_SHIFT;
@@ -823,6 +824,7 @@ static int sgtl5000_set_clock(struct snd_soc_codec *codec, int frame_rate)
                clk_ctl |= SGTL5000_MCLK_FREQ_512FS <<
                        SGTL5000_MCLK_FREQ_SHIFT;
                break;
+#endif
        default:
                /* if mclk not satisify the divider, use pll */
                if (sgtl5000->master) {
@@ -1103,7 +1105,11 @@ static int ldo_regulator_register(struct snd_soc_codec *codec,
                                struct regulator_init_data *init_data,
                                int voltage)
 {
+#ifdef CONFIG_IMX_HAVE_PLATFORM_MVF_SAI
+       return 0;
+#else
        return -EINVAL;
+#endif
 }
 
 static int ldo_regulator_remove(struct snd_soc_codec *codec)
@@ -1193,7 +1199,7 @@ static struct snd_soc_dai_driver sgtl5000_dai = {
        .name = "sgtl5000",
        .playback = {
                .stream_name = "Playback",
-               .channels_min = 2,
+               .channels_min = 1,
                .channels_max = 2,
                /*
                 * only support 8~48K + 96K,
@@ -1204,7 +1210,7 @@ static struct snd_soc_dai_driver sgtl5000_dai = {
        },
        .capture = {
                .stream_name = "Capture",
-               .channels_min = 2,
+               .channels_min = 1,
                .channels_max = 2,
                .rates = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_96000,
                .formats = SGTL5000_FORMATS,
@@ -1505,7 +1511,6 @@ static int sgtl5000_enable_regulators(struct snd_soc_codec *codec)
                /* free VDDD regulator */
                regulator_bulk_free(ARRAY_SIZE(sgtl5000->supplies),
                                        sgtl5000->supplies);
-
                ret = ldo_regulator_register(codec, &ldo_init_data, voltage);
                if (ret)
                        return ret;
@@ -1580,11 +1585,12 @@ static int sgtl5000_probe(struct snd_soc_codec *codec)
 
        sgtl5000_fill_reg_cache(codec);
 
+#ifndef CONFIG_IMX_HAVE_PLATFORM_MVF_SAI
        /* power up sgtl5000 */
        ret = sgtl5000_set_power_regs(codec);
        if (ret)
                goto err;
-
+#endif
        /* enable small pop, introduce 400ms delay in turning off */
        snd_soc_update_bits(codec, SGTL5000_CHIP_REF_CTRL,
                                SGTL5000_SMALL_POP,
diff --git a/sound/soc/mvf/Kconfig b/sound/soc/mvf/Kconfig
new file mode 100644 (file)
index 0000000..6577df8
--- /dev/null
@@ -0,0 +1,26 @@
+menuconfig SND_MVF_SOC
+       tristate "SoC Audio for Freescale Faraday CPUs"
+       depends on ARCH_MVF && IMX_HAVE_PLATFORM_MVF_SAI
+       select SND_PCM
+       default y
+       help
+         Say Y or M if you want to add support for codecs attached to
+         the Faraday SAI interface.
+
+
+if SND_MVF_SOC
+
+config SND_MVF_SOC_TWR
+       tristate
+
+config SND_SOC_MVF_SGTL5000
+       tristate "SoC Audio support for Faraday boards with sgtl5000"
+       depends on I2C
+       select SND_SOC_SGTL5000
+       select SND_MVF_SOC_TWR
+       default y
+       help
+         Say Y if you want to add support for SoC audio on an Farday board with
+         a sgtl5000 codec.
+
+endif
diff --git a/sound/soc/mvf/Makefile b/sound/soc/mvf/Makefile
new file mode 100644 (file)
index 0000000..c9e179a
--- /dev/null
@@ -0,0 +1,11 @@
+# Faraday Platform Support
+snd-soc-mvf-objs := mvf-sai.o
+snd-soc-mvf-twr-objs := mvf-pcm-dma-twr.o
+
+obj-$(CONFIG_SND_MVF_SOC) += snd-soc-mvf.o
+obj-$(CONFIG_SND_MVF_SOC_TWR) += snd-soc-mvf-twr.o
+
+# Faraday Machine Support
+snd-soc-mvf-sgtl5000-objs := mvf-sgtl5000.o
+
+obj-$(CONFIG_SND_SOC_MVF_SGTL5000) += snd-soc-mvf-sgtl5000.o
diff --git a/sound/soc/mvf/mvf-pcm-dma-twr.c b/sound/soc/mvf/mvf-pcm-dma-twr.c
new file mode 100644 (file)
index 0000000..8383273
--- /dev/null
@@ -0,0 +1,403 @@
+/*
+ * mvf-pcm-dma-twr.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/dmaengine.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/dma.h>
+#include <mach/mvf_edma.h>
+#include <mach/mcf_edma.h>
+
+#include "mvf-sai.h"
+
+
+#define DRIVER_NAME            "mvf-pcm-audio"
+#define TCD_NUMBER             4
+#define EDMA_PRIO_HIGH         6
+
+struct edma_tcd {
+       __le32 saddr;           /* source address */
+       __le16 soffset;         /* source offset */
+       __le16 attr;            /* transfer attribute */
+       __le32 nbytes;          /* minor byte count */
+       __le32 slast;           /* last source address adjust */
+       __le32 daddr;           /* dest address */
+       __le16 doffset;         /* dest offset */
+       __le16 citer;           /* current minor looplink, major count */
+       __le32 dlast_sga;       /* last dest addr adjust, scatter/gather addr*/
+       __le16 csr;             /* control and status */
+       __le16 biter;           /* begging minor looklink, major count */
+};
+
+struct mvf_pcm_runtime_data {
+       struct edma_tcd tcd[TCD_NUMBER];
+       /* physical address of mvf_pcm_runtime_data */
+       dma_addr_t tcd_buf_phys;
+       dma_addr_t dma_buf_phys;
+       dma_addr_t dma_buf_next;
+       dma_addr_t dma_buf_end;
+
+       int dma_chan;   /* channel number */
+       int tcd_chan;
+       dma_addr_t src_addr;
+       dma_addr_t dst_addr;
+       __le16 soffset;
+       __le16 doffset;
+       struct imx_dma_data dma_data;
+
+       int period_bytes;
+       int periods;
+       int dma;
+       unsigned long offset;
+       unsigned long size;
+       int period_time;
+};
+
+static irqreturn_t audio_dma_irq(int channel, void *data)
+{
+       struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+
+       iprtd->offset += iprtd->period_bytes;
+       iprtd->offset %= iprtd->period_bytes * iprtd->periods;
+
+       snd_pcm_period_elapsed(substream);
+
+       mcf_edma_confirm_interrupt_handled(iprtd->dma_chan);
+
+       return IRQ_HANDLED;
+}
+
+static int edma_request_channel(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+       int err;
+
+       err = mcf_edma_request_channel(iprtd->dma_chan, audio_dma_irq, NULL,
+               iprtd->dma_data.priority, substream, NULL, DRIVER_NAME);
+
+       return err;
+}
+
+static int mvf_sai_dma_alloc(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct imx_pcm_dma_params *dma_params;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+
+       dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+       iprtd->dma_data.priority = EDMA_PRIO_HIGH;
+       iprtd->dma_data.dma_request = dma_params->dma;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               iprtd->dma_chan = DMA_MUX_SAI2_TX;
+       else
+               iprtd->dma_chan = DMA_MUX_SAI2_RX;
+
+       iprtd->tcd_chan = edma_request_channel(substream);
+       if (iprtd->tcd_chan < 0)
+               return -EINVAL;
+
+       return 0;
+}
+
+void fill_tcd_params(void *base, u32 source, u32 dest, u32 attr, u32 soff,
+       u32 nbytes, u32 slast, u32 citer, u32 biter, u32 doff, u32 dlast_sga,
+       int major_int, int disable_req, int enable_sg)
+{
+       struct edma_tcd *tcd = (struct edma_tcd *)base;
+
+       tcd->saddr = source;
+       tcd->attr = attr;
+       tcd->soffset = soff;
+       tcd->nbytes = nbytes;
+       tcd->slast = slast;
+       tcd->daddr = dest;
+       tcd->citer = citer & 0x7fff;
+       tcd->doffset = doff;
+       tcd->dlast_sga = dlast_sga;
+       tcd->biter = biter & 0x7fff;
+       tcd->csr = ((major_int) ? 0x2 : 0) | ((disable_req) ? 0x8 : 0) |
+               ((enable_sg) ? 0x10 : 0);
+}
+
+static int edma_engine_config(struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+       u32 size = frames_to_bytes(runtime, runtime->period_size);
+       struct imx_pcm_dma_params *dma_params;
+       u32 sg_addr;
+       int i;
+
+       dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+       iprtd->dma_buf_phys = runtime->dma_addr;
+       iprtd->dma_buf_next = iprtd->dma_buf_phys;
+       iprtd->dma_buf_end = iprtd->dma_buf_phys + runtime->periods * size;
+
+       sg_addr = iprtd->tcd_buf_phys;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               iprtd->src_addr = iprtd->dma_buf_next;
+               iprtd->dst_addr = dma_params->dma_addr;
+               iprtd->soffset = 2;
+               iprtd->doffset = 0;
+       } else {
+               iprtd->src_addr = dma_params->dma_addr;
+               iprtd->dst_addr = iprtd->dma_buf_next;
+               iprtd->soffset = 0;
+               iprtd->doffset = 2;
+       }
+
+       mcf_edma_set_tcd_params(iprtd->tcd_chan,
+               iprtd->src_addr, iprtd->dst_addr,
+               MCF_EDMA_TCD_ATTR_SSIZE_16BIT | MCF_EDMA_TCD_ATTR_DSIZE_16BIT,
+               iprtd->soffset, 4, 0, size / 4, size / 4, iprtd->doffset,
+               sg_addr, 1, 0, 1);
+
+       for (i = 0; i < TCD_NUMBER; i++) {
+               iprtd->dma_buf_next += size;
+               if (iprtd->dma_buf_next >= iprtd->dma_buf_end)
+                       iprtd->dma_buf_next = iprtd->dma_buf_phys;
+
+               sg_addr = iprtd->tcd_buf_phys +
+                       ((i + 1) % TCD_NUMBER) * sizeof(struct edma_tcd);
+
+               if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+                       iprtd->src_addr = iprtd->dma_buf_next;
+               else
+                       iprtd->dst_addr = iprtd->dma_buf_next;
+
+               fill_tcd_params(&iprtd->tcd[i],
+                       iprtd->src_addr, iprtd->dst_addr,
+                       MCF_EDMA_TCD_ATTR_SSIZE_16BIT |
+                               MCF_EDMA_TCD_ATTR_DSIZE_16BIT,
+                       iprtd->soffset, 4, 0, size / 4, size / 4,
+                       iprtd->doffset, sg_addr, 1, 0, 1);
+       }
+
+       return 0;
+}
+
+static int snd_mvf_pcm_hw_params(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+       struct imx_pcm_dma_params *dma_params;
+       int ret;
+
+       dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+       ret = mvf_sai_dma_alloc(substream, params);
+       if (ret)
+               return ret;
+
+       iprtd->size = params_buffer_bytes(params);
+       iprtd->periods = params_periods(params);
+       iprtd->period_bytes = params_period_bytes(params);
+       iprtd->offset = 0;
+       iprtd->period_time = HZ / (params_rate(params) /
+                       params_period_size(params));
+
+       snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+       return 0;
+}
+
+static int snd_mvf_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+
+       if (iprtd->dma_chan)
+               mcf_edma_free_channel(iprtd->tcd_chan, substream);
+
+       return 0;
+}
+
+static int snd_mvf_pcm_prepare(struct snd_pcm_substream *substream)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct mvf_pcm_dma_params *dma_params;
+       int ret;
+
+       dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+       ret = edma_engine_config(substream);
+       if (ret)
+               return ret;
+
+       return 0;
+}
+
+static int snd_mvf_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+               mcf_edma_start_transfer(iprtd->tcd_chan);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+               mcf_edma_stop_transfer(iprtd->tcd_chan);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static
+snd_pcm_uframes_t snd_mvf_pcm_pointer(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+
+       return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static struct snd_pcm_hardware snd_mvf_hardware = {
+       .info = SNDRV_PCM_INFO_INTERLEAVED |
+               SNDRV_PCM_INFO_BLOCK_TRANSFER |
+               SNDRV_PCM_INFO_MMAP |
+               SNDRV_PCM_INFO_MMAP_VALID |
+               SNDRV_PCM_INFO_PAUSE |
+               SNDRV_PCM_INFO_RESUME,
+       .formats = SNDRV_PCM_FMTBIT_S16_LE,
+       .rate_min = 8000,
+       .channels_min = 2,
+       .channels_max = 2,
+       .buffer_bytes_max = MVF_SAI_DMABUF_SIZE,
+       .period_bytes_min = 4096,
+       .period_bytes_max = MVF_SAI_DMABUF_SIZE / TCD_NUMBER,
+       .periods_min = TCD_NUMBER,
+       .periods_max = TCD_NUMBER,
+       .fifo_size = 0,
+};
+
+static int snd_mvf_open(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd;
+       dma_addr_t tcd_buf_phys;
+       int ret;
+
+       iprtd = dma_alloc_coherent(substream->pcm->dev, sizeof(*iprtd),
+                       &tcd_buf_phys, GFP_KERNEL);
+       if (iprtd == NULL)
+               return -ENOMEM;
+
+       iprtd->tcd_buf_phys = tcd_buf_phys;
+       runtime->private_data = iprtd;
+
+       ret = snd_pcm_hw_constraint_integer(substream->runtime,
+                       SNDRV_PCM_HW_PARAM_PERIODS);
+       if (ret < 0) {
+               kfree(iprtd);
+               return ret;
+       }
+
+       snd_soc_set_runtime_hwparams(substream, &snd_mvf_hardware);
+
+       return 0;
+}
+
+static int snd_mvf_close(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct mvf_pcm_runtime_data *iprtd = runtime->private_data;
+
+       dma_free_coherent(substream->pcm->dev, sizeof(*iprtd), iprtd,
+               iprtd->tcd_buf_phys);
+
+       return 0;
+}
+
+static struct snd_pcm_ops mvf_pcm_ops = {
+       .open           = snd_mvf_open,
+       .close          = snd_mvf_close,
+       .ioctl          = snd_pcm_lib_ioctl,
+       .hw_params      = snd_mvf_pcm_hw_params,
+       .hw_free        = snd_mvf_pcm_hw_free,
+       .prepare        = snd_mvf_pcm_prepare,
+       .trigger        = snd_mvf_pcm_trigger,
+       .pointer        = snd_mvf_pcm_pointer,
+       .mmap           = snd_mvf_pcm_mmap,
+};
+
+static struct snd_soc_platform_driver mvf_soc_platform = {
+       .ops            = &mvf_pcm_ops,
+       .pcm_new        = mvf_pcm_new,
+       .pcm_free       = mvf_pcm_free,
+};
+
+static int __devinit mvf_soc_platform_probe(struct platform_device *pdev)
+{
+       return snd_soc_register_platform(&pdev->dev, &mvf_soc_platform);
+}
+
+static int __devexit mvf_soc_platform_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_platform(&pdev->dev);
+       return 0;
+}
+
+static struct platform_driver mvf_pcm_driver = {
+       .driver = {
+                       .name = DRIVER_NAME,
+                       .owner = THIS_MODULE,
+       },
+       .probe = mvf_soc_platform_probe,
+       .remove = __devexit_p(mvf_soc_platform_remove),
+};
+
+static int __init snd_mvf_pcm_init(void)
+{
+       return platform_driver_register(&mvf_pcm_driver);
+}
+module_init(snd_mvf_pcm_init);
+
+static void __exit snd_mvf_pcm_exit(void)
+{
+       platform_driver_unregister(&mvf_pcm_driver);
+}
+module_exit(snd_mvf_pcm_exit);
+
+MODULE_AUTHOR("Alison Wang, <b18965@freescale.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mvf-pcm-audio");
diff --git a/sound/soc/mvf/mvf-sai.c b/sound/soc/mvf/mvf-sai.c
new file mode 100644 (file)
index 0000000..68b819d
--- /dev/null
@@ -0,0 +1,642 @@
+/*
+ * mvf-sai.c  --  ALSA Soc Audio Layer
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * 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/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/sai.h>
+#include <mach/hardware.h>
+
+#include "mvf-sai.h"
+
+#define MVF_SAI_FORMATS \
+       (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+       SNDRV_PCM_FMTBIT_S24_LE)
+
+/* SAI Network Mode or TDM slots configuration */
+static int mvf_sai_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
+       unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       u32 tcr4, rcr4;
+
+       if (sai->play_enabled == 0) {
+               tcr4 = readl(sai->base + SAI_TCR4);
+               tcr4 &= ~SAI_TCR4_FRSZ_MASK;
+               tcr4 |= SAI_TCR4_FRSZ(1);
+               writel(tcr4, sai->base + SAI_TCR4);
+               writel(tx_mask, sai->base + SAI_TMR);
+       }
+
+       if (sai->cap_enabled == 0) {
+               rcr4 = readl(sai->base + SAI_RCR4);
+               rcr4 &= ~SAI_RCR4_FRSZ_MASK;
+               rcr4 |= SAI_RCR4_FRSZ(1);
+               writel(rcr4, sai->base + SAI_RCR4);
+               writel(rx_mask, sai->base + SAI_RMR);
+       }
+       return 0;
+}
+
+/* SAI DAI format configuration */
+static int mvf_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       u32 tcr2, tcr3, tcr4;
+       u32 rcr2 = 0;
+
+       tcr2 = readl(sai->base + SAI_TCR2);
+       tcr3 = readl(sai->base + SAI_TCR3);
+       tcr4 = readl(sai->base + SAI_TCR4);
+
+       tcr4 |= SAI_TCR4_MF;
+
+       /* 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 */
+               tcr4 |= SAI_TCR4_FSE;
+               tcr4 |= SAI_TCR4_FSP;
+               break;
+       }
+
+       /* DAI clock inversion */
+       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+       case SND_SOC_DAIFMT_IB_IF:
+               tcr4 |= SAI_TCR4_FSP;
+               tcr2 &= ~SAI_TCR2_BCP;
+               break;
+       case SND_SOC_DAIFMT_IB_NF:
+               tcr4 &= ~SAI_TCR4_FSP;
+               tcr2 &= ~SAI_TCR2_BCP;
+               break;
+       case SND_SOC_DAIFMT_NB_IF:
+               tcr4 |= SAI_TCR4_FSP;
+               tcr2 |= SAI_TCR2_BCP;
+               break;
+       case SND_SOC_DAIFMT_NB_NF:
+               tcr4 &= ~SAI_TCR4_FSP;
+               tcr2 |= SAI_TCR2_BCP;
+               break;
+       }
+
+       /* DAI clock master masks */
+       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+       case SND_SOC_DAIFMT_CBS_CFS:
+               tcr2 |= SAI_TCR2_BCD_MSTR;
+               tcr4 |= SAI_TCR4_FSD_MSTR;
+               break;
+       case SND_SOC_DAIFMT_CBM_CFM:
+               tcr2 &= ~SAI_TCR2_BCD_MSTR;
+               tcr4 &= ~SAI_TCR4_FSD_MSTR;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       tcr3 |= SAI_TCR3_TCE;
+
+       if (sai->flags & MVF_SAI_TRA_SYN) {
+               rcr2 = tcr2;
+               rcr2 |= SAI_TCR2_SYNC;
+       }
+
+       if (sai->play_enabled == 0) {
+               writel(tcr2, sai->base + SAI_TCR2);
+               writel(tcr3, sai->base + SAI_TCR3);
+               writel(tcr4, sai->base + SAI_TCR4);
+       }
+
+       if (sai->cap_enabled == 0) {
+               writel(rcr2, sai->base + SAI_RCR2);
+               writel(tcr3, sai->base + SAI_RCR3);
+               writel(tcr4, sai->base + SAI_RCR4);
+       }
+       return 0;
+}
+
+/* SAI system clock configuration */
+static int mvf_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+                                 int clk_id, unsigned int freq, int dir)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       u32 tcr2;
+
+       tcr2 = readl(sai->base + SAI_TCR2);
+
+       if (dir == SND_SOC_CLOCK_IN)
+               return 0;
+
+       switch (clk_id) {
+       case MVF_SAI_BUS_CLK:
+               tcr2 &= ~SAI_TCR2_MSEL_MASK;
+               tcr2 |= SAI_TCR2_MSEL_BUS;
+               break;
+       case MVF_SAI_MAST_CLK1:
+               tcr2 &= ~SAI_TCR2_MSEL_MASK;
+               tcr2 |= SAI_TCR2_MSEL_MCLK1;
+               break;
+       case MVF_SAI_MAST_CLK2:
+               tcr2 &= ~SAI_TCR2_MSEL_MASK;
+               tcr2 |= SAI_TCR2_MSEL_MCLK2;
+               break;
+       case MVF_SAI_MAST_CLK3:
+               tcr2 &= ~SAI_TCR2_MSEL_MASK;
+               tcr2 |= SAI_TCR2_MSEL_MCLK3;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (sai->play_enabled == 0)
+               writel(tcr2, sai->base + SAI_TCR2);
+       if (sai->cap_enabled == 0)
+               writel(tcr2, sai->base + SAI_RCR2);
+       return 0;
+}
+
+/* SAI Clock dividers */
+static int mvf_sai_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+                                 int div_id, int div)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       u32 tcr2, rcr2;
+
+       tcr2 = readl(sai->base + SAI_TCR2);
+       rcr2 = readl(sai->base + SAI_RCR2);
+
+       switch (div_id) {
+       case MVF_SAI_TX_DIV:
+               tcr2 &= ~SAI_TCR2_DIV_MASK;
+               tcr2 |= SAI_TCR2_DIV(div);
+               break;
+       case MVF_SAI_RX_DIV:
+               rcr2 &= ~SAI_RCR2_DIV_MASK;
+               rcr2 |= SAI_RCR2_DIV(div);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (sai->play_enabled == 0)
+               writel(tcr2, sai->base + SAI_TCR2);
+       if (sai->cap_enabled == 0)
+               writel(rcr2, sai->base + SAI_RCR2);
+       return 0;
+}
+
+static int mvf_sai_hw_params(struct snd_pcm_substream *substream,
+                            struct snd_pcm_hw_params *params,
+                            struct snd_soc_dai *cpu_dai)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+       struct imx_pcm_dma_params *dma_data;
+       u32 tcr4, tcr5;
+
+       /* Tx/Rx config */
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               dma_data = &sai->dma_params_tx;
+       else
+               dma_data = &sai->dma_params_rx;
+
+       snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
+
+       tcr4 = readl(sai->base + SAI_TCR4);
+       tcr4 &= ~SAI_TCR4_SYWD_MASK;
+
+       tcr5 = readl(sai->base + SAI_TCR5);
+       tcr5 &= ~SAI_TCR5_WNW_MASK;
+       tcr5 &= ~SAI_TCR5_W0W_MASK;
+       tcr5 &= ~SAI_TCR5_FBT_MASK;
+
+       /* DAI data (word) size */
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               tcr4 |= SAI_TCR4_SYWD(16 - 1);
+               tcr5 |= SAI_TCR5_WNW(16 - 1);
+               tcr5 |= SAI_TCR5_W0W(16 - 1);
+               tcr5 |= SAI_TCR5_FBT(16 - 1);
+               break;
+       case SNDRV_PCM_FORMAT_S20_3LE:
+               tcr4 |= SAI_TCR4_SYWD(20 - 1);
+               tcr5 |= SAI_TCR5_WNW(20 - 1);
+               tcr5 |= SAI_TCR5_W0W(20 - 1);
+               tcr5 |= SAI_TCR5_FBT(20 - 1);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               tcr4 |= SAI_TCR4_SYWD(24 - 1);
+               tcr5 |= SAI_TCR5_WNW(24 - 1);
+               tcr5 |= SAI_TCR5_W0W(24 - 1);
+               tcr5 |= SAI_TCR5_FBT(24 - 1);
+               break;
+       }
+
+       writel(tcr4, sai->base + SAI_TCR4);
+       writel(tcr5, sai->base + SAI_TCR5);
+       writel(tcr4, sai->base + SAI_RCR4);
+       writel(tcr5, sai->base + SAI_RCR5);
+       return 0;
+}
+
+static int mvf_sai_trigger(struct snd_pcm_substream *substream, int cmd,
+               struct snd_soc_dai *dai)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(dai);
+       unsigned int tcsr, rcsr;
+
+       tcsr = readl(sai->base + SAI_TCSR);
+       rcsr = readl(sai->base + SAI_RCSR);
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               sai->play_enabled = 1;
+               if (sai->flags & MVF_SAI_DMA)
+                       tcsr |= SAI_TCSR_FRDE;
+               else
+                       tcsr |= SAI_TCSR_FRIE | SAI_TCSR_FWF;
+       } else {
+               sai->cap_enabled = 1;
+               if (sai->flags & MVF_SAI_DMA)
+                       rcsr |= SAI_RCSR_FRDE;
+               else
+                       rcsr |= SAI_RCSR_FRIE | SAI_RCSR_FWF;
+       }
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+                       rcsr |= SAI_RCSR_RE;
+                       tcsr |= SAI_TCSR_TE;
+                       writel(tcsr, sai->base + SAI_TCSR);
+                       writel(rcsr, sai->base + SAI_RCSR);
+               break;
+
+       case SNDRV_PCM_TRIGGER_STOP:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+                       tcsr &= ~SAI_TCSR_TE;
+                       rcsr &= ~SAI_RCSR_RE;
+                       if (!(dai->playback_active & dai->capture_active)) {
+                               writel(tcsr, sai->base + SAI_TCSR);
+                               writel(rcsr, sai->base + SAI_RCSR);
+                       }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int mvf_sai_startup(struct snd_pcm_substream *substream,
+                          struct snd_soc_dai *cpu_dai)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+
+       if (cpu_dai->playback_active || cpu_dai->capture_active)
+               return 0;
+
+       clk_enable(sai->clk);
+
+       return 0;
+}
+
+static void mvf_sai_shutdown(struct snd_pcm_substream *substream,
+                            struct snd_soc_dai *cpu_dai)
+{
+       struct mvf_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+
+       if (cpu_dai->playback_active == 0)
+               sai->play_enabled = 0;
+       if (cpu_dai->capture_active == 0)
+               sai->cap_enabled = 0;
+
+       /* shutdown SAI if neither Tx or Rx is active */
+       if (cpu_dai->playback_active || cpu_dai->capture_active)
+               return;
+
+       clk_disable(sai->clk);
+}
+
+static struct snd_soc_dai_ops mvf_sai_pcm_dai_ops = {
+       .hw_params      = mvf_sai_hw_params,
+       .set_fmt        = mvf_sai_set_dai_fmt,
+       .set_clkdiv     = mvf_sai_set_dai_clkdiv,
+       .set_sysclk     = mvf_sai_set_dai_sysclk,
+       .set_tdm_slot   = mvf_sai_set_dai_tdm_slot,
+       .trigger        = mvf_sai_trigger,
+       .startup        = mvf_sai_startup,
+       .shutdown       = mvf_sai_shutdown,
+};
+
+int snd_mvf_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_mvf_pcm_mmap);
+
+static int mvf_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 = MVF_SAI_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 mvf_pcm_dmamask = DMA_BIT_MASK(32);
+
+int mvf_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 = &mvf_pcm_dmamask;
+       if (!card->dev->coherent_dma_mask)
+               card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+       if (dai->driver->playback.channels_min) {
+               ret = mvf_pcm_preallocate_dma_buffer(pcm,
+                       SNDRV_PCM_STREAM_PLAYBACK);
+               if (ret)
+                       goto out;
+       }
+
+       if (dai->driver->capture.channels_min) {
+               ret = mvf_pcm_preallocate_dma_buffer(pcm,
+                       SNDRV_PCM_STREAM_CAPTURE);
+               if (ret)
+                       goto out;
+       }
+
+out:
+       return ret;
+}
+EXPORT_SYMBOL_GPL(mvf_pcm_new);
+
+void mvf_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(mvf_pcm_free);
+
+static int mvf_sai_dai_probe(struct snd_soc_dai *dai)
+{
+       struct mvf_sai *sai = dev_get_drvdata(dai->dev);
+
+       snd_soc_dai_set_drvdata(dai, sai);
+
+       writel(sai->dma_params_tx.burstsize, sai->base + SAI_TCR1);
+       writel(sai->dma_params_rx.burstsize, sai->base + SAI_RCR1);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM
+static int mvf_sai_dai_suspend(struct snd_soc_dai *dai)
+{
+       return 0;
+}
+
+static int mvf_sai_dai_resume(struct snd_soc_dai *dai)
+{
+       return 0;
+}
+#else
+#define mvf_sai_dai_suspend    NULL
+#define mvf_sai_dai_resume     NULL
+#endif
+
+static struct snd_soc_dai_driver mvf_sai_dai = {
+       .probe = mvf_sai_dai_probe,
+       .suspend = mvf_sai_dai_suspend,
+       .resume = mvf_sai_dai_resume,
+       .playback = {
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_96000,
+               .formats = MVF_SAI_FORMATS,
+       },
+       .capture = {
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_96000,
+               .formats = MVF_SAI_FORMATS,
+       },
+       .ops = &mvf_sai_pcm_dai_ops,
+};
+
+static int mvf_sai_probe(struct platform_device *pdev)
+{
+       struct resource *res;
+       struct mvf_sai *sai;
+       struct mvf_sai_platform_data *pdata = pdev->dev.platform_data;
+       int ret = 0;
+       struct snd_soc_dai_driver *dai;
+
+       sai = kzalloc(sizeof(*sai), GFP_KERNEL);
+       if (!sai)
+               return -ENOMEM;
+       dev_set_drvdata(&pdev->dev, sai);
+
+       if (pdata)
+               sai->flags = pdata->flags;
+
+       res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+       if (!res) {
+               ret = -ENODEV;
+               goto failed_clk;
+       }
+       sai->irq = res->start;
+
+       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;
+       }
+
+       sai->base = ioremap(res->start, resource_size(res));
+       if (!sai->base) {
+               dev_err(&pdev->dev, "ioremap failed\n");
+               ret = -ENODEV;
+               goto failed_ioremap;
+       }
+
+       sai->clk = clk_get(&pdev->dev, "sai_clk");
+       if (IS_ERR(sai->clk)) {
+               ret = PTR_ERR(sai->clk);
+               dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+                       ret);
+               goto failed_clk;
+       }
+       clk_enable(sai->clk);
+
+       if (sai->flags & MVF_SAI_USE_I2S_SLAVE)
+               dai = &mvf_sai_dai;
+
+       writel(0x0, sai->base + SAI_TCSR);
+       writel(0x0, sai->base + SAI_RCSR);
+
+       sai->dma_params_rx.dma_addr = res->start + SAI_RDR;
+       sai->dma_params_tx.dma_addr = res->start + SAI_TDR;
+
+       sai->dma_params_tx.burstsize = 6;
+       sai->dma_params_rx.burstsize = 6;
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
+       if (res)
+               sai->dma_params_tx.dma = res->start;
+
+       res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
+       if (res)
+               sai->dma_params_rx.dma = res->start;
+
+       platform_set_drvdata(pdev, sai);
+
+       ret = snd_soc_register_dai(&pdev->dev, dai);
+       if (ret) {
+               dev_err(&pdev->dev, "register DAI failed\n");
+               goto failed_register;
+       }
+
+       sai->soc_platform_pdev =
+                       platform_device_alloc("mvf-pcm-audio", pdev->id);
+       if (!sai->soc_platform_pdev) {
+               ret = -ENOMEM;
+               goto failed_pdev_alloc;
+       }
+
+       platform_set_drvdata(sai->soc_platform_pdev, sai);
+       ret = platform_device_add(sai->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(sai->soc_platform_pdev);
+failed_pdev_alloc:
+       snd_soc_unregister_dai(&pdev->dev);
+failed_register:
+       iounmap(sai->base);
+failed_ioremap:
+       release_mem_region(res->start, resource_size(res));
+failed_get_resource:
+       clk_disable(sai->clk);
+       clk_put(sai->clk);
+failed_clk:
+       kfree(sai);
+
+       return ret;
+}
+
+static int __devexit mvf_sai_remove(struct platform_device *pdev)
+{
+       struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       struct mvf_sai *sai = platform_get_drvdata(pdev);
+
+       platform_device_unregister(sai->soc_platform_pdev);
+
+       snd_soc_unregister_dai(&pdev->dev);
+
+       iounmap(sai->base);
+       release_mem_region(res->start, resource_size(res));
+       clk_disable(sai->clk);
+       kfree(sai);
+
+       return 0;
+}
+
+static struct platform_driver mvf_sai_driver = {
+       .probe = mvf_sai_probe,
+       .remove = __devexit_p(mvf_sai_remove),
+
+       .driver = {
+               .name = "mvf-sai",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init mvf_sai_init(void)
+{
+       return platform_driver_register(&mvf_sai_driver);
+}
+
+static void __exit mvf_sai_exit(void)
+{
+       platform_driver_unregister(&mvf_sai_driver);
+}
+
+module_init(mvf_sai_init);
+module_exit(mvf_sai_exit);
+
+/* Module information */
+MODULE_AUTHOR("Alison Wang, <b18965@freescale.com>");
+MODULE_DESCRIPTION("Faraday I2S SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:mvf-sai");
diff --git a/sound/soc/mvf/mvf-sai.h b/sound/soc/mvf/mvf-sai.h
new file mode 100644 (file)
index 0000000..1406e28
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, 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.
+ */
+
+#ifndef _MVF_SAI_H
+#define _MVF_SAI_H
+
+#define SAI_TX_DIV             0
+
+#define SAI_TCSR               0x00
+#define SAI_TCSR_TE            (1 << 31)
+#define SAI_TCSR_FWF           (1 << 17)
+#define SAI_TCSR_FRIE          (1 << 8)
+#define SAI_TCSR_FRDE          (1 << 0)
+
+#define SAI_TCR1               0x04
+
+#define SAI_TCR2               0x08
+#define SAI_TCR2_SYNC          (1 << 30)
+#define SAI_TCR2_MSEL_MASK     (0xff << 26)
+#define SAI_TCR2_MSEL_BUS      (0 << 26)
+#define SAI_TCR2_MSEL_MCLK1    (1 << 26)
+#define SAI_TCR2_MSEL_MCLK2    (2 << 26)
+#define SAI_TCR2_MSEL_MCLK3    (3 << 26)
+/* Bit clock is active low with driver outputs on
+ * falling edge and sample inputs on rising edge */
+#define SAI_TCR2_BCP           (1 << 25)
+#define SAI_TCR2_BCD_MSTR      (1 << 24)
+#define SAI_TCR2_DIV(x)                (x)
+#define SAI_TCR2_DIV_MASK      0xff
+
+#define SAI_TCR3               0x0c
+#define SAI_TCR3_TCE           (1 << 16)
+#define SAI_TCR3_WDFL(x)       (x)
+#define SAI_TCR3_WDFL_MASK     0x1f
+
+#define SAI_TCR4               0x10
+#define SAI_TCR4_FRSZ(x)       (x << 16)
+#define SAI_TCR4_FRSZ_MASK     (0x1f << 16)
+#define SAI_TCR4_SYWD(x)       ((x) << 8)
+#define SAI_TCR4_SYWD_MASK     (0x1f << 8)
+#define SAI_TCR4_MF            (1 << 4)
+/* Frame sync is active low */
+#define SAI_TCR4_FSE           (1 << 3)
+#define SAI_TCR4_FSP           (1 << 1)
+#define SAI_TCR4_FSD_MSTR      (1 << 0)
+
+#define SAI_TCR5               0x14
+#define SAI_TCR5_WNW(x)                ((x) << 24)
+#define SAI_TCR5_WNW_MASK      (0x1f << 24)
+#define SAI_TCR5_W0W(x)                ((x) << 16)
+#define SAI_TCR5_W0W_MASK      (0x1f << 16)
+#define SAI_TCR5_FBT(x)                ((x) << 8)
+#define SAI_TCR5_FBT_MASK      (0x1f << 8)
+
+#define SAI_TDR                        0x20
+
+#define SAI_TFR                        0x40
+
+#define SAI_TMR                        0x60
+
+
+#define SAI_RCSR               0x80
+#define SAI_RCSR_RE            (1 << 31)
+#define SAI_RCSR_FWF           (1 << 17)
+#define SAI_RCSR_FRIE          (1 << 8)
+#define SAI_RCSR_FRDE          (1 << 0)
+
+#define SAI_RCR1               0x84
+
+#define SAI_RCR2               0x88
+#define SAI_RCR2_MSEL_MASK     (0xff << 26)
+#define SAI_RCR2_MSEL_BUS      (0 << 26)
+#define SAI_RCR2_MSEL_MCLK1    (1 << 26)
+#define SAI_RCR2_MSEL_MCLK2    (2 << 26)
+#define SAI_RCR2_MSEL_MCLK3    (3 << 26)
+/* Bit clock is active low with driver outputs on
+ * falling edge and sample inputs on rising edge */
+#define SAI_RCR2_BCP           (1 << 25)
+#define SAI_RCR2_BCD_MSTR      (1 << 24)
+#define SAI_RCR2_DIV(x)                (x)
+#define SAI_RCR2_DIV_MASK      0xff
+
+#define SAI_RCR3               0x8c
+#define SAI_RCR3_TCE           (1 << 16)
+#define SAI_RCR3_WDFL(x)       (x)
+#define SAI_RCR3_WDFL_MASK     0x1f
+
+#define SAI_RCR4               0x90
+/* Frame sync is active low */
+#define SAI_RCR4_FRSZ(x)       (x << 16)
+#define SAI_RCR4_FRSZ_MASK     (0x1f << 16)
+#define SAI_RCR4_SYWD(x)       (x << 8)
+#define SAI_RCR4_SYWD_MASK     (0x1f << 8)
+#define SAI_RCR4_MF            (1 << 4)
+/* Frame sync is active low */
+#define SAI_RCR4_FSE           (1 << 3)
+#define SAI_RCR4_FSP           (1 << 1)
+#define SAI_RCR4_FSD_MSTR      (1 << 0)
+
+#define SAI_RCR5               0x94
+#define SAI_RCR5_WNW(x)                (x << 24)
+#define SAI_RCR5_WNW_MASK      (0x1f << 24)
+#define SAI_RCR5_W0W(x)                (x << 16)
+#define SAI_RCR5_W0W_MASK      (0x1f << 16)
+
+#define SAI_RDR                        0xa0
+
+#define SAI_RFR                        0xc0
+
+#define SAI_RMR                        0xe0
+
+/* SAI clock sources */
+#define MVF_SAI_BUS_CLK                0
+#define MVF_SAI_MAST_CLK1      1
+#define MVF_SAI_MAST_CLK2      2
+#define MVF_SAI_MAST_CLK3      3
+
+/* SAI audio dividers */
+#define MVF_SAI_TX_DIV         0
+#define MVF_SAI_RX_DIV         1
+
+#define DRV_NAME "mvf-sai"
+
+#include <linux/dmaengine.h>
+#include <mach/dma.h>
+
+struct mvf_sai {
+       struct clk *clk;
+       void __iomem *base;
+       int irq;
+       int fiq_enable;
+       unsigned int offset;
+
+       unsigned int flags;
+
+       struct imx_pcm_dma_params       dma_params_rx;
+       struct imx_pcm_dma_params       dma_params_tx;
+
+       int play_enabled;
+       int cap_enabled;
+
+       struct platform_device *soc_platform_pdev;
+       struct platform_device *soc_platform_pdev_fiq;
+};
+
+int snd_mvf_pcm_mmap(struct snd_pcm_substream *substream,
+                       struct vm_area_struct *vma);
+int mvf_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+       struct snd_pcm *pcm);
+void mvf_pcm_free(struct snd_pcm *pcm);
+
+#define MVF_SAI_DMABUF_SIZE    (32 * 1024)
+#define TCD_NUMBER             4
+
+#endif /* _MVF_SAI_H */
diff --git a/sound/soc/mvf/mvf-sgtl5000.c b/sound/soc/mvf/mvf-sgtl5000.c
new file mode 100644 (file)
index 0000000..a5fb360
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+ * sound/soc/mvf-sgtl5000.c --  SoC audio for Faraday TWR-AUDIO-SGTL boards
+ *                             with sgtl5000 codec
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/fsl_devices.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
+
+#include "../codecs/sgtl5000.h"
+#include "mvf-sai.h"
+
+
+static struct mvf_sgtl5000_priv {
+       int sysclk;
+       int hw;
+       struct platform_device *pdev;
+} card_priv;
+
+static struct snd_soc_card mvf_sgtl5000;
+
+static int sgtl5000_params(struct snd_pcm_substream *substream,
+       struct snd_pcm_hw_params *params)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_dai *codec_dai = rtd->codec_dai;
+       struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+       u32 dai_format;
+       int ret;
+       unsigned int channels = params_channels(params);
+
+       snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, card_priv.sysclk, 1);
+
+       dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+               SND_SOC_DAIFMT_CBM_CFM;
+
+       /* set codec DAI configuration */
+       ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+       if (ret < 0)
+               return ret;
+
+       /* TODO: The SAI driver should figure this out for us */
+       switch (channels) {
+       case 2:
+               snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffc, 0xfffffffc, 2, 0);
+               break;
+       case 1:
+               snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffe, 0xfffffffe, 1, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       /* set cpu DAI configuration */
+       dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF |
+               SND_SOC_DAIFMT_CBM_CFM;
+       ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static struct snd_soc_ops mvf_sgtl5000_hifi_ops = {
+       .hw_params = sgtl5000_params,
+};
+
+static int sgtl5000_jack_func;
+static int sgtl5000_spk_func;
+static int sgtl5000_line_in_func;
+
+static const char * const jack_function[] = { "off", "on"};
+
+static const char * const spk_function[] = { "off", "on" };
+
+static const char * const line_in_function[] = { "off", "on" };
+
+static const struct soc_enum sgtl5000_enum[] = {
+       SOC_ENUM_SINGLE_EXT(2, jack_function),
+       SOC_ENUM_SINGLE_EXT(2, spk_function),
+       SOC_ENUM_SINGLE_EXT(2, line_in_function),
+};
+
+static int sgtl5000_get_jack(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.enumerated.item[0] = sgtl5000_jack_func;
+       return 0;
+}
+
+static int sgtl5000_set_jack(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+       if (sgtl5000_jack_func == ucontrol->value.enumerated.item[0])
+               return 0;
+
+       sgtl5000_jack_func = ucontrol->value.enumerated.item[0];
+       if (sgtl5000_jack_func)
+               snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+       else
+               snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack");
+
+       snd_soc_dapm_sync(&codec->dapm);
+       return 1;
+}
+
+static int sgtl5000_get_spk(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.enumerated.item[0] = sgtl5000_spk_func;
+       return 0;
+}
+
+static int sgtl5000_set_spk(struct snd_kcontrol *kcontrol,
+                           struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+       if (sgtl5000_spk_func == ucontrol->value.enumerated.item[0])
+               return 0;
+
+       sgtl5000_spk_func = ucontrol->value.enumerated.item[0];
+       if (sgtl5000_spk_func)
+               snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk");
+       else
+               snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk");
+
+       snd_soc_dapm_sync(&codec->dapm);
+       return 1;
+}
+
+static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func;
+       return 0;
+}
+
+static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+       if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0])
+               return 0;
+
+       sgtl5000_line_in_func = ucontrol->value.enumerated.item[0];
+       if (sgtl5000_line_in_func)
+               snd_soc_dapm_enable_pin(&codec->dapm, "Line In Jack");
+       else
+               snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack");
+
+       snd_soc_dapm_sync(&codec->dapm);
+       return 1;
+}
+
+static const struct snd_soc_dapm_widget mvf_twr_dapm_widgets[] = {
+       SND_SOC_DAPM_MIC("Mic Jack", NULL),
+       SND_SOC_DAPM_LINE("Line In Jack", NULL),
+       SND_SOC_DAPM_SPK("Ext Spk", NULL),
+       SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new sgtl5000_machine_controls[] = {
+       SOC_ENUM_EXT("Jack Function", sgtl5000_enum[0], sgtl5000_get_jack,
+                    sgtl5000_set_jack),
+       SOC_ENUM_EXT("Speaker Function", sgtl5000_enum[1], sgtl5000_get_spk,
+                    sgtl5000_set_spk),
+       SOC_ENUM_EXT("Line In Function", sgtl5000_enum[1], sgtl5000_get_line_in,
+                    sgtl5000_set_line_in),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+
+       /* Mic Jack --> MIC_IN (with automatic bias) */
+       {"MIC_IN", NULL, "Mic Jack"},
+
+       /* Line in Jack --> LINE_IN */
+       {"LINE_IN", NULL, "Line In Jack"},
+
+       /* HP_OUT --> Headphone Jack */
+       {"Headphone Jack", NULL, "HP_OUT"},
+
+       /* LINE_OUT --> Ext Speaker */
+       {"Ext Spk", NULL, "LINE_OUT"},
+};
+
+static int mvf_twr_sgtl5000_init(struct snd_soc_pcm_runtime *rtd)
+{
+       struct snd_soc_codec *codec = rtd->codec;
+       int ret;
+
+       ret = snd_soc_add_controls(codec, sgtl5000_machine_controls,
+                       ARRAY_SIZE(sgtl5000_machine_controls));
+       if (ret)
+               return ret;
+
+       /* Add mvf_twr specific widgets */
+       snd_soc_dapm_new_controls(&codec->dapm, mvf_twr_dapm_widgets,
+                                 ARRAY_SIZE(mvf_twr_dapm_widgets));
+
+       /* Set up mvf_twr specific audio path audio_map */
+       snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
+
+       snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack");
+       snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
+       snd_soc_dapm_sync(&codec->dapm);
+
+       return 0;
+}
+
+static struct snd_soc_dai_link mvf_sgtl5000_dai[] = {
+       {
+               .name           = "HiFi",
+               .stream_name    = "HiFi",
+               .codec_dai_name = "sgtl5000",
+               .codec_name     = "sgtl5000.0-000a",
+               .cpu_dai_name   = "mvf-sai.0",
+               .platform_name  = "mvf-pcm-audio.0",
+               .init           = mvf_twr_sgtl5000_init,
+               .ops            = &mvf_sgtl5000_hifi_ops,
+       },
+};
+
+static struct snd_soc_card mvf_sgtl5000 = {
+       .name           = "sgtl5000-sai",
+       .dai_link       = mvf_sgtl5000_dai,
+       .num_links      = ARRAY_SIZE(mvf_sgtl5000_dai),
+};
+
+static struct platform_device *mvf_sgtl5000_snd_device;
+
+static int __devinit mvf_sgtl5000_probe(struct platform_device *pdev)
+{
+       struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+       card_priv.pdev = pdev;
+
+       if (plat->init && plat->init())
+               return -EINVAL;
+
+       card_priv.sysclk = 24576000;
+
+       return 0;
+}
+
+static int mvf_sgtl5000_remove(struct platform_device *pdev)
+{
+       struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+       if (plat->finit)
+               plat->finit();
+
+       return 0;
+}
+
+static struct platform_driver mvf_sgtl5000_audio_driver = {
+       .probe = mvf_sgtl5000_probe,
+       .remove = mvf_sgtl5000_remove,
+       .driver = {
+                  .name = "mvf-sgtl5000",
+                  .owner = THIS_MODULE,
+                  },
+};
+
+static int __init mvf_sgtl5000_init(void)
+{
+       int ret;
+
+       ret = platform_driver_register(&mvf_sgtl5000_audio_driver);
+       if (ret)
+               return -ENOMEM;
+
+       mvf_sgtl5000_dai[0].codec_name = "sgtl5000.0-000a";
+
+       mvf_sgtl5000_snd_device = platform_device_alloc("soc-audio", 1);
+       if (!mvf_sgtl5000_snd_device)
+               return -ENOMEM;
+
+       platform_set_drvdata(mvf_sgtl5000_snd_device, &mvf_sgtl5000);
+
+       ret = platform_device_add(mvf_sgtl5000_snd_device);
+
+       if (ret) {
+               printk(KERN_ERR "ASoC: Platform device allocation failed\n");
+               platform_device_put(mvf_sgtl5000_snd_device);
+       }
+
+       return ret;
+}
+
+static void __exit mvf_sgtl5000_exit(void)
+{
+       platform_driver_unregister(&mvf_sgtl5000_audio_driver);
+       platform_device_unregister(mvf_sgtl5000_snd_device);
+}
+
+module_init(mvf_sgtl5000_init);
+module_exit(mvf_sgtl5000_exit);
+
+MODULE_AUTHOR("Alison Wang, <b18965@freescale.com>");
+MODULE_DESCRIPTION("PhyCORE ALSA SoC driver");
+MODULE_LICENSE("GPL");