diff options
author | Stefan Agner <stefan.agner@toradex.com> | 2015-02-02 15:56:32 +0100 |
---|---|---|
committer | Stefan Agner <stefan.agner@toradex.com> | 2015-02-02 15:56:32 +0100 |
commit | 67bacaabc4e9cf5bade60354736d87a90325d5ec (patch) | |
tree | 1a584115adc173a91c1b818f44d5ef57ea2a8c68 | |
parent | 43328a22788c8d856f30a9b0b909e00b7a0c6ecc (diff) | |
parent | 5757b6753c3290e7b7e8ce2047368eac572392ee (diff) |
Merge branch 'vf610-ac97-sai' into toradex_vf_3.18-next
-rw-r--r-- | arch/arm/boot/dts/vf500.dtsi | 4 | ||||
-rw-r--r-- | arch/arm/boot/dts/vf610-colibri.dtsi | 80 | ||||
-rw-r--r-- | arch/arm/boot/dts/vfxxx.dtsi | 19 | ||||
-rw-r--r-- | arch/arm/configs/colibri_vf_defconfig | 4 | ||||
-rw-r--r-- | arch/arm/mach-imx/clk-vf610.c | 7 | ||||
-rw-r--r-- | drivers/dma/fsl-edma.c | 290 | ||||
-rw-r--r-- | sound/soc/fsl/Kconfig | 10 | ||||
-rw-r--r-- | sound/soc/fsl/Makefile | 3 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_sai.h | 7 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_sai_ac97.c | 1237 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_sai_clk.c | 198 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_sai_wm9712.c | 129 |
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); |