summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/core/pcm_lib.c38
-rw-r--r--sound/soc/fsl/Kconfig28
-rw-r--r--sound/soc/fsl/Makefile8
-rw-r--r--sound/soc/fsl/fsl_sai.c640
-rw-r--r--sound/soc/fsl/fsl_sai.h114
-rw-r--r--sound/soc/fsl/vf610-sgtl5000.c169
6 files changed, 996 insertions, 1 deletions
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index e1ef106..6dd3089 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -1964,6 +1964,9 @@ static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
+ int i;
+ char tmp;
+
if (substream->ops->copy) {
if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
return err;
@@ -1971,6 +1974,22 @@ static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream,
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
return -EFAULT;
+
+ if (substream->data_swapped) {
+ switch (runtime->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ for (i = 0;
+ i < frames_to_bytes(runtime, frames);
+ i = i + 2) {
+ tmp = *(hwbuf + i);
+ *(hwbuf + i) = *(hwbuf + i + 1);
+ *(hwbuf + i + 1) = tmp;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
}
return 0;
}
@@ -2186,11 +2205,30 @@ static int snd_pcm_lib_read_transfer(struct snd_pcm_substream *substream,
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
+ int i;
+ char tmp;
+
if (substream->ops->copy) {
if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
return err;
} else {
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
+ if (substream->data_swapped) {
+ switch (runtime->format) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ for (i = 0;
+ i < frames_to_bytes(runtime, frames);
+ i = i + 2) {
+ tmp = *(hwbuf + i);
+ *(hwbuf + i) = *(hwbuf + i + 1);
+ *(hwbuf + i + 1) = tmp;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
if (copy_to_user(buf, hwbuf, frames_to_bytes(runtime, frames)))
return -EFAULT;
}
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index b7ab71f..5acb70b 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -1,3 +1,8 @@
+config SND_SOC_FSL_SAI
+ tristate
+ select REGMAP_MMIO
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+
config SND_SOC_FSL_SSI
tristate
@@ -213,3 +218,26 @@ config SND_SOC_IMX_MC13783
select SND_SOC_IMX_PCM_DMA
endif # SND_IMX_SOC
+
+menuconfig SND_VF610_SOC
+ tristate "SoC Audio for Freescale VF610 CPUs"
+ select DMA_ENGINE
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the VF610 CPUs.
+
+ This will enable Freeacale SAI and SGTL5000 codec, and an extra
+ TWR-AUDIO-SGTL sub-board is needed for SGTL5000.
+
+if SND_VF610_SOC
+
+config SND_SOC_VF610_SGTL5000
+ tristate "SoC Audio support for VF610 boards with SGTL5000"
+ depends on OF && I2C
+ select SND_SOC_FSL_SAI
+ select SND_SOC_SGTL5000
+ help
+ Say Y if you want to add support for SoC audio on an VF610 board with
+ a SGTL5000 codec and a SAI.
+
+endif # SND_VF610_SOC
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index 8db705b..96110d1 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -10,16 +10,22 @@ obj-$(CONFIG_SND_SOC_P1022_DS) += snd-soc-p1022-ds.o
snd-soc-p1022-rdk-objs := p1022_rdk.o
obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
-# Freescale PowerPC SSI/DMA Platform Support
+# Freescale SSI/DMA/SAI/SPDIF Support
+snd-soc-fsl-sai-objs := fsl_sai.o
snd-soc-fsl-ssi-objs := fsl_ssi.o
snd-soc-fsl-spdif-objs := fsl_spdif.o
snd-soc-fsl-utils-objs := fsl_utils.o
snd-soc-fsl-dma-objs := fsl_dma.o
+obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
+# VF610 Platform Support
+snd-soc-vf610-sgtl5000-objs := vf610-sgtl5000.o
+obj-$(CONFIG_SND_SOC_VF610_SGTL5000) += snd-soc-vf610-sgtl5000.o
+
# MPC5200 Platform Support
obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o
obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
new file mode 100644
index 0000000..3653b25
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.c
@@ -0,0 +1,640 @@
+/*
+ * Freescale ALSA SoC Digital Audio Interface (SAI) driver.
+ *
+ * Copyright 2012-2013 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/dmaengine.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+
+#include "fsl_sai.h"
+
+static int fsl_sai_set_dai_sysclk_tr(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int fsl_dir)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 val_cr2, reg_cr2;
+
+ if (fsl_dir == FSL_FMT_TRANSMITTER)
+ reg_cr2 = FSL_SAI_TCR2;
+ else
+ reg_cr2 = FSL_SAI_RCR2;
+
+ regmap_read(sai->regmap, reg_cr2, &val_cr2);
+
+ val_cr2 &= ~FSL_SAI_CR2_MSEL_MASK;
+
+ switch (clk_id) {
+ case FSL_SAI_CLK_BUS:
+ val_cr2 |= FSL_SAI_CR2_MSEL_BUS;
+ break;
+ case FSL_SAI_CLK_MAST1:
+ val_cr2 |= FSL_SAI_CR2_MSEL_MCLK1;
+ break;
+ case FSL_SAI_CLK_MAST2:
+ val_cr2 |= FSL_SAI_CR2_MSEL_MCLK2;
+ break;
+ case FSL_SAI_CLK_MAST3:
+ val_cr2 |= FSL_SAI_CR2_MSEL_MCLK3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write(sai->regmap, reg_cr2, val_cr2);
+
+ return 0;
+}
+
+static int fsl_sai_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ int ret;
+
+ if (dir == SND_SOC_CLOCK_IN)
+ return 0;
+
+ ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
+ FSL_FMT_TRANSMITTER);
+ if (ret) {
+ dev_err(cpu_dai->dev, "Cannot set tx sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_sai_set_dai_sysclk_tr(cpu_dai, clk_id, freq,
+ FSL_FMT_RECEIVER);
+ if (ret)
+ dev_err(cpu_dai->dev, "Cannot set rx sysclk: %d\n", ret);
+
+ return ret;
+}
+
+static int fsl_sai_set_dai_fmt_tr(struct snd_soc_dai *cpu_dai,
+ unsigned int fmt, int fsl_dir)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 val_cr2, val_cr4, reg_cr2, reg_cr4;
+
+ if (fsl_dir == FSL_FMT_TRANSMITTER) {
+ reg_cr2 = FSL_SAI_TCR2;
+ reg_cr4 = FSL_SAI_TCR4;
+ } else {
+ reg_cr2 = FSL_SAI_RCR2;
+ reg_cr4 = FSL_SAI_RCR4;
+ }
+
+ regmap_read(sai->regmap, reg_cr2, &val_cr2);
+ regmap_read(sai->regmap, reg_cr4, &val_cr4);
+
+ if (sai->big_endian_data)
+ val_cr4 &= ~FSL_SAI_CR4_MF;
+ else
+ val_cr4 |= FSL_SAI_CR4_MF;
+
+ /* DAI mode */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ /*
+ * Frame low, 1clk before data, one word length for frame sync,
+ * frame sync starts one serial clock cycle earlier,
+ * that is, together with the last bit of the previous
+ * data word.
+ */
+ val_cr2 &= ~FSL_SAI_CR2_BCP;
+ val_cr4 |= FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ /*
+ * Frame high, one word length for frame sync,
+ * frame sync asserts with the first bit of the frame.
+ */
+ val_cr2 &= ~FSL_SAI_CR2_BCP;
+ val_cr4 &= ~(FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP);
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ /*
+ * Frame high, 1clk before data, one bit for frame sync,
+ * frame sync starts one serial clock cycle earlier,
+ * that is, together with the last bit of the previous
+ * data word.
+ */
+ val_cr2 &= ~FSL_SAI_CR2_BCP;
+ val_cr4 &= ~FSL_SAI_CR4_FSP;
+ val_cr4 |= FSL_SAI_CR4_FSE;
+ sai->is_dsp_mode = true;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ /*
+ * Frame high, one bit for frame sync,
+ * frame sync asserts with the first bit of the frame.
+ */
+ val_cr2 &= ~FSL_SAI_CR2_BCP;
+ val_cr4 &= ~(FSL_SAI_CR4_FSE | FSL_SAI_CR4_FSP);
+ sai->is_dsp_mode = true;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ /* To be done */
+ default:
+ return -EINVAL;
+ }
+
+ /* DAI clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_IB_IF:
+ /* Invert both clocks */
+ val_cr2 ^= FSL_SAI_CR2_BCP;
+ val_cr4 ^= FSL_SAI_CR4_FSP;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ /* Invert bit clock */
+ val_cr2 ^= FSL_SAI_CR2_BCP;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ /* Invert frame clock */
+ val_cr4 ^= FSL_SAI_CR4_FSP;
+ break;
+ case SND_SOC_DAIFMT_NB_NF:
+ /* Nothing to do for both normal cases */
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* DAI clock master masks */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
+ val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ val_cr2 &= ~FSL_SAI_CR2_BCD_MSTR;
+ val_cr4 &= ~FSL_SAI_CR4_FSD_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ val_cr2 |= FSL_SAI_CR2_BCD_MSTR;
+ val_cr4 &= ~FSL_SAI_CR4_FSD_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ val_cr2 &= ~FSL_SAI_CR2_BCD_MSTR;
+ val_cr4 |= FSL_SAI_CR4_FSD_MSTR;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write(sai->regmap, reg_cr2, val_cr2);
+ regmap_write(sai->regmap, reg_cr4, val_cr4);
+
+ return 0;
+}
+
+static int fsl_sai_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+ int ret;
+
+ ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_TRANSMITTER);
+ if (ret) {
+ dev_err(cpu_dai->dev, "Cannot set tx format: %d\n", ret);
+ return ret;
+ }
+
+ ret = fsl_sai_set_dai_fmt_tr(cpu_dai, fmt, FSL_FMT_RECEIVER);
+ if (ret)
+ dev_err(cpu_dai->dev, "Cannot set rx format: %d\n", ret);
+
+ return ret;
+}
+
+static int fsl_sai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 val_cr4, val_cr5, val_mr, reg_cr4, reg_cr5, reg_mr;
+ unsigned int channels = params_channels(params);
+ u32 word_width = snd_pcm_format_width(params_format(params));
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ reg_cr4 = FSL_SAI_TCR4;
+ reg_cr5 = FSL_SAI_TCR5;
+ reg_mr = FSL_SAI_TMR;
+ } else {
+ reg_cr4 = FSL_SAI_RCR4;
+ reg_cr5 = FSL_SAI_RCR5;
+ reg_mr = FSL_SAI_RMR;
+ }
+
+ regmap_read(sai->regmap, reg_cr4, &val_cr4);
+ regmap_read(sai->regmap, reg_cr4, &val_cr5);
+
+ val_cr4 &= ~FSL_SAI_CR4_SYWD_MASK;
+ val_cr4 &= ~FSL_SAI_CR4_FRSZ_MASK;
+
+ val_cr5 &= ~FSL_SAI_CR5_WNW_MASK;
+ val_cr5 &= ~FSL_SAI_CR5_W0W_MASK;
+ val_cr5 &= ~FSL_SAI_CR5_FBT_MASK;
+
+ if (!sai->is_dsp_mode)
+ val_cr4 |= FSL_SAI_CR4_SYWD(word_width);
+
+ val_cr5 |= FSL_SAI_CR5_WNW(word_width);
+ val_cr5 |= FSL_SAI_CR5_W0W(word_width);
+
+ val_cr5 &= ~FSL_SAI_CR5_FBT_MASK;
+ if (sai->big_endian_data)
+ val_cr5 |= FSL_SAI_CR5_FBT(0);
+ else
+ val_cr5 |= FSL_SAI_CR5_FBT(word_width - 1);
+
+ val_cr4 |= FSL_SAI_CR4_FRSZ(channels);
+ val_mr = ~0UL - ((1 << channels) - 1);
+
+ regmap_write(sai->regmap, reg_cr4, val_cr4);
+ regmap_write(sai->regmap, reg_cr5, val_cr5);
+ regmap_write(sai->regmap, reg_mr, val_mr);
+
+ return 0;
+}
+
+static int fsl_sai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 tcsr, rcsr;
+
+ /*
+ * The transmitter bit clock and frame sync are to be
+ * used by both the transmitter and receiver.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC,
+ ~FSL_SAI_CR2_SYNC);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC,
+ FSL_SAI_CR2_SYNC);
+
+ regmap_read(sai->regmap, FSL_SAI_TCSR, &tcsr);
+ regmap_read(sai->regmap, FSL_SAI_RCSR, &rcsr);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ tcsr |= FSL_SAI_CSR_FRDE;
+ rcsr &= ~FSL_SAI_CSR_FRDE;
+ } else {
+ rcsr |= FSL_SAI_CSR_FRDE;
+ tcsr &= ~FSL_SAI_CSR_FRDE;
+ }
+
+ /*
+ * It is recommended that the transmitter is the last enabled
+ * and the first disabled.
+ */
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ tcsr |= FSL_SAI_CSR_TERE;
+ rcsr |= FSL_SAI_CSR_TERE;
+
+ regmap_write(sai->regmap, FSL_SAI_RCSR, rcsr);
+ regmap_write(sai->regmap, FSL_SAI_TCSR, tcsr);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (!(cpu_dai->playback_active || cpu_dai->capture_active)) {
+ tcsr &= ~FSL_SAI_CSR_TERE;
+ rcsr &= ~FSL_SAI_CSR_TERE;
+ }
+
+ regmap_write(sai->regmap, FSL_SAI_TCSR, tcsr);
+ regmap_write(sai->regmap, FSL_SAI_RCSR, rcsr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_sai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 reg;
+
+ if (sai->big_endian_regs)
+ substream->data_swapped = 1;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = FSL_SAI_TCR3;
+ else
+ reg = FSL_SAI_RCR3;
+
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR3_TRCE,
+ FSL_SAI_CR3_TRCE);
+
+ return 0;
+}
+
+static void fsl_sai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = snd_soc_dai_get_drvdata(cpu_dai);
+ u32 reg;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ reg = FSL_SAI_TCR3;
+ else
+ reg = FSL_SAI_RCR3;
+
+ regmap_update_bits(sai->regmap, reg, FSL_SAI_CR3_TRCE,
+ ~FSL_SAI_CR3_TRCE);
+}
+
+static const struct snd_soc_dai_ops fsl_sai_pcm_dai_ops = {
+ .set_sysclk = fsl_sai_set_dai_sysclk,
+ .set_fmt = fsl_sai_set_dai_fmt,
+ .hw_params = fsl_sai_hw_params,
+ .trigger = fsl_sai_trigger,
+ .startup = fsl_sai_startup,
+ .shutdown = fsl_sai_shutdown,
+};
+
+static int fsl_sai_dai_probe(struct snd_soc_dai *cpu_dai)
+{
+ struct fsl_sai *sai = dev_get_drvdata(cpu_dai->dev);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, 0xffffffff, 0x0);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR, 0xffffffff, 0x0);
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR1, FSL_SAI_CR1_RFW_MASK,
+ FSL_SAI_MAXBURST_TX * 2);
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR1, FSL_SAI_CR1_RFW_MASK,
+ FSL_SAI_MAXBURST_RX - 1);
+
+ cpu_dai->playback_dma_data = &sai->dma_params_tx;
+ cpu_dai->capture_dma_data = &sai->dma_params_rx;
+
+ snd_soc_dai_set_drvdata(cpu_dai, sai);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_sai_dai = {
+ .probe = fsl_sai_dai_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_SAI_FORMATS,
+ },
+ .capture = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = FSL_SAI_FORMATS,
+ },
+ .ops = &fsl_sai_pcm_dai_ops,
+};
+
+static const struct snd_soc_component_driver fsl_component = {
+ .name = "fsl-sai",
+};
+
+static bool fsl_sai_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_SAI_TCSR:
+ case FSL_SAI_TCR1:
+ case FSL_SAI_TCR2:
+ case FSL_SAI_TCR3:
+ case FSL_SAI_TCR4:
+ case FSL_SAI_TCR5:
+ case FSL_SAI_TFR:
+ case FSL_SAI_TMR:
+ case FSL_SAI_RCSR:
+ case FSL_SAI_RCR1:
+ case FSL_SAI_RCR2:
+ case FSL_SAI_RCR3:
+ case FSL_SAI_RCR4:
+ case FSL_SAI_RCR5:
+ case FSL_SAI_RDR:
+ case FSL_SAI_RFR:
+ case FSL_SAI_RMR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool fsl_sai_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_SAI_TCSR:
+ case FSL_SAI_TFR:
+ case FSL_SAI_TDR:
+ case FSL_SAI_RCSR:
+ case FSL_SAI_RFR:
+ case FSL_SAI_RDR:
+ return true;
+ default:
+ return false;
+ }
+
+}
+
+static bool fsl_sai_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case FSL_SAI_TCSR:
+ case FSL_SAI_TCR1:
+ case FSL_SAI_TCR2:
+ case FSL_SAI_TCR3:
+ case FSL_SAI_TCR4:
+ case FSL_SAI_TCR5:
+ case FSL_SAI_TDR:
+ case FSL_SAI_TMR:
+ case FSL_SAI_RCSR:
+ case FSL_SAI_RCR1:
+ case FSL_SAI_RCR2:
+ case FSL_SAI_RCR3:
+ case FSL_SAI_RCR4:
+ case FSL_SAI_RCR5:
+ case FSL_SAI_RMR:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static struct regmap_config fsl_sai_regmap_config = {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+
+ .max_register = FSL_SAI_RMR,
+ .readable_reg = fsl_sai_readable_reg,
+ .volatile_reg = fsl_sai_volatile_reg,
+ .writeable_reg = fsl_sai_writeable_reg,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static bool fsl_sai_filter(struct dma_chan *chan, void *param)
+{
+ struct snd_dmaengine_dai_dma_data *dma_data = param;
+
+ chan->private = dma_data->filter_data;
+
+ return true;
+}
+
+static const struct snd_pcm_hardware fsl_sai_pcm_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 = FSL_SAI_DMABUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = 65535,
+ .periods_min = 2,
+ .periods_max = 255,
+ .fifo_size = 0,
+};
+
+static const struct snd_dmaengine_pcm_config fsl_sai_dmaengine_pcm_config = {
+ .pcm_hardware = &fsl_sai_pcm_hardware,
+ .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+ .compat_filter_fn = fsl_sai_filter,
+ .prealloc_buffer_size = FSL_SAI_DMABUF_SIZE,
+};
+
+static int fsl_sai_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_sai *sai;
+ struct resource *res;
+ void __iomem *base;
+ int ret;
+
+ sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+ if (!sai)
+ return -ENOMEM;
+
+ sai->big_endian_regs = of_property_read_bool(np, "big-endian-regs");
+ if (sai->big_endian_regs)
+ fsl_sai_regmap_config.val_format_endian = REGMAP_ENDIAN_BIG;
+
+ sai->big_endian_data = of_property_read_bool(np, "big-endian-data");
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+ "sai", base, &fsl_sai_regmap_config);
+ if (IS_ERR(sai->regmap)) {
+ dev_err(&pdev->dev, "regmap init failed\n");
+ return PTR_ERR(sai->regmap);
+ }
+
+ if (sai->big_endian_regs) {
+ sai->dma_params_rx.addr = res->start + FSL_SAI_RDR + 2;
+ sai->dma_params_tx.addr = res->start + FSL_SAI_TDR + 2;
+ } else {
+ sai->dma_params_rx.addr = res->start + FSL_SAI_RDR;
+ sai->dma_params_tx.addr = res->start + FSL_SAI_TDR;
+ }
+
+ sai->dma_params_rx.maxburst = FSL_SAI_MAXBURST_RX;
+ sai->dma_params_tx.maxburst = FSL_SAI_MAXBURST_TX;
+
+ platform_set_drvdata(pdev, sai);
+
+ ret = snd_soc_register_component(&pdev->dev, &fsl_component,
+ &fsl_sai_dai, 1);
+ if (ret)
+ return ret;
+
+ ret = snd_dmaengine_pcm_register(&pdev->dev,
+ &fsl_sai_dmaengine_pcm_config,
+ SND_DMAENGINE_PCM_FLAG_NO_RESIDUE);
+ if (ret)
+ snd_soc_unregister_component(&pdev->dev);
+
+ return ret;
+}
+
+static int fsl_sai_remove(struct platform_device *pdev)
+{
+ snd_dmaengine_pcm_unregister(&pdev->dev);
+ snd_soc_unregister_component(&pdev->dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_sai_suspend(struct device *dev)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+
+ regcache_cache_only(sai->regmap, true);
+ regcache_mark_dirty(sai->regmap);
+
+ return 0;
+}
+
+static int fsl_sai_resume(struct device *dev)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+
+ /* Restore all registers */
+ regcache_cache_only(sai->regmap, false);
+ regcache_sync(sai->regmap);
+
+ return 0;
+};
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_sai_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_sai_suspend, fsl_sai_resume)
+};
+
+static const struct of_device_id fsl_sai_ids[] = {
+ { .compatible = "fsl,vf610-sai", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver fsl_sai_driver = {
+ .probe = fsl_sai_probe,
+ .remove = fsl_sai_remove,
+ .driver = {
+ .name = "fsl-sai",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_sai_ids,
+ .pm = &fsl_sai_pm,
+ },
+};
+module_platform_driver(fsl_sai_driver);
+
+MODULE_DESCRIPTION("Freescale Soc SAI Interface");
+MODULE_AUTHOR("Xiubo Li, <Li.Xiubo@freescale.com>");
+MODULE_ALIAS("platform:fsl-sai");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h
new file mode 100644
index 0000000..fc12d96
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012-2013 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 __FSL_SAI_H
+#define __FSL_SAI_H
+
+#include <sound/dmaengine_pcm.h>
+
+#define FSL_SAI_DMABUF_SIZE (64 * 1024)
+
+#define FSL_SAI_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+ SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+/* SAI Register Map Register */
+#define FSL_SAI_TCSR 0x00 /* SAI Transmit Control */
+#define FSL_SAI_TCR1 0x04 /* SAI Transmit Configuration 1 */
+#define FSL_SAI_TCR2 0x08 /* SAI Transmit Configuration 2 */
+#define FSL_SAI_TCR3 0x0c /* SAI Transmit Configuration 3 */
+#define FSL_SAI_TCR4 0x10 /* SAI Transmit Configuration 4 */
+#define FSL_SAI_TCR5 0x14 /* SAI Transmit Configuration 5 */
+#define FSL_SAI_TDR 0x20 /* SAI Transmit Data */
+#define FSL_SAI_TFR 0x40 /* SAI Transmit FIFO */
+#define FSL_SAI_TMR 0x60 /* SAI Transmit Mask */
+#define FSL_SAI_RCSR 0x80 /* SAI Receive Control */
+#define FSL_SAI_RCR1 0x84 /* SAI Receive Configuration 1 */
+#define FSL_SAI_RCR2 0x88 /* SAI Receive Configuration 2 */
+#define FSL_SAI_RCR3 0x8c /* SAI Receive Configuration 3 */
+#define FSL_SAI_RCR4 0x90 /* SAI Receive Configuration 4 */
+#define FSL_SAI_RCR5 0x94 /* SAI Receive Configuration 5 */
+#define FSL_SAI_RDR 0xa0 /* SAI Receive Data */
+#define FSL_SAI_RFR 0xc0 /* SAI Receive FIFO */
+#define FSL_SAI_RMR 0xe0 /* SAI Receive Mask */
+
+/* SAI Transmit/Recieve Control Register */
+#define FSL_SAI_CSR_TERE BIT(31)
+#define FSL_SAI_CSR_FWF BIT(17)
+#define FSL_SAI_CSR_FRIE BIT(8)
+#define FSL_SAI_CSR_FRDE BIT(0)
+
+/* SAI Transmit and Recieve Configuration 1 Register */
+#define FSL_SAI_CR1_RFW_MASK 0x1f
+
+/* SAI Transmit and Recieve Configuration 2 Register */
+#define FSL_SAI_CR2_SYNC BIT(30)
+#define FSL_SAI_CR2_MSEL_MASK (0xff << 26)
+#define FSL_SAI_CR2_MSEL_BUS 0
+#define FSL_SAI_CR2_MSEL_MCLK1 BIT(26)
+#define FSL_SAI_CR2_MSEL_MCLK2 BIT(27)
+#define FSL_SAI_CR2_MSEL_MCLK3 (BIT(26) | BIT(27))
+#define FSL_SAI_CR2_BCP BIT(25)
+#define FSL_SAI_CR2_BCD_MSTR BIT(24)
+
+/* SAI Transmit and Recieve Configuration 3 Register */
+#define FSL_SAI_CR3_TRCE BIT(16)
+#define FSL_SAI_CR3_WDFL(x) (x)
+#define FSL_SAI_CR3_WDFL_MASK 0x1f
+
+/* SAI Transmit and Recieve Configuration 4 Register */
+#define FSL_SAI_CR4_FRSZ(x) (((x) - 1) << 16)
+#define FSL_SAI_CR4_FRSZ_MASK (0x1f << 16)
+#define FSL_SAI_CR4_SYWD(x) (((x) - 1) << 8)
+#define FSL_SAI_CR4_SYWD_MASK (0x1f << 8)
+#define FSL_SAI_CR4_MF BIT(4)
+#define FSL_SAI_CR4_FSE BIT(3)
+#define FSL_SAI_CR4_FSP BIT(1)
+#define FSL_SAI_CR4_FSD_MSTR BIT(0)
+
+/* SAI Transmit and Recieve Configuration 5 Register */
+#define FSL_SAI_CR5_WNW(x) (((x) - 1) << 24)
+#define FSL_SAI_CR5_WNW_MASK (0x1f << 24)
+#define FSL_SAI_CR5_W0W(x) (((x) - 1) << 16)
+#define FSL_SAI_CR5_W0W_MASK (0x1f << 16)
+#define FSL_SAI_CR5_FBT(x) ((x) << 8)
+#define FSL_SAI_CR5_FBT_MASK (0x1f << 8)
+
+/* SAI type */
+#define FSL_SAI_DMA BIT(0)
+#define FSL_SAI_USE_AC97 BIT(1)
+#define FSL_SAI_NET BIT(2)
+#define FSL_SAI_TRA_SYN BIT(3)
+#define FSL_SAI_REC_SYN BIT(4)
+#define FSL_SAI_USE_I2S_SLAVE BIT(5)
+
+#define FSL_FMT_TRANSMITTER 0
+#define FSL_FMT_RECEIVER 1
+
+/* SAI clock sources */
+#define FSL_SAI_CLK_BUS 0
+#define FSL_SAI_CLK_MAST1 1
+#define FSL_SAI_CLK_MAST2 2
+#define FSL_SAI_CLK_MAST3 3
+
+/* SAI data transfer numbers per DMA request */
+#define FSL_SAI_MAXBURST_TX 6
+#define FSL_SAI_MAXBURST_RX 6
+
+struct fsl_sai {
+ struct regmap *regmap;
+
+ bool big_endian_regs;
+ bool big_endian_data;
+ bool is_dsp_mode;
+
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+};
+
+#endif /* __FSL_SAI_H */
diff --git a/sound/soc/fsl/vf610-sgtl5000.c b/sound/soc/fsl/vf610-sgtl5000.c
new file mode 100644
index 0000000..d53d554
--- /dev/null
+++ b/sound/soc/fsl/vf610-sgtl5000.c
@@ -0,0 +1,169 @@
+/*
+ * Freescale ALSA SoC Audio using SGTL5000 as codec.
+ *
+ * Copyright 2012-2014 Freescale Semiconductor, Inc.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+
+#include "../codecs/sgtl5000.h"
+#include "fsl_sai.h"
+
+static unsigned int sysclk_rate;
+
+static int vf610_sgtl5000_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ int ret;
+ struct device *dev = rtd->card->dev;
+
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, SGTL5000_SYSCLK,
+ sysclk_rate, SND_SOC_CLOCK_IN);
+ if (ret) {
+ dev_err(dev, "could not set codec driver clock params :%d\n",
+ ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, FSL_SAI_CLK_BUS,
+ sysclk_rate, SND_SOC_CLOCK_OUT);
+ if (ret) {
+ dev_err(dev, "could not set cpu dai driver clock params :%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct snd_soc_dai_link vf610_sgtl5000_dai = {
+ .name = "HiFi",
+ .stream_name = "HiFi",
+ .codec_dai_name = "sgtl5000",
+ .init = &vf610_sgtl5000_dai_init,
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBM_CFM,
+};
+
+static const struct snd_soc_dapm_widget vf610_sgtl5000_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC("Microphone Jack", NULL),
+ SND_SOC_DAPM_LINE("Line In Jack", NULL),
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker Ext", NULL),
+};
+
+static struct snd_soc_card vf610_sgtl5000_card = {
+ .owner = THIS_MODULE,
+ .num_links = 1,
+ .dai_link = &vf610_sgtl5000_dai,
+ .dapm_widgets = vf610_sgtl5000_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(vf610_sgtl5000_dapm_widgets),
+};
+
+static int vf610_sgtl5000_parse_dt(struct platform_device *pdev)
+{
+ int ret;
+ struct device_node *sai_np, *codec_np;
+ struct clk *codec_clk;
+ struct i2c_client *codec_dev;
+ struct device_node *np = pdev->dev.of_node;
+
+ ret = snd_soc_of_parse_card_name(&vf610_sgtl5000_card,
+ "simple-audio-card,name");
+ if (ret)
+ return ret;
+
+ ret = snd_soc_of_parse_audio_routing(&vf610_sgtl5000_card,
+ "simple-audio-card,routing");
+ if (ret)
+ return ret;
+
+ sai_np = of_parse_phandle(np, "simple-audio-card,cpu", 0);
+ if (!sai_np) {
+ dev_err(&pdev->dev, "parsing \"saif-controller\" error\n");
+ return -EINVAL;
+ }
+ vf610_sgtl5000_dai.cpu_of_node = sai_np;
+ vf610_sgtl5000_dai.platform_of_node = sai_np;
+
+ codec_np = of_parse_phandle(np, "simple-audio-card,codec", 0);
+ if (!codec_np) {
+ dev_err(&pdev->dev, "parsing \"audio-codec\" error\n");
+ ret = -EINVAL;
+ goto sai_np_fail;
+ }
+ vf610_sgtl5000_dai.codec_of_node = codec_np;
+
+ codec_dev = of_find_i2c_device_by_node(codec_np);
+ if (!codec_dev) {
+ dev_err(&pdev->dev, "failed to find codec platform device\n");
+ ret = PTR_ERR(codec_dev);
+ goto codec_np_fail;
+ }
+
+ codec_clk = devm_clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(codec_clk)) {
+ dev_err(&pdev->dev, "failed to get codec clock\n");
+ ret = PTR_ERR(codec_clk);
+ goto codec_np_fail;
+ }
+
+ sysclk_rate = clk_get_rate(codec_clk);
+
+codec_np_fail:
+ of_node_put(codec_np);
+sai_np_fail:
+ of_node_put(sai_np);
+
+ return ret;
+}
+
+static int vf610_sgtl5000_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ vf610_sgtl5000_card.dev = &pdev->dev;
+
+ ret = vf610_sgtl5000_parse_dt(pdev);
+ if (ret) {
+ dev_err(&pdev->dev, "parse sgtl5000 device tree failed :%d\n",
+ ret);
+ return ret;
+ }
+
+ ret = snd_soc_register_card(&vf610_sgtl5000_card);
+ if (ret) {
+ dev_err(&pdev->dev, "TWR-AUDIO-SGTL board required :%d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id vf610_sgtl5000_dt_ids[] = {
+ { .compatible = "fsl,vf610-sgtl5000", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, vf610_sgtl5000_dt_ids);
+
+static struct platform_driver vf610_sgtl5000_driver = {
+ .probe = vf610_sgtl5000_probe,
+ .driver = {
+ .name = "vf610-sgtl5000",
+ .owner = THIS_MODULE,
+ .of_match_table = vf610_sgtl5000_dt_ids,
+ },
+};
+module_platform_driver(vf610_sgtl5000_driver);
+
+MODULE_DESCRIPTION("Freescale SGTL5000 ASoC driver");
+MODULE_LICENSE("GPL");