summaryrefslogtreecommitdiff
path: root/sound/soc/fsl
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/fsl')
-rw-r--r--sound/soc/fsl/Kconfig9
-rw-r--r--sound/soc/fsl/Makefile3
-rw-r--r--sound/soc/fsl/fsl_sai.h6
-rw-r--r--sound/soc/fsl/fsl_sai_ac97.c1353
-rw-r--r--sound/soc/fsl/fsl_sai_clk.c260
-rw-r--r--sound/soc/fsl/fsl_sai_wm9712.c130
6 files changed, 1759 insertions, 2 deletions
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 14dfdee05fd5..7d60d5b03f63 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -15,6 +15,7 @@ config SND_SOC_FSL_ASRC
config SND_SOC_FSL_SAI
tristate "Synchronous Audio Interface (SAI) module support"
select REGMAP_MMIO
+ select SND_SOC_AC97_BUS
select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
select SND_SOC_GENERIC_DMAENGINE_PCM
help
@@ -217,6 +218,14 @@ config SND_SOC_PHYCORE_AC97
Say Y if you want to add support for SoC audio on Phytec phyCORE
and phyCARD boards in AC97 mode
+config SND_SOC_FSL_SAI_WM9712
+ tristate "SoC Audio support for Freescale SoC's using SAI and WM9712 codec"
+ select SND_SOC_FSL_SAI
+ select SND_SOC_WM9712
+ help
+ Say Y or M here if you want to add support for SoC audio on Freescale
+ SoC using SAI and the WM9712 (or compatible) codec.
+
config SND_SOC_EUKREA_TLV320
tristate "Eukrea TLV320"
depends on ARCH_MXC && I2C
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index d28dc25c9375..43f5da761675 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -13,7 +13,7 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
# Freescale SSI/DMA/SAI/SPDIF Support
snd-soc-fsl-asoc-card-objs := fsl-asoc-card.o
snd-soc-fsl-asrc-objs := fsl_asrc.o fsl_asrc_dma.o
-snd-soc-fsl-sai-objs := fsl_sai.o
+snd-soc-fsl-sai-objs := fsl_sai.o fsl_sai_clk.o fsl_sai_ac97.o
snd-soc-fsl-ssi-y := fsl_ssi.o
snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o
snd-soc-fsl-spdif-objs := fsl_spdif.o
@@ -60,6 +60,7 @@ snd-soc-imx-mc13783-objs := imx-mc13783.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
+obj-$(CONFIG_SND_SOC_FSL_SAI_WM9712) += fsl_sai_wm9712.o
obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
diff --git a/sound/soc/fsl/fsl_sai.h b/sound/soc/fsl/fsl_sai.h
index b95fbc3f68eb..8ca899dfca77 100644
--- a/sound/soc/fsl/fsl_sai.h
+++ b/sound/soc/fsl/fsl_sai.h
@@ -48,6 +48,7 @@
/* SAI Transmit/Receive Control Register */
#define FSL_SAI_CSR_TERE BIT(31)
+#define FSL_SAI_CSR_BCE BIT(28)
#define FSL_SAI_CSR_FR BIT(25)
#define FSL_SAI_CSR_SR BIT(24)
#define FSL_SAI_CSR_xF_SHIFT 16
@@ -72,7 +73,9 @@
#define FSL_SAI_CR1_RFW_MASK 0x1f
/* SAI Transmit and Receive Configuration 2 Register */
+#define FSL_SAI_CR2_SYNC_MASK (0x3 << 30)
#define FSL_SAI_CR2_SYNC BIT(30)
+#define FSL_SAI_CR2_BCS BIT(29)
#define FSL_SAI_CR2_MSEL_MASK (0x3 << 26)
#define FSL_SAI_CR2_MSEL_BUS 0
#define FSL_SAI_CR2_MSEL_MCLK1 BIT(26)
@@ -82,6 +85,7 @@
#define FSL_SAI_CR2_BCP BIT(25)
#define FSL_SAI_CR2_BCD_MSTR BIT(24)
#define FSL_SAI_CR2_DIV_MASK 0xff
+#define FSL_SAI_CR2_DIV(x) ((x) & 0xff)
/* SAI Transmit and Receive Configuration 3 Register */
#define FSL_SAI_CR3_TRCE BIT(16)
@@ -103,7 +107,7 @@
#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(x) ((x - 1) << 8)
#define FSL_SAI_CR5_FBT_MASK (0x1f << 8)
/* SAI type */
diff --git a/sound/soc/fsl/fsl_sai_ac97.c b/sound/soc/fsl/fsl_sai_ac97.c
new file mode 100644
index 000000000000..3bd483256c62
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai_ac97.c
@@ -0,0 +1,1353 @@
+/*
+ * Freescale ALSA SoC Digital Audio Interface (SAI) AC97 driver.
+ *
+ * Copyright (C) 2013-2015 Toradex, Inc.
+ * Authors: Stefan Agner, Marcel Ziswiler
+ *
+ * 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/dma-mapping.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm_params.h>
+#include <linux/pinctrl/consumer.h>
+
+#include <sound/ac97_codec.h>
+#include <sound/initval.h>
+
+#include "fsl_sai.h"
+#include "imx-pcm.h"
+
+struct imx_pcm_runtime_data {
+ unsigned int period;
+ int periods;
+ unsigned long offset;
+ struct snd_pcm_substream *substream;
+};
+
+#define EDMA_PRIO_HIGH 6
+#define SAI_AC97_DMABUF_SIZE (13 * 4)
+#define SAI_AC97_RBUF_COUNT (4)
+#define SAI_AC97_RBUF_FRAMES (1024)
+#define SAI_AC97_RBUF_SIZE (SAI_AC97_RBUF_FRAMES * SAI_AC97_DMABUF_SIZE)
+#define SAI_AC97_RBUF_SIZE_TOT (SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_SIZE)
+
+static struct fsl_sai_ac97 *info;
+
+struct fsl_sai_ac97 {
+ struct platform_device *pdev;
+
+ resource_size_t mapbase;
+
+ struct regmap *regmap;
+ struct clk *bus_clk;
+ struct clk *mclk_clk[FSL_SAI_MCLK_MAX];
+
+ struct dma_chan *dma_tx_chan;
+ struct dma_chan *dma_rx_chan;
+ struct dma_async_tx_descriptor *dma_tx_desc;
+ struct dma_async_tx_descriptor *dma_rx_desc;
+
+ dma_cookie_t dma_tx_cookie;
+ dma_cookie_t dma_rx_cookie;
+
+ struct snd_dma_buffer rx_buf;
+ struct snd_dma_buffer tx_buf;
+
+ bool big_endian_regs;
+ bool big_endian_data;
+ bool is_dsp_mode;
+ bool sai_on_imx;
+
+ struct snd_dmaengine_dai_dma_data dma_params_rx;
+ struct snd_dmaengine_dai_dma_data dma_params_tx;
+
+
+ struct snd_card *card;
+
+ struct mutex lock;
+
+ int cmdbufid;
+ unsigned short reg;
+ unsigned short val;
+
+ struct snd_soc_platform platform;
+
+ atomic_t playing;
+ atomic_t capturing;
+
+ struct imx_pcm_runtime_data *iprtd_playback;
+ struct imx_pcm_runtime_data *iprtd_capture;
+};
+
+#define FSL_SAI_FLAGS (FSL_SAI_CSR_SEIE |\
+ FSL_SAI_CSR_FEIE)
+
+
+struct ac97_tx {
+ /*
+ * Slot 0: TAG
+ * Bit 15 Codec Ready
+ * Bit 14:3 Slot Valid (Which of slot 1 to slot 12 contain valid data)
+ * Bit 2 Zero
+ * Bit 1:0 Codec ID
+ */
+ unsigned int reserved_2:4; /* Align the 16-bit Tag to 20-bit */
+ unsigned int codec_id:2;
+ unsigned int reserved_1:1;
+ unsigned int slot_valid:12;
+ unsigned int valid:1;
+ unsigned int align_32_0:12;
+
+ /*
+ * Slot 1: Command Address Port
+ * Bit(19) Read/Write command (1=read, 0=write)
+ * Bit(18:12) Control Register Index (64 16-bit locations,
+ * addressed on even byte boundaries)
+ * Bit(11:0) Reserved (Stuffed with 0’s)
+ */
+ unsigned int reserved_3:12;
+ unsigned int cmdindex:7;
+ unsigned int cmdread:1;
+ unsigned int align_32_1:12;
+
+
+ /*
+ * Slot 2: Command Data Port
+ * The command data port is used to deliver 16-bit
+ * control register write data in the event that
+ * the current command port operation is a write
+ * cycle. (as indicated by Slot 1, bit 19)
+ * Bit(19:4) Control Register Write Data (Completed
+ * with 0’s if current operation is a read)
+ * Bit(3:0) Reserved (Completed with 0’s)
+ */
+ unsigned int reserved_4:4;
+ unsigned int cmddata:16;
+ unsigned int align_32_2:12;
+
+ unsigned int slots_data[10];
+} __attribute__((__packed__));
+
+struct ac97_rx {
+ /*
+ * Slot 0: TAG
+ * Bit 15 Codec Ready
+ * Bit 14:3 Slot Valid (Which of slot 1 to slot 12 contain valid data)
+ * Bit 2:0 Zero
+ */
+ unsigned int reserved_2:4; /* Align the 16-bit Tag to 20-bit */
+ unsigned int reserved_1:3;
+ unsigned int slot_valid:12;
+ unsigned int valid:1;
+ unsigned int align_32_0:12;
+
+ /*
+ * Slot 1: Status Address
+ */
+ unsigned int reserved_4:2;
+ unsigned int slot_req:10;
+ unsigned int regindex:7;
+ unsigned int reserved_3:1;
+ unsigned int align_32_1:12;
+
+ /*
+ * Slot 2: Status Data
+ * Bit 19:4 Control Register Read Data (Completed with 0’s if tagged
+ * “invalid” by AC‘97)
+ * Bit 3:0 RESERVED (Completed with 0’s)
+ */
+ unsigned int reserved_5:4;
+ unsigned int cmddata:16;
+ unsigned int align_32_2:12;
+
+ unsigned int slots_data[10];
+} __attribute__((__packed__));
+
+static void fsl_dma_tx_complete(void *arg)
+{
+ struct fsl_sai_ac97 *sai = arg;
+ struct ac97_tx *aclink;
+ struct imx_pcm_runtime_data *iprtd = sai->iprtd_playback;
+ int i = 0;
+ struct dma_tx_state state;
+ enum dma_status status;
+ int bufid;
+
+ async_tx_ack(sai->dma_tx_desc);
+
+ status = dmaengine_tx_status(sai->dma_tx_chan, sai->dma_tx_cookie, &state);
+
+ /* Calculate the id of the running buffer */
+ if (state.residue % SAI_AC97_RBUF_SIZE == 0)
+ bufid = 4 - (state.residue / SAI_AC97_RBUF_SIZE);
+ else
+ bufid = 3 - (state.residue / SAI_AC97_RBUF_SIZE);
+
+ /* Calculate the id of the next free buffer */
+ bufid = (bufid + 1) % 4;
+
+ /* First frame of the just completed buffer... */
+ aclink = (struct ac97_tx *)(sai->tx_buf.area + (bufid * SAI_AC97_RBUF_SIZE));
+
+ if (atomic_read(&info->playing))
+ {
+ struct snd_dma_buffer *buf = &iprtd->substream->dma_buffer;
+ u16 *ptr = (u16 *)(buf->area + iprtd->offset);
+
+ /* Copy samples of the PCM stream into PCM slots 3/4 */
+ for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++) {
+
+ aclink->valid = 1;
+ aclink->slot_valid |= (1 << 9 | 1 << 8);
+ aclink->slots_data[0] = ptr[i * 2];
+ aclink->slots_data[0] <<= 4;
+ aclink->slots_data[1] = ptr[i * 2 + 1];
+ aclink->slots_data[1] <<= 4;
+ aclink++;
+ }
+
+ iprtd->offset += SAI_AC97_RBUF_FRAMES * 4;
+ iprtd->offset %= (SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT);
+ snd_pcm_period_elapsed(iprtd->substream);
+ }
+ else if (aclink->slot_valid & (1 << 9 | 1 << 8))
+ {
+ /* There is nothing playing anymore, clean the samples */
+ for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++) {
+ aclink->valid = 0;
+ aclink->slot_valid &= ~(1 << 9 | 1 << 8);
+ aclink->slots_data[0] = 0;
+ aclink->slots_data[1] = 0;
+ aclink++;
+ }
+ }
+}
+
+static void fsl_dma_rx_complete(void *arg)
+{
+ struct fsl_sai_ac97 *sai = arg;
+ struct ac97_rx *aclink;
+ struct imx_pcm_runtime_data *iprtd = sai->iprtd_capture;
+ struct dma_tx_state state;
+ enum dma_status status;
+ int bufid;
+ int i;
+
+ async_tx_ack(sai->dma_rx_desc);
+
+ status = dmaengine_tx_status(sai->dma_rx_chan, sai->dma_rx_cookie, &state);
+
+ /* Calculate the id of the running buffer */
+ if (state.residue % SAI_AC97_RBUF_SIZE == 0)
+ bufid = 4 - (state.residue / SAI_AC97_RBUF_SIZE);
+ else
+ bufid = 3 - (state.residue / SAI_AC97_RBUF_SIZE);
+
+ /* Calculate the id of the last processed buffer */
+ bufid = (bufid + 3) % 4;
+
+ /* First frame of the just completed buffer... */
+ aclink = (struct ac97_rx *)(sai->rx_buf.area + (bufid * SAI_AC97_RBUF_SIZE));
+
+ if (atomic_read(&info->capturing))
+ {
+ struct snd_dma_buffer *buf = &iprtd->substream->dma_buffer;
+ u16 *ptr = (u16 *)buf->area;
+
+ /*
+ * Loop through all AC97 frames, but only some might have data:
+ * Depending on bit rate, the valid flag might not be set for
+ * all frames (see AC97 VBR specification)
+ */
+ for (i = 0; i < SAI_AC97_RBUF_FRAMES; i++, aclink++) {
+ if (!aclink->valid)
+ continue;
+
+ if (aclink->slot_valid & (1 << 9)) {
+ ptr[iprtd->offset / 2] = aclink->slots_data[0] >> 4;
+ iprtd->offset+=2;
+ }
+
+ if (aclink->slot_valid & (1 << 8)) {
+ ptr[iprtd->offset / 2] = aclink->slots_data[1] >> 4;
+ iprtd->offset+=2;
+ }
+
+ iprtd->offset %= (SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT);
+ }
+
+ snd_pcm_period_elapsed(iprtd->substream);
+ }
+}
+
+static int vf610_sai_ac97_read_write(struct snd_ac97 *ac97, bool isread,
+ unsigned short reg, unsigned short *val)
+{
+ enum dma_status rx_status;
+ enum dma_status tx_status;
+ struct dma_tx_state tx_state;
+ struct dma_tx_state rx_state;
+ struct ac97_tx *tx_aclink;
+ struct ac97_rx *rx_aclink;
+ int rxbufidstart, txbufidstart, txbufid, rxbufid, curbufid;
+ unsigned long flags;
+ int ret = 0;
+ int rxbufmaxcheck = 10;
+ int timeout = 10;
+
+ /*
+ * We need to disable interrupts to make sure we insert the message
+ * before the next AC97 frame has been sent
+ */
+ local_irq_save(flags);
+ tx_status = dmaengine_tx_status(info->dma_tx_chan, info->dma_tx_cookie,
+ &tx_state);
+ rx_status = dmaengine_tx_status(info->dma_rx_chan, info->dma_rx_cookie,
+ &rx_state);
+
+ /* Calculate next DMA buffer sent out to the AC97 codec */
+ rxbufidstart = (SAI_AC97_RBUF_SIZE_TOT - rx_state.residue) / SAI_AC97_DMABUF_SIZE;
+ rxbufidstart %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES;
+ txbufidstart = (SAI_AC97_RBUF_SIZE_TOT - tx_state.residue) / SAI_AC97_DMABUF_SIZE;
+ txbufidstart %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES;
+
+ /* Safety margin, use next buffer in case current buffer is DMA'ed now */
+ txbufid = txbufidstart + 1;
+ txbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES;
+ tx_aclink = (struct ac97_tx *)(info->tx_buf.area + (txbufid * SAI_AC97_DMABUF_SIZE));
+
+ /* Put our request into the next AC97 frame */
+ tx_aclink->valid = 1;
+ tx_aclink->slot_valid |= (1 << 11);
+
+ tx_aclink->cmdread = isread;
+ tx_aclink->cmdindex = reg;
+
+ if (!isread) {
+ tx_aclink->slot_valid |= (1 << 10);
+ tx_aclink->cmddata = *val;
+ }
+
+ local_irq_restore(flags);
+
+ /* Wait at least until TX frame is in FIFO... */
+ if (!isread) {
+ do {
+ usleep_range(50, 200);
+ tx_status = dmaengine_tx_status(info->dma_tx_chan, info->dma_tx_cookie,
+ &tx_state);
+ curbufid = ((SAI_AC97_RBUF_SIZE_TOT - tx_state.residue) / SAI_AC97_DMABUF_SIZE);
+
+ if (likely(txbufid > txbufidstart) &&
+ (curbufid > txbufid || curbufid < txbufidstart))
+ break;
+
+ /* Wrap-around case */
+ if (unlikely(txbufid < txbufidstart) &&
+ (curbufid > txbufid && curbufid < txbufidstart))
+ break;
+ } while (--timeout);
+ ret = !timeout ? -ETIMEDOUT : 0;
+ goto clear_command;
+ }
+
+ /*
+ * Look into every frame starting at the RX frame which was
+ * last copied by DMA at command insert time. Typically, the
+ * answer is in RX start frame +4. Factors which sum up to
+ * this delay are:
+ * - TX send delay (+1 safety margin, +2 TX FIFO)
+ * - AC97 codec sends back the answer in the next frame (+1)
+ *
+ * TX ring buffer
+ * |------|------|------|------|------|------|------|------|
+ * | | | |txbuf |txbuf | | | |
+ * | | | |start | | | | |
+ * |------|------|------|------|------|------|------|------|
+ *
+ * RX ring buffer
+ * |------|------|------|------|------|------|------|------|
+ * | |rxbuf | | | |rxbuf | | |
+ * | |start | | | | | | |
+ * |------|------|------|------|------|------|------|------|
+ *
+ */
+ rxbufid = rxbufidstart;
+ curbufid = rxbufid;
+ do {
+ while (rxbufid == curbufid && --timeout)
+ {
+ /* Wait for frames being transmitted/received... */
+ usleep_range(50, 200);
+ rx_status = dmaengine_tx_status(info->dma_rx_chan, info->dma_rx_cookie,
+ &rx_state);
+ curbufid = ((SAI_AC97_RBUF_SIZE_TOT - rx_state.residue) / SAI_AC97_DMABUF_SIZE);
+ }
+
+ if (!timeout) {
+ ret = -ETIMEDOUT;
+ goto clear_command;
+ }
+
+ /* Ok, check frames... */
+ rx_aclink = (struct ac97_rx *)(info->rx_buf.area + rxbufid * SAI_AC97_DMABUF_SIZE);
+ if (rx_aclink->slot_valid & (1 << 11 | 1 << 10) &&
+ rx_aclink->regindex == reg)
+ {
+ *val = rx_aclink->cmddata;
+ break;
+ }
+
+ rxbufmaxcheck--;
+ rxbufid++;
+ rxbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES;
+ } while (rxbufmaxcheck);
+
+ if (!rxbufmaxcheck) {
+ pr_err("%s: rx timeout, checked buffer %d to %d, current %d\n",
+ __func__, rxbufidstart, rxbufid, curbufid);
+ ret = -ETIMEDOUT;
+ }
+
+clear_command:
+ /* Clear sent command... */
+ tx_aclink->slot_valid &= ~(1 << 11 | 1 << 10);
+ tx_aclink->cmdread = 0;
+ tx_aclink->cmdindex = 0;
+ tx_aclink->cmddata = 0;
+
+ return ret;
+}
+
+static unsigned short vf610_sai_ac97_read(struct snd_ac97 *ac97,
+ unsigned short reg)
+{
+ unsigned short val = 0;
+ int err;
+
+ err = vf610_sai_ac97_read_write(ac97, true, reg, &val);
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ if (err)
+ pr_err("failed to read register 0x%02x\n", reg);
+
+ return val;
+}
+
+static void vf610_sai_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
+ unsigned short val)
+{
+ int err;
+
+ err = vf610_sai_ac97_read_write(ac97, false, reg, &val);
+ pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+
+ if (err)
+ pr_err("failed to write register 0x%02x\n", reg);
+}
+
+
+static struct snd_ac97_bus_ops fsl_sai_ac97_ops = {
+ .read = vf610_sai_ac97_read,
+ .write = vf610_sai_ac97_write,
+};
+
+static int fsl_sai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ pr_debug("%s, %d\n", __func__, substream->stream);
+
+ return 0;
+}
+
+static void fsl_sai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ pr_debug("%s, %d\n", __func__, substream->stream);
+}
+
+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_ac97 *sai = dev_get_drvdata(cpu_dai->dev);
+
+ snd_soc_dai_set_drvdata(cpu_dai, sai);
+
+ /*
+ * Mark DAI as active since we use it for AC97 control messages,
+ * otherwise snd_soc_register_card would request pinctrl state
+ * "sleep"...
+ */
+ cpu_dai->active++;
+
+ return 0;
+}
+
+static int fsl_sai_dai_remove(struct snd_soc_dai *cpu_dai)
+{
+ cpu_dai->active--;
+
+ return 0;
+}
+static struct snd_soc_dai_driver fsl_sai_ac97_dai = {
+ .name = "fsl-sai-ac97-pcm",
+ .bus_control = true,
+ .probe = fsl_sai_dai_probe,
+ .remove = fsl_sai_dai_remove,
+ .playback = {
+ .stream_name = "PCM Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .capture = {
+ .stream_name = "PCM Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .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_TFR:
+ case FSL_SAI_RFR:
+ case FSL_SAI_TDR:
+ case FSL_SAI_RDR:
+ return true;
+ default:
+ return false;
+ }
+
+}
+
+static bool fsl_sai_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ 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,
+ .precious_reg = fsl_sai_precious_reg,
+ .readable_reg = fsl_sai_readable_reg,
+ .volatile_reg = fsl_sai_volatile_reg,
+ .writeable_reg = fsl_sai_writeable_reg,
+ .cache_type = REGCACHE_FLAT,
+};
+
+static struct snd_pcm_hardware snd_sai_ac97_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,
+ .buffer_bytes_max = SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT,
+ .period_bytes_min = SAI_AC97_RBUF_FRAMES * 4,
+ .period_bytes_max = SAI_AC97_RBUF_FRAMES * 4,
+ .periods_min = SAI_AC97_RBUF_COUNT,
+ .periods_max = SAI_AC97_RBUF_COUNT,
+ .fifo_size = 0,
+};
+
+static int snd_fsl_sai_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ iprtd->periods = params_periods(params);
+ iprtd->period = params_period_bytes(params);
+ iprtd->offset = 0;
+
+ pr_debug("%s: period %d, periods %d\n", __func__,
+ iprtd->period, iprtd->periods);
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+
+ return 0;
+}
+
+static int snd_fsl_sai_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ pr_debug("%s:, %p, cmd %d\n", __func__, substream, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ atomic_set(&info->playing, 1);
+ else
+ atomic_set(&info->capturing, 1);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ atomic_set(&info->playing, 0);
+ else
+ atomic_set(&info->capturing, 0);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_fsl_sai_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ return bytes_to_frames(substream->runtime, iprtd->offset);
+}
+
+static int snd_fsl_sai_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int ret = 0;
+
+ ret = dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
+
+ return ret;
+}
+
+static int snd_fsl_sai_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ pr_debug("%s, %p\n", __func__, substream);
+ return 0;
+}
+
+static int snd_fsl_sai_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd;
+ int ret;
+
+ iprtd = kzalloc(sizeof(*iprtd), GFP_KERNEL);
+
+ if (iprtd == NULL)
+ return -ENOMEM;
+
+ runtime->private_data = iprtd;
+ iprtd->substream = substream;
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ kfree(iprtd);
+ return ret;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ atomic_set(&info->playing, 0);
+ info->iprtd_playback = iprtd;
+ } else {
+ atomic_set(&info->capturing, 0);
+ info->iprtd_capture = iprtd;
+ }
+
+ snd_soc_set_runtime_hwparams(substream, &snd_sai_ac97_hardware);
+
+ return 0;
+}
+
+static int snd_fsl_sai_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ info->iprtd_playback = NULL;
+ else
+ info->iprtd_capture = NULL;
+
+ kfree(iprtd);
+
+ return 0;
+}
+
+static struct snd_pcm_ops fsl_sai_pcm_ops = {
+ .open = snd_fsl_sai_open,
+ .close = snd_fsl_sai_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_fsl_sai_pcm_hw_params,
+ .prepare = snd_fsl_sai_pcm_prepare,
+ .trigger = snd_fsl_sai_pcm_trigger,
+ .pointer = snd_fsl_sai_pcm_pointer,
+ .mmap = snd_fsl_sai_pcm_mmap,
+};
+
+static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+ /* Allocate for buffers, 16-Bit stereo data.. */
+ size_t size = SAI_AC97_RBUF_FRAMES * 4 * SAI_AC97_RBUF_COUNT;
+
+ 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 int fsl_sai_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_card *card = rtd->card->snd_card;
+ int ret;
+
+ ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ return ret;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = imx_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ return ret;
+ }
+
+ pr_debug("%s, %p\n", __func__, pcm);
+
+ return 0;
+}
+
+static void fsl_sai_pcm_free(struct snd_pcm *pcm)
+{
+ pr_debug("%s, %p\n", __func__, pcm);
+}
+
+static struct snd_soc_platform_driver ac97_software_pcm_platform = {
+ .ops = &fsl_sai_pcm_ops,
+ .pcm_new = fsl_sai_pcm_new,
+ .pcm_free = fsl_sai_pcm_free,
+};
+
+
+static int fsl_sai_ac97_prepare_tx_dma(struct fsl_sai_ac97 *sai)
+{
+ struct dma_slave_config dma_tx_sconfig;
+ int ret;
+
+ dma_tx_sconfig.dst_addr = sai->mapbase + FSL_SAI_TDR;
+ dma_tx_sconfig.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dma_tx_sconfig.dst_maxburst = 13;
+ dma_tx_sconfig.direction = DMA_MEM_TO_DEV;
+ ret = dmaengine_slave_config(sai->dma_tx_chan, &dma_tx_sconfig);
+ if (ret < 0) {
+ dev_err(&sai->pdev->dev,
+ "DMA slave config failed, err = %d\n", ret);
+ dma_release_channel(sai->dma_tx_chan);
+ return ret;
+ }
+ sai->dma_tx_desc = dmaengine_prep_dma_cyclic(sai->dma_tx_chan,
+ sai->tx_buf.addr, SAI_AC97_RBUF_SIZE_TOT,
+ SAI_AC97_RBUF_SIZE, DMA_MEM_TO_DEV,
+ DMA_PREP_INTERRUPT);
+ sai->dma_tx_desc->callback = fsl_dma_tx_complete;
+ sai->dma_tx_desc->callback_param = sai;
+
+ return 0;
+};
+
+static int fsl_sai_ac97_prepare_rx_dma(struct fsl_sai_ac97 *sai)
+{
+ struct dma_slave_config dma_rx_sconfig;
+ int ret;
+
+ dma_rx_sconfig.src_addr = sai->mapbase + FSL_SAI_RDR;
+ dma_rx_sconfig.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dma_rx_sconfig.src_maxburst = 13;
+ dma_rx_sconfig.direction = DMA_DEV_TO_MEM;
+ ret = dmaengine_slave_config(sai->dma_rx_chan, &dma_rx_sconfig);
+ if (ret < 0) {
+ dev_err(&sai->pdev->dev,
+ "DMA slave config failed, err = %d\n", ret);
+ dma_release_channel(sai->dma_rx_chan);
+ return ret;
+ }
+ sai->dma_rx_desc = dmaengine_prep_dma_cyclic(sai->dma_rx_chan,
+ sai->rx_buf.addr, SAI_AC97_RBUF_SIZE_TOT,
+ SAI_AC97_RBUF_SIZE, DMA_DEV_TO_MEM,
+ DMA_PREP_INTERRUPT);
+ sai->dma_rx_desc->callback = fsl_dma_rx_complete;
+ sai->dma_rx_desc->callback_param = sai;
+
+ return 0;
+};
+
+static void fsl_sai_ac97_reset_sai(struct fsl_sai_ac97 *sai)
+{
+ /* TX */
+ /* Issue software reset */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR,
+ FSL_SAI_CSR_SR);
+
+ udelay(2);
+ /* Release software reset */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_SR, 0);
+
+ /* FIFO reset */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_FR,
+ FSL_SAI_CSR_FR);
+
+ /* RX */
+ /* Issue software reset */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR,
+ FSL_SAI_CSR_SR);
+
+ udelay(2);
+ /* Release software reset */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_SR, 0);
+
+ /* FIFO reset */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR, FSL_SAI_CSR_FR,
+ FSL_SAI_CSR_FR);
+};
+
+static int fsl_sai_ac97_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct fsl_sai_ac97 *sai;
+ struct resource *res;
+ void __iomem *base;
+ char tmp[8];
+ int ret, i;
+
+ sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+ if (!sai)
+ return -ENOMEM;
+
+ info = sai;
+ sai->pdev = pdev;
+
+ if (of_device_is_compatible(pdev->dev.of_node, "fsl,imx6sx-sai"))
+ sai->sai_on_imx = true;
+
+ 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);
+ sai->mapbase = res->start;
+ base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ sai->regmap = devm_regmap_init_mmio_clk(&pdev->dev,
+ "bus", base, &fsl_sai_regmap_config);
+
+ /* Compatible with old DTB cases */
+ if (IS_ERR(sai->regmap))
+ 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);
+ }
+
+ /* No error out for old DTB cases but only mark the clock NULL */
+ sai->bus_clk = devm_clk_get(&pdev->dev, "bus");
+ if (IS_ERR(sai->bus_clk)) {
+ dev_err(&pdev->dev, "failed to get bus clock: %ld\n",
+ PTR_ERR(sai->bus_clk));
+ sai->bus_clk = NULL;
+ }
+
+ ret = clk_prepare_enable(sai->bus_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable bus clk: %d\n", ret);
+ return ret;
+ }
+
+ sai->mclk_clk[0] = sai->bus_clk;
+ for (i = 1; i < FSL_SAI_MCLK_MAX; i++) {
+ sprintf(tmp, "mclk%d", i);
+ sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp);
+ if (IS_ERR(sai->mclk_clk[i])) {
+ dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n",
+ i, PTR_ERR(sai->mclk_clk[i]));
+ sai->mclk_clk[i] = NULL;
+ }
+ }
+
+ ret = snd_soc_set_ac97_ops_of_reset(&fsl_sai_ac97_ops, pdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to reset AC97 link: %d\n", ret);
+ goto err_disable_clock;
+ }
+
+ mutex_init(&info->lock);
+
+ /* clear transmit/receive configuration/status registers */
+ regmap_write(sai->regmap, FSL_SAI_TCSR, 0x0);
+ regmap_write(sai->regmap, FSL_SAI_RCSR, 0x0);
+
+ /* pre allocate DMA buffers */
+ sai->tx_buf.dev.type = SNDRV_DMA_TYPE_DEV;
+ sai->tx_buf.dev.dev = &pdev->dev;
+ sai->tx_buf.private_data = NULL;
+ sai->tx_buf.area = dma_alloc_writecombine(&pdev->dev, SAI_AC97_RBUF_SIZE_TOT,
+ &sai->tx_buf.addr, GFP_KERNEL);
+ if (!sai->tx_buf.area) {
+ ret = -ENOMEM;
+ //goto failed_tx_buf;
+ return ret;
+ }
+ sai->tx_buf.bytes = SAI_AC97_RBUF_SIZE_TOT;
+
+ sai->rx_buf.dev.type = SNDRV_DMA_TYPE_DEV;
+ sai->rx_buf.dev.dev = &pdev->dev;
+ sai->rx_buf.private_data = NULL;
+ sai->rx_buf.area = dma_alloc_writecombine(&pdev->dev, SAI_AC97_RBUF_SIZE_TOT,
+ &sai->rx_buf.addr, GFP_KERNEL);
+ if (!sai->rx_buf.area) {
+ ret = -ENOMEM;
+ //goto failed_rx_buf;
+ return ret;
+ }
+ sai->rx_buf.bytes = SAI_AC97_RBUF_SIZE_TOT;
+
+ memset(sai->tx_buf.area, 0, SAI_AC97_RBUF_SIZE_TOT);
+ memset(sai->rx_buf.area, 0, SAI_AC97_RBUF_SIZE_TOT);
+
+ /* 1. Configuration of SAI clock mode */
+
+ /*
+ * Issue software reset and FIFO reset for Transmitter and Receiver
+ * sections before starting configuration.
+ */
+ fsl_sai_ac97_reset_sai(sai);
+
+ /* Configure FIFO watermark. FIFO watermark is used as an indicator for
+ DMA trigger when read or write data from/to FIFOs. */
+ /* Watermark level for all enabled transmit channels of one SAI module.
+ */
+ regmap_write(sai->regmap, FSL_SAI_TCR1, 13);
+ regmap_write(sai->regmap, FSL_SAI_RCR1, 13);
+
+ /* Configure the clocking mode, bitclock polarity, direction, and
+ divider. Clocking mode defines synchronous or asynchronous operation
+ for SAI module. Bitclock polarity configures polarity of the
+ bitclock. Bitclock direction configures direction of the bitclock.
+ Bus master has bitclock generated externally, slave has bitclock
+ generated internally */
+
+ /* TX */
+ /* The transmitter must be configured for asynchronous operation */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC_MASK, 0);
+
+ /* bit clock not swapped */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCS, 0);
+
+ /* Bitclock is active high (drive outputs on rising edge and sample
+ * inputs on falling edge
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCP, 0);
+
+ /* Bitclock is generated externally (Slave mode) */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCD_MSTR, 0);
+
+ /* RX */
+ /* The receiver must be configured for synchronous operation. */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_SYNC_MASK,
+ FSL_SAI_CR2_SYNC);
+
+ /* bit clock not swapped */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCS, 0);
+
+ /* Bitclock is active high (drive outputs on rising edge and sample
+ * inputs on falling edge
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCP, 0);
+
+ /* Bitclock is generated externally (Slave mode) */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR2, FSL_SAI_CR2_BCD_MSTR, 0);
+
+ /* Configure frame size, frame sync width, MSB first, frame sync early,
+ polarity, and direction
+ Frame size – configures the number of words in each frame. AC97
+ requires 13 words per frame.
+ Frame sync width – configures the length of the frame sync in number
+ of bitclock. The sync width cannot be longer than the first word of
+ the frame. AC97 requires frame sync asserted for first word. */
+
+ /* Configures number of words in each frame. The value written should be
+ * one less than the number of words in the frame (part of define!)
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FRSZ_MASK,
+ FSL_SAI_CR4_FRSZ(13));
+
+ /* Configures length of the frame sync. The value written should be one
+ * less than the number of bitclocks.
+ * AC97 - 16 bits transmitted in first word.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_SYWD_MASK,
+ FSL_SAI_CR4_SYWD(16));
+
+
+ /* MSB is transmitted first */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_MF,
+ FSL_SAI_CR4_MF);
+
+ /* Frame sync asserted one bit before the first bit of the frame */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSE,
+ FSL_SAI_CR4_FSE);
+
+ /* A new AC-link input frame begins with a low to high transition of
+ * SYNC. Frame sync is active high
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSP, 0);
+
+ /* Frame sync is generated internally (Master mode) */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR4, FSL_SAI_CR4_FSD_MSTR,
+ FSL_SAI_CR4_FSD_MSTR);
+
+ /* RX */
+ /* Configures number of words in each frame. The value written should be
+ * one less than the number of words in the frame (part of define!)
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FRSZ_MASK,
+ FSL_SAI_CR4_FRSZ(13));
+
+ /* Configures length of the frame sync. The value written should be one
+ * less than the number of bitclocks.
+ * AC97 - 16 bits transmitted in first word.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_SYWD_MASK,
+ FSL_SAI_CR4_SYWD(16));
+
+
+ /* MSB is transmitted first */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_MF,
+ FSL_SAI_CR4_MF);
+
+ /* Frame sync asserted one bit before the first bit of the frame */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSE,
+ FSL_SAI_CR4_FSE);
+
+ /* A new AC-link input frame begins with a low to high transition of
+ * SYNC. Frame sync is active high
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSP, 0);
+
+ /* Frame sync is generated internally (Master mode) */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR4, FSL_SAI_CR4_FSD_MSTR,
+ FSL_SAI_CR4_FSD_MSTR);
+
+ /* Configure the Word 0 and next word sizes.
+ W0W – defines number of bits in the first word in each frame.
+ WNW – defines number of bits in each word for each word except the
+ first in the frame. */
+
+ /* TX */
+ /* Number of bits in first word in each frame. AC97 – 16-bit word is
+ * transmitted.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_W0W_MASK,
+ FSL_SAI_CR5_W0W(16));
+
+ /* Number of bits in each word in each frame. AC97 – 20-bit word is
+ * transmitted.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_WNW_MASK,
+ FSL_SAI_CR5_WNW(20));
+
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_W0W_MASK,
+ FSL_SAI_CR5_W0W(16));
+
+ /* Configures the bit index for the first bit transmitted for each word
+ * in the frame. The value written must be greater than or equal to the
+ * word width when configured for MSB First.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR5, FSL_SAI_CR5_FBT_MASK,
+ FSL_SAI_CR5_FBT(20));
+
+ /* RX */
+ /* Number of bits in first word in each frame. AC97 – 16-bit word is
+ * transmitted.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_W0W_MASK,
+ FSL_SAI_CR5_W0W(16));
+
+ /* Number of bits in each word in each frame. AC97 – 20-bit word is
+ * transmitted.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_WNW_MASK,
+ FSL_SAI_CR5_WNW(20));
+
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_W0W_MASK,
+ FSL_SAI_CR5_W0W(16));
+
+ /* Configures the bit index for the first bit transmitted for each word
+ * in the frame. The value written must be greater than or equal to the
+ * word width when configured for MSB First.
+ */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR5, FSL_SAI_CR5_FBT_MASK,
+ FSL_SAI_CR5_FBT(20));
+
+
+ /* Clear the Transmit and Receive Mask registers. */
+ regmap_write(sai->regmap, FSL_SAI_TMR, 0);
+ regmap_write(sai->regmap, FSL_SAI_RMR, 0);
+
+
+ sai->dma_tx_chan = dma_request_slave_channel(&pdev->dev, "tx");
+ if (!sai->dma_tx_chan) {
+ dev_err(&pdev->dev, "DMA tx channel request failed!\n");
+ return -ENODEV;
+ }
+
+ sai->dma_rx_chan = dma_request_slave_channel(&pdev->dev, "rx");
+ if (!sai->dma_rx_chan) {
+ dev_err(&pdev->dev, "DMA rx channel request failed!\n");
+ return -ENODEV;
+ }
+
+ /* Enables a data channel for a transmit operation. */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR3, FSL_SAI_CR3_TRCE,
+ FSL_SAI_CR3_TRCE);
+
+ /* Enables a data channel for a receive operation. */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCR3, FSL_SAI_CR3_TRCE,
+ FSL_SAI_CR3_TRCE);
+
+
+ /* In synchronous mode, receiver is enabled only when both transmitter
+ and receiver are enabled. It is recommended that transmitter is
+ enabled last and disabled first. */
+ /* Enable receiver */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE);
+
+ /* Enable transmitter */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE);
+
+ platform_set_drvdata(pdev, sai);
+
+ ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
+ &fsl_sai_ac97_dai, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register Component: %d\n", ret);
+ goto err_disable_clock;
+ }
+
+ /* Register our own PCM device, which fills the AC97 frames... */
+ snd_soc_add_platform(&pdev->dev, &sai->platform, &ac97_software_pcm_platform);
+
+ /* Start the DMA engine */
+ fsl_sai_ac97_prepare_tx_dma(sai);
+ fsl_sai_ac97_prepare_rx_dma(sai);
+
+ sai->dma_tx_cookie = dmaengine_submit(sai->dma_tx_desc);
+ dma_async_issue_pending(sai->dma_tx_chan);
+
+ sai->dma_rx_cookie = dmaengine_submit(sai->dma_rx_desc);
+ dma_async_issue_pending(sai->dma_rx_chan);
+
+ return 0;
+
+err_disable_clock:
+ clk_disable_unprepare(sai->bus_clk);
+
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_sai_ac97_suspend(struct device *dev)
+{
+ struct fsl_sai_ac97 *sai = dev_get_drvdata(dev);
+
+ /* Disable receiver/transmitter */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, 0x0);
+
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE, 0x0);
+
+ dmaengine_terminate_all(sai->dma_tx_chan);
+ dmaengine_terminate_all(sai->dma_rx_chan);
+
+ regcache_cache_only(sai->regmap, true);
+
+ return 0;
+}
+
+static int fsl_sai_ac97_resume(struct device *dev)
+{
+ struct fsl_sai_ac97 *sai = dev_get_drvdata(dev);
+
+ regcache_mark_dirty(sai->regmap);
+ regcache_cache_only(sai->regmap, false);
+ regcache_sync(sai->regmap);
+
+ /* Reset SAI */
+ fsl_sai_ac97_reset_sai(sai);
+
+ /* Enable receiver */
+ regmap_update_bits(sai->regmap, FSL_SAI_RCSR,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE);
+
+ /* Enable transmitter */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE,
+ FSL_SAI_CSR_FRDE | FSL_SAI_CSR_TERE);
+
+ /* Restart the DMA engine */
+ fsl_sai_ac97_prepare_tx_dma(sai);
+ fsl_sai_ac97_prepare_rx_dma(sai);
+
+ sai->dma_tx_cookie = dmaengine_submit(sai->dma_tx_desc);
+ dma_async_issue_pending(sai->dma_tx_chan);
+
+ sai->dma_rx_cookie = dmaengine_submit(sai->dma_rx_desc);
+ dma_async_issue_pending(sai->dma_rx_chan);
+
+ return 0;
+}
+#else
+#define fsl_sai_ac97_suspend NULL
+#define fsl_sai_ac97_resume NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_sai_ac97_pm = {
+ .suspend = fsl_sai_ac97_suspend,
+ .resume = fsl_sai_ac97_resume,
+};
+static const struct of_device_id fsl_sai_ac97_ids[] = {
+ { .compatible = "fsl,vf610-sai-ac97", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver fsl_sai_ac97_driver = {
+ .probe = fsl_sai_ac97_probe,
+ .driver = {
+ .name = "fsl-sai-ac97",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_sai_ac97_ids,
+ .pm = &fsl_sai_ac97_pm,
+ },
+};
+module_platform_driver(fsl_sai_ac97_driver);
+
+MODULE_DESCRIPTION("Freescale SoC SAI AC97 Interface");
+MODULE_AUTHOR("Stefan Agner, Marcel Ziswiler");
+MODULE_ALIAS("platform:fsl-sai-ac97");
+MODULE_LICENSE("GPLv2");
diff --git a/sound/soc/fsl/fsl_sai_clk.c b/sound/soc/fsl/fsl_sai_clk.c
new file mode 100644
index 000000000000..1d0b4cb5f126
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai_clk.c
@@ -0,0 +1,260 @@
+/*
+ * Freescale SAI driver to use SAI as a clock source
+ *
+ * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ * Copyright 2015-2016 Toradex AG
+ *
+ * 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 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_TFR:
+ case FSL_SAI_RFR:
+ case FSL_SAI_TDR:
+ 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_FLAT,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_sai_clk_suspend(struct device *dev)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+
+ /* disable AC97 master clock */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_BCE, 0);
+
+ regcache_cache_only(sai->regmap, true);
+
+ clk_disable_unprepare(sai->mclk_clk[1]);
+ clk_disable_unprepare(sai->bus_clk);
+
+ return 0;
+}
+
+static int fsl_sai_clk_resume(struct device *dev)
+{
+ struct fsl_sai *sai = dev_get_drvdata(dev);
+
+ clk_prepare_enable(sai->bus_clk);
+ clk_prepare_enable(sai->mclk_clk[1]);
+
+ regcache_mark_dirty(sai->regmap);
+ regcache_cache_only(sai->regmap, false);
+ regcache_sync(sai->regmap);
+
+ /* enable AC97 master clock */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_BCE,
+ FSL_SAI_CSR_BCE);
+
+ return 0;
+}
+#else
+#define fsl_sai_clk_suspend NULL
+#define fsl_sai_clk_resume NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops fsl_sai_clk_pm = {
+ .suspend_late = fsl_sai_clk_suspend,
+ .resume_early = fsl_sai_clk_resume,
+};
+
+static int fsl_sai_clk_probe(struct platform_device *pdev)
+{
+ struct fsl_sai *sai;
+ struct resource *res;
+ void __iomem *base;
+ char tmp[8];
+ int ret;
+ int i;
+
+ sai = devm_kzalloc(&pdev->dev, sizeof(*sai), GFP_KERNEL);
+ if (!sai)
+ return -ENOMEM;
+
+ sai->pdev = pdev;
+
+ 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,
+ "bus", base, &fsl_sai_regmap_config);
+
+ /* Compatible with old DTB cases */
+ if (IS_ERR(sai->regmap))
+ 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);
+ }
+
+ /* No error out for old DTB cases but only mark the clock NULL */
+ sai->bus_clk = devm_clk_get(&pdev->dev, "bus");
+ if (IS_ERR(sai->bus_clk)) {
+ dev_err(&pdev->dev, "failed to get bus clock: %ld\n",
+ PTR_ERR(sai->bus_clk));
+ sai->bus_clk = NULL;
+ }
+
+ sai->mclk_clk[0] = sai->bus_clk;
+ for (i = 1; i < FSL_SAI_MCLK_MAX; i++) {
+ sprintf(tmp, "mclk%d", i);
+ sai->mclk_clk[i] = devm_clk_get(&pdev->dev, tmp);
+ if (IS_ERR(sai->mclk_clk[i])) {
+ dev_err(&pdev->dev, "failed to get mclk%d clock: %ld\n",
+ i, PTR_ERR(sai->mclk_clk[i]));
+ sai->mclk_clk[i] = NULL;
+ }
+ }
+
+ ret = clk_prepare_enable(sai->bus_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable bus clk: %d\n", ret);
+ return ret;
+ }
+
+ /* configure AC97 master clock */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC,
+ ~FSL_SAI_CR2_SYNC);
+
+
+ /* asynchronous aka independent operation */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_SYNC_MASK, 0);
+
+ /* Bit clock not swapped */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCS, 0);
+
+ /* Clock selected by CCM_CSCMR1[SAIn_CLK_SEL] */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_MSEL_MASK,
+ FSL_SAI_CR2_MSEL_MCLK1);
+
+ /* Bitclock is generated internally (master mode) */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_BCD_MSTR,
+ FSL_SAI_CR2_BCD_MSTR);
+
+ /* Divide by 6 */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCR2, FSL_SAI_CR2_DIV_MASK,
+ FSL_SAI_CR2_DIV(2));
+
+ ret = clk_prepare_enable(sai->mclk_clk[1]);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to enable mclk1: %d\n", ret);
+ return ret;
+ }
+
+ /* enable AC97 master clock */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_BCE,
+ FSL_SAI_CSR_BCE);
+
+ platform_set_drvdata(pdev, sai);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_sai_ids[] = {
+ { .compatible = "fsl,vf610-sai-clk", },
+ { /* sentinel */ }
+};
+
+static struct platform_driver fsl_sai_driver = {
+ .probe = fsl_sai_clk_probe,
+ .driver = {
+ .name = "fsl-sai-clk",
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_sai_ids,
+ .pm = &fsl_sai_clk_pm,
+ },
+};
+module_platform_driver(fsl_sai_driver);
+
+MODULE_DESCRIPTION("Freescale SoC SAI as clock generator");
+MODULE_AUTHOR("Stefan Agner, <stefan.agner@toradex.com>");
+MODULE_ALIAS("platform:fsl-sai-clk");
+MODULE_LICENSE("GPLv2");
diff --git a/sound/soc/fsl/fsl_sai_wm9712.c b/sound/soc/fsl/fsl_sai_wm9712.c
new file mode 100644
index 000000000000..617c5ab45f64
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai_wm9712.c
@@ -0,0 +1,130 @@
+/*
+ * fsl_sai_wm9712.c -- SoC audio for Freescale SAI
+ *
+ * Copyright 2014 Stefan Agner, Toradex AG <stefan@agner.ch>
+ *
+ * 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 <sound/soc.h>
+
+#define DRV_NAME "fsl-sai-ac97-dt-driver"
+
+static struct snd_soc_dai_link fsl_sai_wm9712_dai = {
+ .name = "AC97 HiFi",
+ .stream_name = "AC97 HiFi",
+ .codec_dai_name = "wm9712-hifi",
+ .codec_name = "wm9712-codec",
+};
+
+static const struct snd_soc_dapm_widget tegra_wm9712_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_LINE("LineIn", NULL),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+
+static struct snd_soc_card snd_soc_card_fsl_sai_wm9712 = {
+ .name = "Colibri VF61 AC97 Audio",
+ .owner = THIS_MODULE,
+ .dai_link = &fsl_sai_wm9712_dai,
+ .num_links = 1,
+
+ .dapm_widgets = tegra_wm9712_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(tegra_wm9712_dapm_widgets),
+ .fully_routed = true,
+};
+
+static struct platform_device *codec;
+
+static int fsl_sai_wm9712_driver_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct snd_soc_card *card = &snd_soc_card_fsl_sai_wm9712;
+ int ret;
+
+ card->dev = &pdev->dev;
+ platform_set_drvdata(pdev, card);
+
+ codec = platform_device_alloc("wm9712-codec", -1);
+ if (!codec)
+ return -ENOMEM;
+
+ ret = platform_device_add(codec);
+ if (ret)
+ goto codec_put;
+
+ ret = snd_soc_of_parse_card_name(card, "fsl,model");
+ if (ret)
+ goto codec_unregister;
+
+ ret = snd_soc_of_parse_audio_routing(card, "fsl,audio-routing");
+ if (ret)
+ goto codec_unregister;
+
+ fsl_sai_wm9712_dai.cpu_of_node = of_parse_phandle(np,
+ "fsl,ac97-controller", 0);
+ if (!fsl_sai_wm9712_dai.cpu_of_node) {
+ dev_err(&pdev->dev,
+ "Property 'fsl,ac97-controller' missing or invalid\n");
+ ret = -EINVAL;
+ goto codec_unregister;
+ }
+
+ fsl_sai_wm9712_dai.platform_of_node = fsl_sai_wm9712_dai.cpu_of_node;
+
+ ret = snd_soc_register_card(card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+ ret);
+ goto codec_unregister;
+ }
+
+ return 0;
+
+codec_unregister:
+ platform_device_del(codec);
+
+codec_put:
+ platform_device_put(codec);
+ return ret;
+}
+
+static int fsl_sai_wm9712_driver_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_card(card);
+
+ platform_device_unregister(codec);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_sai_wm9712_of_match[] = {
+ { .compatible = "fsl,fsl-sai-audio-wm9712", },
+ {},
+};
+
+static struct platform_driver fsl_sai_wm9712_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = fsl_sai_wm9712_of_match,
+ },
+ .probe = fsl_sai_wm9712_driver_probe,
+ .remove = fsl_sai_wm9712_driver_remove,
+};
+module_platform_driver(fsl_sai_wm9712_driver);
+
+/* Module information */
+MODULE_AUTHOR("Stefan Agner <stefan@agner.ch>");
+MODULE_DESCRIPTION("ALSA SoC Freescale SAI+WM9712");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(of, fsl_sai_wm9712_of_match);