summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Agner <stefan.agner@toradex.com>2015-02-02 15:56:32 +0100
committerStefan Agner <stefan.agner@toradex.com>2015-02-02 15:56:32 +0100
commit67bacaabc4e9cf5bade60354736d87a90325d5ec (patch)
tree1a584115adc173a91c1b818f44d5ef57ea2a8c68
parent43328a22788c8d856f30a9b0b909e00b7a0c6ecc (diff)
parent5757b6753c3290e7b7e8ce2047368eac572392ee (diff)
Merge branch 'vf610-ac97-sai' into toradex_vf_3.18-next
-rw-r--r--arch/arm/boot/dts/vf500.dtsi4
-rw-r--r--arch/arm/boot/dts/vf610-colibri.dtsi80
-rw-r--r--arch/arm/boot/dts/vfxxx.dtsi19
-rw-r--r--arch/arm/configs/colibri_vf_defconfig4
-rw-r--r--arch/arm/mach-imx/clk-vf610.c7
-rw-r--r--drivers/dma/fsl-edma.c290
-rw-r--r--sound/soc/fsl/Kconfig10
-rw-r--r--sound/soc/fsl/Makefile3
-rw-r--r--sound/soc/fsl/fsl_sai.h7
-rw-r--r--sound/soc/fsl/fsl_sai_ac97.c1237
-rw-r--r--sound/soc/fsl/fsl_sai_clk.c198
-rw-r--r--sound/soc/fsl/fsl_sai_wm9712.c129
12 files changed, 1886 insertions, 102 deletions
diff --git a/arch/arm/boot/dts/vf500.dtsi b/arch/arm/boot/dts/vf500.dtsi
index d7e3771b70ed..104c6c47ebf8 100644
--- a/arch/arm/boot/dts/vf500.dtsi
+++ b/arch/arm/boot/dts/vf500.dtsi
@@ -148,6 +148,10 @@
interrupts = <GIC_SPI 24 IRQ_TYPE_LEVEL_HIGH>;
};
+&sai0 {
+ interrupts = <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>;
+};
+
&sai2 {
interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>;
};
diff --git a/arch/arm/boot/dts/vf610-colibri.dtsi b/arch/arm/boot/dts/vf610-colibri.dtsi
index 19fe045b8334..8bc0548aabc9 100644
--- a/arch/arm/boot/dts/vf610-colibri.dtsi
+++ b/arch/arm/boot/dts/vf610-colibri.dtsi
@@ -17,9 +17,89 @@
memory {
reg = <0x80000000 0x10000000>;
};
+
+ sound {
+ compatible = "fsl,fsl-sai-audio-wm9712";
+ fsl,ac97-controller = <&sai2>;
+
+ fsl,model = "Colibri VF61 AC97 Audio";
+
+ fsl,audio-routing =
+ "Headphone", "HPOUTL",
+ "Headphone", "HPOUTR",
+ "LineIn", "LINEINL",
+ "LineIn", "LINEINR",
+ "Mic", "MIC1";
+ };
+};
+
+&sai0 {
+ compatible = "fsl,vf610-sai-clk";
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_sai0>;
+ status = "okay";
+};
+
+&sai2 {
+ compatible = "fsl,vf610-sai-ac97";
+ #sound-dai-cells = <0>;
+
+ pinctrl-names = "default", "ac97-running", "ac97-reset",
+ "ac97-warm-reset";
+ pinctrl-0 = <&pinctrl_sai2_ac97_running>;
+ pinctrl-1 = <&pinctrl_sai2_ac97_running>;
+ pinctrl-2 = <&pinctrl_sai2_ac97_reset>;
+ pinctrl-3 = <&pinctrl_sai2_ac97_reset>;
+ ac97-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH &gpio0 8 GPIO_ACTIVE_HIGH
+ &gpio0 13 GPIO_ACTIVE_HIGH>;
+ status = "okay";
};
&L2 {
arm,data-latency = <2 1 2>;
arm,tag-latency = <3 2 3>;
};
+
+&iomuxc {
+ vf610-colibri {
+ pinctrl_sai0: sai0grp_1 {
+ fsl,pins = <
+ VF610_PAD_PTB23__SAI0_TX_BCLK 0x31C3
+ >;
+ };
+ pinctrl_sai2_ac97_reset: sai2grp_1 {
+ fsl,pins = <
+ /* Pen-down */
+ VF610_PAD_PTA11__GPIO_4 0x22ed
+ /* AC97 SData Out (test mode selection) */
+ VF610_PAD_PTA18__GPIO_8 0x22ed
+ /* AC97 Sync (warm reset) */
+ VF610_PAD_PTA19__GPIO_9 0x22ed
+ /* AC97 Reset (cold reset) */
+ VF610_PAD_PTA23__GPIO_13 0x22eb
+ >;
+ };
+
+ pinctrl_sai2_ac97_running: sai2grp_2 {
+ fsl,pins = <
+ /* AC97 Bit clock */
+ VF610_PAD_PTA16__SAI2_TX_BCLK 0x31C3
+
+ /* AC97 SData Out */
+ VF610_PAD_PTA18__SAI2_TX_DATA 0x31C2
+
+ /* AC97 Sync */
+ VF610_PAD_PTA19__SAI2_TX_SYNC 0x31C3
+
+ /* AC97 SData In */
+ VF610_PAD_PTA22__SAI2_RX_DATA 0x0041
+
+ /* AC97 Reset (cold reset, keep output buffer on) */
+ VF610_PAD_PTA23__GPIO_13 0x22eb
+
+ /* GenIRQ */
+ VF610_PAD_PTB2__GPIO_24 0x22ed
+ >;
+ };
+ };
+};
diff --git a/arch/arm/boot/dts/vfxxx.dtsi b/arch/arm/boot/dts/vfxxx.dtsi
index dfed92bd7bad..6eeaf9ddbc34 100644
--- a/arch/arm/boot/dts/vfxxx.dtsi
+++ b/arch/arm/boot/dts/vfxxx.dtsi
@@ -161,11 +161,26 @@
status = "disabled";
};
+ sai0: sai@4002f000 {
+ compatible = "fsl,vf610-sai";
+ reg = <0x4002f000 0x1000>;
+ clocks = <&clks VF610_CLK_SAI0>,
+ <&clks VF610_CLK_SAI0>,
+ <&clks 0>, <&clks 0>;
+ clock-names = "bus", "mclk1", "mclk2", "mclk3";
+ dma-names = "tx", "rx";
+ dmas = <&edma0 0 17>,
+ <&edma0 0 16>;
+ status = "disabled";
+ };
+
sai2: sai@40031000 {
compatible = "fsl,vf610-sai";
reg = <0x40031000 0x1000>;
- clocks = <&clks VF610_CLK_SAI2>;
- clock-names = "sai";
+ clocks = <&clks VF610_CLK_SAI2>,
+ <&clks VF610_CLK_SAI2>,
+ <&clks 0>, <&clks 0>;
+ clock-names = "bus", "mclk1", "mclk2", "mclk3";
dma-names = "tx", "rx";
dmas = <&edma0 0 21>,
<&edma0 0 20>;
diff --git a/arch/arm/configs/colibri_vf_defconfig b/arch/arm/configs/colibri_vf_defconfig
index c6c9056ac959..af36561b5f9d 100644
--- a/arch/arm/configs/colibri_vf_defconfig
+++ b/arch/arm/configs/colibri_vf_defconfig
@@ -107,6 +107,9 @@ CONFIG_KEYBOARD_IMX=y
# CONFIG_MOUSE_PS2 is not set
CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_EGALAX=y
+CONFIG_TOUCHSCREEN_WM97XX=y
+# CONFIG_TOUCHSCREEN_WM9705 is not set
+# CONFIG_TOUCHSCREEN_WM9713 is not set
CONFIG_TOUCHSCREEN_STMPE=y
CONFIG_TOUCHSCREEN_COLIBRI_VF50=y
# CONFIG_LEGACY_PTYS is not set
@@ -159,6 +162,7 @@ CONFIG_SOUND=y
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_SND_IMX_SOC=y
+CONFIG_SND_SOC_FSL_SAI_WM9712=y
CONFIG_SND_SOC_IMX_SGTL5000=y
CONFIG_USB=y
CONFIG_USB_EHCI_HCD=y
diff --git a/arch/arm/mach-imx/clk-vf610.c b/arch/arm/mach-imx/clk-vf610.c
index 9fc721acf39d..a731975b23c3 100644
--- a/arch/arm/mach-imx/clk-vf610.c
+++ b/arch/arm/mach-imx/clk-vf610.c
@@ -403,10 +403,11 @@ static void __init vf610_clocks_init(struct device_node *ccm_node)
clk_set_rate(clk[VF610_CLK_QSPI1_X2_DIV], clk_get_rate(clk[VF610_CLK_QSPI1_X4_DIV]) / 2);
clk_set_rate(clk[VF610_CLK_QSPI1_X1_DIV], clk_get_rate(clk[VF610_CLK_QSPI1_X2_DIV]) / 2);
- clk_set_parent(clk[VF610_CLK_SAI0_SEL], clk[VF610_CLK_AUDIO_EXT]);
- clk_set_parent(clk[VF610_CLK_SAI1_SEL], clk[VF610_CLK_AUDIO_EXT]);
- clk_set_parent(clk[VF610_CLK_SAI2_SEL], clk[VF610_CLK_AUDIO_EXT]);
+ clk_set_parent(clk[VF610_CLK_SAI0_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]);
+ clk_set_parent(clk[VF610_CLK_SAI1_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]);
+ clk_set_parent(clk[VF610_CLK_SAI2_SEL], clk[VF610_CLK_PLL4_MAIN_DIV]);
clk_set_parent(clk[VF610_CLK_SAI3_SEL], clk[VF610_CLK_AUDIO_EXT]);
+ clk_set_rate(clk[VF610_CLK_PLL4_MAIN_DIV], 147456000);
clk_set_parent(clk[VF610_CLK_DCU0_SEL], clk[VF610_CLK_PLL1_PFD2]);
diff --git a/drivers/dma/fsl-edma.c b/drivers/dma/fsl-edma.c
index 58c6fc7e902e..d622feb38716 100644
--- a/drivers/dma/fsl-edma.c
+++ b/drivers/dma/fsl-edma.c
@@ -110,6 +110,8 @@
#define EDMAMUX_CHCFG_ENBL 0x80
#define EDMAMUX_CHCFG_SOURCE(n) ((n) & 0x3F)
+#define SLAVE_ID_ALWAYSON 63 /* the always on slave id */
+
#define DMAMUX_NR 2
#define FSL_EDMA_BUSWIDTHS BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) | \
@@ -118,17 +120,17 @@
BIT(DMA_SLAVE_BUSWIDTH_8_BYTES)
struct fsl_edma_hw_tcd {
- u32 saddr;
- u16 soff;
- u16 attr;
- u32 nbytes;
- u32 slast;
- u32 daddr;
- u16 doff;
- u16 citer;
- u32 dlast_sga;
- u16 csr;
- u16 biter;
+ __le32 saddr;
+ __le16 soff;
+ __le16 attr;
+ __le32 nbytes;
+ __le32 slast;
+ __le32 daddr;
+ __le16 doff;
+ __le16 citer;
+ __le32 dlast_sga;
+ __le16 csr;
+ __le16 biter;
};
struct fsl_edma_sw_tcd {
@@ -147,6 +149,7 @@ struct fsl_edma_slave_config {
struct fsl_edma_chan {
struct virt_dma_chan vchan;
enum dma_status status;
+ u32 slave_id;
struct fsl_edma_engine *edma;
struct fsl_edma_desc *edesc;
struct fsl_edma_slave_config fsc;
@@ -175,18 +178,12 @@ struct fsl_edma_engine {
};
/*
- * R/W functions for big- or little-endian registers
- * the eDMA controller's endian is independent of the CPU core's endian.
+ * R/W functions for big- or little-endian registers:
+ * The eDMA controller's endian is independent of the CPU core's endian.
+ * For the big-endian IP module, the offset for 8-bit or 16-bit registers
+ * should also be swapped opposite to that in little-endian IP.
*/
-static u16 edma_readw(struct fsl_edma_engine *edma, void __iomem *addr)
-{
- if (edma->big_endian)
- return ioread16be(addr);
- else
- return ioread16(addr);
-}
-
static u32 edma_readl(struct fsl_edma_engine *edma, void __iomem *addr)
{
if (edma->big_endian)
@@ -197,13 +194,18 @@ static u32 edma_readl(struct fsl_edma_engine *edma, void __iomem *addr)
static void edma_writeb(struct fsl_edma_engine *edma, u8 val, void __iomem *addr)
{
- iowrite8(val, addr);
+ /* swap the reg offset for these in big-endian mode */
+ if (edma->big_endian)
+ iowrite8(val, (void __iomem *)((unsigned long)addr ^ 0x3));
+ else
+ iowrite8(val, addr);
}
static void edma_writew(struct fsl_edma_engine *edma, u16 val, void __iomem *addr)
{
+ /* swap the reg offset for these in big-endian mode */
if (edma->big_endian)
- iowrite16be(val, addr);
+ iowrite16be(val, (void __iomem *)((unsigned long)addr ^ 0x2));
else
iowrite16(val, addr);
}
@@ -254,13 +256,12 @@ static void fsl_edma_chan_mux(struct fsl_edma_chan *fsl_chan,
chans_per_mux = fsl_chan->edma->n_chans / DMAMUX_NR;
ch_off = fsl_chan->vchan.chan.chan_id % chans_per_mux;
muxaddr = fsl_chan->edma->muxbase[ch / chans_per_mux];
+ slot = EDMAMUX_CHCFG_SOURCE(slot);
if (enable)
- edma_writeb(fsl_chan->edma,
- EDMAMUX_CHCFG_ENBL | EDMAMUX_CHCFG_SOURCE(slot),
- muxaddr + ch_off);
+ iowrite8(EDMAMUX_CHCFG_ENBL | slot, muxaddr + ch_off);
else
- edma_writeb(fsl_chan->edma, EDMAMUX_CHCFG_DIS, muxaddr + ch_off);
+ iowrite8(EDMAMUX_CHCFG_DIS, muxaddr + ch_off);
}
static unsigned int fsl_edma_get_tcd_attr(enum dma_slave_buswidth addr_width)
@@ -286,9 +287,8 @@ static void fsl_edma_free_desc(struct virt_dma_desc *vdesc)
fsl_desc = to_fsl_edma_desc(vdesc);
for (i = 0; i < fsl_desc->n_tcds; i++)
- dma_pool_free(fsl_desc->echan->tcd_pool,
- fsl_desc->tcd[i].vtcd,
- fsl_desc->tcd[i].ptcd);
+ dma_pool_free(fsl_desc->echan->tcd_pool, fsl_desc->tcd[i].vtcd,
+ fsl_desc->tcd[i].ptcd);
kfree(fsl_desc);
}
@@ -363,8 +363,8 @@ static size_t fsl_edma_desc_residue(struct fsl_edma_chan *fsl_chan,
/* calculate the total size in this desc */
for (len = i = 0; i < fsl_chan->edesc->n_tcds; i++)
- len += edma_readl(fsl_chan->edma, &(edesc->tcd[i].vtcd->nbytes))
- * edma_readw(fsl_chan->edma, &(edesc->tcd[i].vtcd->biter));
+ len += le32_to_cpu(edesc->tcd[i].vtcd->nbytes)
+ * le16_to_cpu(edesc->tcd[i].vtcd->biter);
if (!in_progress)
return len;
@@ -376,14 +376,12 @@ static size_t fsl_edma_desc_residue(struct fsl_edma_chan *fsl_chan,
/* figure out the finished and calculate the residue */
for (i = 0; i < fsl_chan->edesc->n_tcds; i++) {
- size = edma_readl(fsl_chan->edma, &(edesc->tcd[i].vtcd->nbytes))
- * edma_readw(fsl_chan->edma, &(edesc->tcd[i].vtcd->biter));
+ size = le32_to_cpu(edesc->tcd[i].vtcd->nbytes)
+ * le16_to_cpu(edesc->tcd[i].vtcd->biter);
if (dir == DMA_MEM_TO_DEV)
- dma_addr = edma_readl(fsl_chan->edma,
- &(edesc->tcd[i].vtcd->saddr));
+ dma_addr = le32_to_cpu(edesc->tcd[i].vtcd->saddr);
else
- dma_addr = edma_readl(fsl_chan->edma,
- &(edesc->tcd[i].vtcd->daddr));
+ dma_addr = le32_to_cpu(edesc->tcd[i].vtcd->daddr);
len -= size;
if (cur_addr >= dma_addr && cur_addr < dma_addr + size) {
@@ -424,55 +422,67 @@ static enum dma_status fsl_edma_tx_status(struct dma_chan *chan,
return fsl_chan->status;
}
-static void fsl_edma_set_tcd_params(struct fsl_edma_chan *fsl_chan,
- u32 src, u32 dst, u16 attr, u16 soff, u32 nbytes,
- u32 slast, u16 citer, u16 biter, u32 doff, u32 dlast_sga,
- u16 csr)
+static void fsl_edma_set_tcd_regs(struct fsl_edma_chan *fsl_chan,
+ struct fsl_edma_hw_tcd *tcd)
{
+ struct fsl_edma_engine *edma = fsl_chan->edma;
void __iomem *addr = fsl_chan->edma->membase;
u32 ch = fsl_chan->vchan.chan.chan_id;
/*
- * TCD parameters have been swapped in fill_tcd_params(),
- * so just write them to registers in the cpu endian here
+ * TCD parameters are stored in struct fsl_edma_hw_tcd in little
+ * endian format. However, we need to load the TCD registers in
+ * big- or little-endian obeying the eDMA engine model endian.
*/
- writew(0, addr + EDMA_TCD_CSR(ch));
- writel(src, addr + EDMA_TCD_SADDR(ch));
- writel(dst, addr + EDMA_TCD_DADDR(ch));
- writew(attr, addr + EDMA_TCD_ATTR(ch));
- writew(soff, addr + EDMA_TCD_SOFF(ch));
- writel(nbytes, addr + EDMA_TCD_NBYTES(ch));
- writel(slast, addr + EDMA_TCD_SLAST(ch));
- writew(citer, addr + EDMA_TCD_CITER(ch));
- writew(biter, addr + EDMA_TCD_BITER(ch));
- writew(doff, addr + EDMA_TCD_DOFF(ch));
- writel(dlast_sga, addr + EDMA_TCD_DLAST_SGA(ch));
- writew(csr, addr + EDMA_TCD_CSR(ch));
-}
-
-static void fill_tcd_params(struct fsl_edma_engine *edma,
- struct fsl_edma_hw_tcd *tcd, u32 src, u32 dst,
- u16 attr, u16 soff, u32 nbytes, u32 slast, u16 citer,
- u16 biter, u16 doff, u32 dlast_sga, bool major_int,
- bool disable_req, bool enable_sg)
+ edma_writew(edma, 0, addr + EDMA_TCD_CSR(ch));
+ edma_writel(edma, le32_to_cpu(tcd->saddr), addr + EDMA_TCD_SADDR(ch));
+ edma_writel(edma, le32_to_cpu(tcd->daddr), addr + EDMA_TCD_DADDR(ch));
+
+ edma_writew(edma, le16_to_cpu(tcd->attr), addr + EDMA_TCD_ATTR(ch));
+ edma_writew(edma, le16_to_cpu(tcd->soff), addr + EDMA_TCD_SOFF(ch));
+
+ edma_writel(edma, le32_to_cpu(tcd->nbytes), addr + EDMA_TCD_NBYTES(ch));
+ edma_writel(edma, le32_to_cpu(tcd->slast), addr + EDMA_TCD_SLAST(ch));
+
+ edma_writew(edma, le16_to_cpu(tcd->citer), addr + EDMA_TCD_CITER(ch));
+ edma_writew(edma, le16_to_cpu(tcd->biter), addr + EDMA_TCD_BITER(ch));
+ edma_writew(edma, le16_to_cpu(tcd->doff), addr + EDMA_TCD_DOFF(ch));
+
+ edma_writel(edma, le32_to_cpu(tcd->dlast_sga), addr + EDMA_TCD_DLAST_SGA(ch));
+
+ edma_writew(edma, le16_to_cpu(tcd->csr), addr + EDMA_TCD_CSR(ch));
+}
+
+static inline
+void fsl_edma_fill_tcd(struct fsl_edma_hw_tcd *tcd, u32 src, u32 dst,
+ u16 attr, u16 soff, u32 nbytes, u32 slast, u16 citer,
+ u16 biter, u16 doff, u32 dlast_sga, bool major_int,
+ bool disable_req, bool enable_sg)
{
u16 csr = 0;
/*
- * eDMA hardware SGs require the TCD parameters stored in memory
- * the same endian as the eDMA module so that they can be loaded
- * automatically by the engine
+ * eDMA hardware SGs require the TCDs to be stored in little
+ * endian format irrespective of the register endian model.
+ * So we put the value in little endian in memory, waiting
+ * for fsl_edma_set_tcd_regs doing the swap.
*/
- edma_writel(edma, src, &(tcd->saddr));
- edma_writel(edma, dst, &(tcd->daddr));
- edma_writew(edma, attr, &(tcd->attr));
- edma_writew(edma, EDMA_TCD_SOFF_SOFF(soff), &(tcd->soff));
- edma_writel(edma, EDMA_TCD_NBYTES_NBYTES(nbytes), &(tcd->nbytes));
- edma_writel(edma, EDMA_TCD_SLAST_SLAST(slast), &(tcd->slast));
- edma_writew(edma, EDMA_TCD_CITER_CITER(citer), &(tcd->citer));
- edma_writew(edma, EDMA_TCD_DOFF_DOFF(doff), &(tcd->doff));
- edma_writel(edma, EDMA_TCD_DLAST_SGA_DLAST_SGA(dlast_sga), &(tcd->dlast_sga));
- edma_writew(edma, EDMA_TCD_BITER_BITER(biter), &(tcd->biter));
+ tcd->saddr = cpu_to_le32(src);
+ tcd->daddr = cpu_to_le32(dst);
+
+ tcd->attr = cpu_to_le16(attr);
+
+ tcd->soff = cpu_to_le16(EDMA_TCD_SOFF_SOFF(soff));
+
+ tcd->nbytes = cpu_to_le32(EDMA_TCD_NBYTES_NBYTES(nbytes));
+ tcd->slast = cpu_to_le32(EDMA_TCD_SLAST_SLAST(slast));
+
+ tcd->citer = cpu_to_le16(EDMA_TCD_CITER_CITER(citer));
+ tcd->doff = cpu_to_le16(EDMA_TCD_DOFF_DOFF(doff));
+
+ tcd->dlast_sga = cpu_to_le32(EDMA_TCD_DLAST_SGA_DLAST_SGA(dlast_sga));
+
+ tcd->biter = cpu_to_le16(EDMA_TCD_BITER_BITER(biter));
if (major_int)
csr |= EDMA_TCD_CSR_INT_MAJOR;
@@ -482,7 +492,7 @@ static void fill_tcd_params(struct fsl_edma_engine *edma,
if (enable_sg)
csr |= EDMA_TCD_CSR_E_SG;
- edma_writew(edma, csr, &(tcd->csr));
+ tcd->csr = cpu_to_le16(csr);
}
static struct fsl_edma_desc *fsl_edma_alloc_desc(struct fsl_edma_chan *fsl_chan,
@@ -558,9 +568,9 @@ static struct dma_async_tx_descriptor *fsl_edma_prep_dma_cyclic(
doff = fsl_chan->fsc.addr_width;
}
- fill_tcd_params(fsl_chan->edma, fsl_desc->tcd[i].vtcd, src_addr,
- dst_addr, fsl_chan->fsc.attr, soff, nbytes, 0,
- iter, iter, doff, last_sg, true, false, true);
+ fsl_edma_fill_tcd(fsl_desc->tcd[i].vtcd, src_addr, dst_addr,
+ fsl_chan->fsc.attr, soff, nbytes, 0, iter,
+ iter, doff, last_sg, true, false, true);
dma_buf_next += period_len;
}
@@ -607,35 +617,70 @@ static struct dma_async_tx_descriptor *fsl_edma_prep_slave_sg(
iter = sg_dma_len(sg) / nbytes;
if (i < sg_len - 1) {
last_sg = fsl_desc->tcd[(i + 1)].ptcd;
- fill_tcd_params(fsl_chan->edma, fsl_desc->tcd[i].vtcd,
- src_addr, dst_addr, fsl_chan->fsc.attr,
- soff, nbytes, 0, iter, iter, doff, last_sg,
- false, false, true);
+ fsl_edma_fill_tcd(fsl_desc->tcd[i].vtcd, src_addr,
+ dst_addr, fsl_chan->fsc.attr, soff,
+ nbytes, 0, iter, iter, doff, last_sg,
+ false, false, true);
} else {
last_sg = 0;
- fill_tcd_params(fsl_chan->edma, fsl_desc->tcd[i].vtcd,
- src_addr, dst_addr, fsl_chan->fsc.attr,
- soff, nbytes, 0, iter, iter, doff, last_sg,
- true, true, false);
+ fsl_edma_fill_tcd(fsl_desc->tcd[i].vtcd, src_addr,
+ dst_addr, fsl_chan->fsc.attr, soff,
+ nbytes, 0, iter, iter, doff, last_sg,
+ true, true, false);
}
}
return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, flags);
}
+static struct dma_async_tx_descriptor *
+fsl_edma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst,
+ dma_addr_t src, size_t len, unsigned long tx_flags)
+{
+ struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
+ struct fsl_edma_desc *fsl_desc;
+ u16 soff, doff, attr;
+
+ /*
+ * use 4-bytes data transfer size if all is 4-bytes aligned,
+ * else 2-bytes data transfer size of all is 2-bytes aligned,
+ * otherwise 1-byte tranfer size.
+ */
+ if (src & 0x1 || dst & 0x1 || len & 0x1) {
+ attr = EDMA_TCD_ATTR_SSIZE_8BIT | EDMA_TCD_ATTR_DSIZE_8BIT;
+ soff = 0x1;
+ doff = 0x1;
+ } else if (src & 0x2 || dst & 0x2 || len & 0x2) {
+ attr = EDMA_TCD_ATTR_SSIZE_16BIT | EDMA_TCD_ATTR_DSIZE_16BIT;
+ soff = 0x2;
+ doff = 0x2;
+ } else {
+ attr = EDMA_TCD_ATTR_SSIZE_32BIT | EDMA_TCD_ATTR_DSIZE_32BIT;
+ soff = 0x4;
+ doff = 0x4;
+ }
+
+ fsl_desc = fsl_edma_alloc_desc(fsl_chan, 1);
+ if (!fsl_desc)
+ return NULL;
+ fsl_desc->iscyclic = false;
+
+ fsl_edma_fill_tcd(fsl_desc->tcd[0].vtcd, src, dst, attr, soff, len,
+ 0, 1, 1, doff, 0, true, true, false);
+
+ return vchan_tx_prep(&fsl_chan->vchan, &fsl_desc->vdesc, tx_flags);
+
+}
+
static void fsl_edma_xfer_desc(struct fsl_edma_chan *fsl_chan)
{
- struct fsl_edma_hw_tcd *tcd;
struct virt_dma_desc *vdesc;
vdesc = vchan_next_desc(&fsl_chan->vchan);
if (!vdesc)
return;
fsl_chan->edesc = to_fsl_edma_desc(vdesc);
- tcd = fsl_chan->edesc->tcd[0].vtcd;
- fsl_edma_set_tcd_params(fsl_chan, tcd->saddr, tcd->daddr, tcd->attr,
- tcd->soff, tcd->nbytes, tcd->slast, tcd->citer,
- tcd->biter, tcd->doff, tcd->dlast_sga, tcd->csr);
+ fsl_edma_set_tcd_regs(fsl_chan, fsl_chan->edesc->tcd[0].vtcd);
fsl_edma_enable_request(fsl_chan);
fsl_chan->status = DMA_IN_PROGRESS;
}
@@ -725,6 +770,7 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec,
{
struct fsl_edma_engine *fsl_edma = ofdma->of_dma_data;
struct dma_chan *chan, *_chan;
+ struct fsl_edma_chan *fsl_chan;
unsigned long chans_per_mux = fsl_edma->n_chans / DMAMUX_NR;
if (dma_spec->args_count != 2)
@@ -738,8 +784,10 @@ static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec,
chan = dma_get_slave_channel(chan);
if (chan) {
chan->device->privatecnt++;
- fsl_edma_chan_mux(to_fsl_edma_chan(chan),
- dma_spec->args[1], true);
+ fsl_chan = to_fsl_edma_chan(chan);
+ fsl_chan->slave_id = dma_spec->args[1];
+ fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id,
+ true);
mutex_unlock(&fsl_edma->fsl_edma_mutex);
return chan;
}
@@ -753,6 +801,17 @@ static int fsl_edma_alloc_chan_resources(struct dma_chan *chan)
{
struct fsl_edma_chan *fsl_chan = to_fsl_edma_chan(chan);
+ /*
+ * If the slave id of the channel DMAMUX is not set yet,
+ * this could happy when the channel is requested by the
+ * dma_request_channel() for memory copy purpose instead
+ * of by dts binding, then configure the DMAMUX with the
+ * always on slave id.
+ */
+ if (fsl_chan->slave_id == 0) {
+ fsl_chan->slave_id = SLAVE_ID_ALWAYSON;
+ fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, true);
+ }
fsl_chan->tcd_pool = dma_pool_create("tcd_pool", chan->device->dev,
sizeof(struct fsl_edma_hw_tcd),
32, 0);
@@ -768,6 +827,7 @@ static void fsl_edma_free_chan_resources(struct dma_chan *chan)
spin_lock_irqsave(&fsl_chan->vchan.lock, flags);
fsl_edma_disable_request(fsl_chan);
fsl_edma_chan_mux(fsl_chan, 0, false);
+ fsl_chan->slave_id = 0;
fsl_chan->edesc = NULL;
vchan_get_all_descriptors(&fsl_chan->vchan, &head);
spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags);
@@ -905,6 +965,7 @@ static int fsl_edma_probe(struct platform_device *pdev)
dma_cap_set(DMA_PRIVATE, fsl_edma->dma_dev.cap_mask);
dma_cap_set(DMA_SLAVE, fsl_edma->dma_dev.cap_mask);
dma_cap_set(DMA_CYCLIC, fsl_edma->dma_dev.cap_mask);
+ dma_cap_set(DMA_MEMCPY, fsl_edma->dma_dev.cap_mask);
fsl_edma->dma_dev.dev = &pdev->dev;
fsl_edma->dma_dev.device_alloc_chan_resources
@@ -914,6 +975,7 @@ static int fsl_edma_probe(struct platform_device *pdev)
fsl_edma->dma_dev.device_tx_status = fsl_edma_tx_status;
fsl_edma->dma_dev.device_prep_slave_sg = fsl_edma_prep_slave_sg;
fsl_edma->dma_dev.device_prep_dma_cyclic = fsl_edma_prep_dma_cyclic;
+ fsl_edma->dma_dev.device_prep_dma_memcpy = fsl_edma_prep_memcpy;
fsl_edma->dma_dev.device_control = fsl_edma_control;
fsl_edma->dma_dev.device_issue_pending = fsl_edma_issue_pending;
fsl_edma->dma_dev.device_slave_caps = fsl_dma_device_slave_caps;
@@ -954,6 +1016,43 @@ static int fsl_edma_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int fsl_edma_pm_suspend(struct device *dev)
+{
+ struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev);
+ struct fsl_edma_chan *fsl_chan;
+ int i;
+
+ for (i = 0; i < fsl_edma->n_chans; i++) {
+ fsl_chan = &fsl_edma->chans[i];
+ fsl_edma_chan_mux(fsl_chan, 0, false);
+ }
+
+ return 0;
+}
+
+static int fsl_edma_pm_resume(struct device *dev)
+{
+ struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev);
+ struct fsl_edma_chan *fsl_chan;
+ int i;
+
+ for (i = 0; i < fsl_edma->n_chans; i++) {
+ fsl_chan = &fsl_edma->chans[i];
+ edma_writew(fsl_edma, 0x0, fsl_edma->membase + EDMA_TCD_CSR(i));
+ /* restore the channel slave id configuration */
+ fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, true);
+ }
+
+ edma_writel(fsl_edma, EDMA_CR_ERGA | EDMA_CR_ERCA,
+ fsl_edma->membase + EDMA_CR);
+
+ return 0;
+}
+#endif
+static
+SIMPLE_DEV_PM_OPS(fsl_edma_pm_ops, fsl_edma_pm_suspend, fsl_edma_pm_resume);
+
static const struct of_device_id fsl_edma_dt_ids[] = {
{ .compatible = "fsl,vf610-edma", },
{ /* sentinel */ }
@@ -965,6 +1064,7 @@ static struct platform_driver fsl_edma_driver = {
.name = "fsl-edma",
.owner = THIS_MODULE,
.of_match_table = fsl_edma_dt_ids,
+ .pm = &fsl_edma_pm_ops,
},
.probe = fsl_edma_probe,
.remove = fsl_edma_remove,
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 081e406b3713..e533dc11bba4 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -15,6 +15,8 @@ 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_AC97_CODEC
select SND_SOC_IMX_PCM_DMA if SND_IMX_SOC != n
select SND_SOC_GENERIC_DMAENGINE_PCM
help
@@ -217,6 +219,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 34667209b607..e77d38a7d117 100644
--- a/sound/soc/fsl/fsl_sai.h
+++ b/sound/soc/fsl/fsl_sai.h
@@ -47,6 +47,7 @@
/* SAI Transmit/Recieve 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
@@ -71,7 +72,9 @@
#define FSL_SAI_CR1_RFW_MASK 0x1f
/* SAI Transmit and Recieve 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 (0xff << 26)
#define FSL_SAI_CR2_MSEL_BUS 0
#define FSL_SAI_CR2_MSEL_MCLK1 BIT(26)
@@ -79,6 +82,8 @@
#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)
+#define FSL_SAI_CR2_DIV_MASK 0xff
+#define FSL_SAI_CR2_DIV(x) (x & 0xff)
/* SAI Transmit and Recieve Configuration 3 Register */
#define FSL_SAI_CR3_TRCE BIT(16)
@@ -100,7 +105,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..5fedad8b6734
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai_ac97.c
@@ -0,0 +1,1237 @@
+/*
+ * 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;
+ atomic_t playing;
+ atomic_t capturing;
+};
+
+#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;
+
+ struct imx_pcm_runtime_data *iprtd;
+};
+
+#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;
+ 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 (iprtd != NULL && atomic_read(&iprtd->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;
+
+ async_tx_ack(sai->dma_rx_desc);
+}
+
+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, txbufid, rxbufid, curbufid;
+ unsigned long flags;
+ int timeout = 20;
+ int ret = 0;
+
+ /*
+ * 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;
+ txbufid = (SAI_AC97_RBUF_SIZE_TOT - tx_state.residue) / SAI_AC97_DMABUF_SIZE;
+ txbufid += 2;
+ txbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES;
+ tx_aclink = (struct ac97_tx *)(info->tx_buf.area + (txbufid * SAI_AC97_DMABUF_SIZE));
+
+ /* We do assume that the RX and TX DMA are running synchrounous */
+ rxbufid = (txbufid + 1);
+ rxbufid %= SAI_AC97_RBUF_COUNT * SAI_AC97_RBUF_FRAMES;
+
+ /* 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 until the TX frame has been sent and the RX frame has been
+ * transmitted back and transferred to the RX buffer by the DMA.
+ * Also take into account that the bufid might wrap around.
+ *
+ * Note also that the transmit DMA is always a bit ahead due to the
+ * transmit FIFO of 32 bytes (~2 AC97 frames).
+ *
+ * TX ring buffer
+ * |------|------|------|------|------|------|------|------|
+ * | | |txbuf | |txbuf | | | |
+ * | | |start | | | | | |
+ * |------|------|------|------|------|------|------|------|
+ *
+ * RX ring buffer
+ * |------|------|------|------|------|------|------|------|
+ * | |rxbuf | | | |rxbuf | | |
+ * | |start | | | | | | |
+ * |------|------|------|------|------|------|------|------|
+ *
+ */
+ do {
+ 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 (likely(rxbufid > rxbufidstart) &&
+ (curbufid > rxbufid || curbufid < rxbufidstart))
+ break;
+
+ /* Wrap-around case */
+ if (unlikely(rxbufid < rxbufidstart) &&
+ (curbufid > rxbufid && curbufid < rxbufidstart))
+ break;
+
+ udelay(50);
+ } while (--timeout);
+
+ if (!timeout) {
+ pr_err("timeout, current buffers: tx: %d, rx: %d, rx cur: %d\n",
+ txbufid, rxbufid, curbufid);
+ ret = -ETIMEDOUT;
+ goto clear_command;
+ }
+
+ /* At this point, we should have an answer in the RX buffer... */
+ rx_aclink = (struct ac97_rx *)(info->rx_buf.area + rxbufid * SAI_AC97_DMABUF_SIZE);
+
+ if (isread) {
+ if (rx_aclink->slot_valid & (1 << 11 | 1 << 10) &&
+ rx_aclink->regindex == reg)
+ *val = rx_aclink->cmddata;
+ else {
+ pr_warn("no valid answer for read command received\n");
+ pr_warn("current rx buffers, start: %d, data: %d, cur: %d\n",
+ rxbufidstart, rxbufid, curbufid);
+ ret = -EBADMSG;
+ goto clear_command;
+ }
+ }
+
+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 0;
+}
+
+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);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver fsl_sai_ac97_dai = {
+ .name = "fsl-sai-ac97-pcm",
+ .ac97_control = 1,
+ .probe = fsl_sai_dai_probe,
+ .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_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_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,
+};
+
+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)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct imx_pcm_runtime_data *iprtd = runtime->private_data;
+
+ 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(&iprtd->playing, 1);
+ else
+ atomic_set(&iprtd->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(&iprtd->playing, 0);
+ else
+ atomic_set(&iprtd->capturing, 0);
+ if (!atomic_read(&iprtd->playing) &&
+ !atomic_read(&iprtd->capturing))
+ 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;
+
+ atomic_set(&iprtd->playing, 0);
+ atomic_set(&iprtd->capturing, 0);
+
+ ret = snd_pcm_hw_constraint_integer(substream->runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ kfree(iprtd);
+ return ret;
+ }
+
+ info->iprtd = 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;
+
+ info->iprtd = 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;
+ }
+
+ 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 irq
+ 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;
+ }
+
+ for (i = 0; i < FSL_SAI_MCLK_MAX; i++) {
+ sprintf(tmp, "mclk%d", i + 1);
+ 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 + 1, 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);
+
+ return 0;
+}
+
+static int fsl_sai_ac97_resume(struct device *dev)
+{
+ struct fsl_sai_ac97 *sai = dev_get_drvdata(dev);
+
+ /* 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;
+}
+#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..bc593d6225ae
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai_clk.c
@@ -0,0 +1,198 @@
+/*
+ * 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 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,
+};
+
+static int fsl_sai_clk_probe(struct platform_device *pdev)
+{
+ struct fsl_sai *sai;
+ struct resource *res;
+ void __iomem *base;
+ char tmp[8];
+ 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;
+ }
+
+ for (i = 0; i < FSL_SAI_MCLK_MAX; i++) {
+ sprintf(tmp, "mclk%d", i + 1);
+ 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 + 1, PTR_ERR(sai->mclk_clk[i]));
+ sai->mclk_clk[i] = NULL;
+ }
+ }
+
+ /* 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));
+
+ /* enable AC97 master clock */
+ regmap_update_bits(sai->regmap, FSL_SAI_TCSR, FSL_SAI_CSR_BCE,
+ FSL_SAI_CSR_BCE);
+
+ return clk_prepare_enable(sai->bus_clk);
+}
+
+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,
+ },
+};
+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..c510395649d5
--- /dev/null
+++ b/sound/soc/fsl/fsl_sai_wm9712.c
@@ -0,0 +1,129 @@
+/*
+ * 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);
+ }
+
+ return ret;
+
+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);