summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorAlison Wang <b18965@freescale.com>2012-07-27 11:10:54 +0800
committerJustin Waters <justin.waters@timesys.com>2012-09-12 16:49:52 -0400
commit74a8bbab14c2f5e9e1fd3a263b090484a988f673 (patch)
treed9efba7d939e75c2c43bd5654c3291029c1e4a89 /sound
parentcb9324a27200e6ba9c39a0e2536336d8cb35d3ab (diff)
ENGR00212251-2: sai: add SAI driver support for Faraday
Add SAI driver support for Faraday. Signed-off-by: Alison Wang <b18965@freescale.com> Signed-off-by: Xiaochun Li <b41219@freescale.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/Kconfig1
-rw-r--r--sound/soc/Makefile1
-rw-r--r--sound/soc/codecs/sgtl5000.c16
-rw-r--r--sound/soc/mvf/Kconfig26
-rw-r--r--sound/soc/mvf/Makefile11
-rw-r--r--sound/soc/mvf/mvf-pcm-dma-twr.c403
-rw-r--r--sound/soc/mvf/mvf-sai.c642
-rw-r--r--sound/soc/mvf/mvf-sai.h160
-rw-r--r--sound/soc/mvf/mvf-sgtl5000.c320
9 files changed, 1575 insertions, 5 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 8224db5f0434..1dacae842f74 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -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"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 1ed61c5df2c5..96471b431b3b 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -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/
diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c
index fd055146192d..687b62895bb2 100644
--- a/sound/soc/codecs/sgtl5000.c
+++ b/sound/soc/codecs/sgtl5000.c
@@ -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
index 000000000000..6577df843926
--- /dev/null
+++ b/sound/soc/mvf/Kconfig
@@ -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
index 000000000000..c9e179ad0a8e
--- /dev/null
+++ b/sound/soc/mvf/Makefile
@@ -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
index 000000000000..83832735408c
--- /dev/null
+++ b/sound/soc/mvf/mvf-pcm-dma-twr.c
@@ -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
index 000000000000..68b819d78aee
--- /dev/null
+++ b/sound/soc/mvf/mvf-sai.c
@@ -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
index 000000000000..1406e28883a0
--- /dev/null
+++ b/sound/soc/mvf/mvf-sai.h
@@ -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
index 000000000000..a5fb36038f66
--- /dev/null
+++ b/sound/soc/mvf/mvf-sgtl5000.c
@@ -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");