[alsa-devel] [PATCH 0/3] ASoC: Add Freescale i.MX S/PDIF controller driver
* This series of patches add i.MX S/PDIF drivers, including CPU DAI, machine. * It also add two missing sample rates support for spdif dummy codec drivers
Nicolin Chen (3): ASoC: codec: spdif: Add S20_3LE and S24_LE support for dummy codec drivers ASoC: fsl: Add S/PDIF CPU DAI driver ASoC: fsl: Add S/PDIF machine driver
.../devicetree/bindings/sound/fsl,spdif.txt | 63 + .../devicetree/bindings/sound/imx-audio-spdif.txt | 29 + sound/soc/codecs/spdif_receiver.c | 2 + sound/soc/codecs/spdif_transmitter.c | 5 +- sound/soc/fsl/Kconfig | 14 + sound/soc/fsl/Makefile | 4 + sound/soc/fsl/fsl_spdif.c | 1276 ++++++++++++++++++++ sound/soc/fsl/fsl_spdif.h | 233 ++++ sound/soc/fsl/imx-spdif.c | 134 ++ 9 files changed, 1758 insertions(+), 2 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/fsl,spdif.txt create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-spdif.txt create mode 100644 sound/soc/fsl/fsl_spdif.c create mode 100644 sound/soc/fsl/fsl_spdif.h create mode 100644 sound/soc/fsl/imx-spdif.c
Generally, S/PDIF supports 20bit and optional 24bit samples. Thus add these two formats for the dummy codec drivers.
If one S/PDIF controller has its own limitation, its CPU DAI driver should set the supported format by its own circumstance, since the soc-pcm driver will use the intersection of cpu_dai's formats and codec_dai's formats.
Signed-off-by: Nicolin Chen b42378@freescale.com --- sound/soc/codecs/spdif_receiver.c | 2 ++ sound/soc/codecs/spdif_transmitter.c | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/spdif_receiver.c b/sound/soc/codecs/spdif_receiver.c index e9d7881..26d3474 100644 --- a/sound/soc/codecs/spdif_receiver.c +++ b/sound/soc/codecs/spdif_receiver.c @@ -25,6 +25,8 @@
#define STUB_RATES SNDRV_PCM_RATE_8000_192000 #define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE)
static struct snd_soc_codec_driver soc_codec_spdif_dir; diff --git a/sound/soc/codecs/spdif_transmitter.c b/sound/soc/codecs/spdif_transmitter.c index 1828049..efc3d88 100644 --- a/sound/soc/codecs/spdif_transmitter.c +++ b/sound/soc/codecs/spdif_transmitter.c @@ -25,8 +25,9 @@ #define DRV_NAME "spdif-dit"
#define STUB_RATES SNDRV_PCM_RATE_8000_96000 -#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE - +#define STUB_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE)
static struct snd_soc_codec_driver soc_codec_spdif_dit;
On Wed, Jul 31, 2013 at 08:07:05PM +0800, Nicolin Chen wrote:
Generally, S/PDIF supports 20bit and optional 24bit samples. Thus add these two formats for the dummy codec drivers.
Applied, thanks. Please check the mailing lists you're posting to - you've got the DT list wrong here and you shouldn't post non-binding reviews there anyway.
On Wed, Jul 31, 2013, Mark Brown wrote:
Applied, thanks. Please check the mailing lists you're posting to - you've got the DT list wrong here and you shouldn't post non-binding reviews there anyway.
Sorry that I forgot to update the DT list addr but using an old one. I'll pay attention to it next time. Thank you.
This patch add S/PDIF controller driver for Freescale SoC.
Signed-off-by: Nicolin Chen b42378@freescale.com --- .../devicetree/bindings/sound/fsl,spdif.txt | 63 + sound/soc/fsl/Kconfig | 3 + sound/soc/fsl/Makefile | 2 + sound/soc/fsl/fsl_spdif.c | 1276 ++++++++++++++++++++ sound/soc/fsl/fsl_spdif.h | 233 ++++ 5 files changed, 1577 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/fsl,spdif.txt create mode 100644 sound/soc/fsl/fsl_spdif.c create mode 100644 sound/soc/fsl/fsl_spdif.h
diff --git a/Documentation/devicetree/bindings/sound/fsl,spdif.txt b/Documentation/devicetree/bindings/sound/fsl,spdif.txt new file mode 100644 index 0000000..a655800 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,spdif.txt @@ -0,0 +1,63 @@ +Freescale Sony/Philips Digital Interface Format (S/PDIF) Controller + +The Freescale S/PDIF audio block is a stereo transceiver that allows the +processor to receive and transmit digital audio via an coaxial cable or +a fibre cable. + +Required properties: + + - compatible : Compatible list, contains "fsl,spdif". + + - reg : Offset and length of the register set for the device. + + - interrupts : <a b> where a is the interrupt number and b is a field that + represents an encoding of the sense and level information for the interrupt. + This should be encoded based on the information in section 2) depending on + the type of interrupt controller you have. + + - clocks : The phandle for the clock ID number registered in clock tree. + + - fsl,spdif-dma-events: The dma event ID numbers for Tx and Rx. + +Optional properties: + + - rx-clk-source : The clock cource for Rx. Need to set this source according + to the SoC datasheet in SPDIF_SRPC section. If absent, the default source is + value 0x0 - if (DPLL Locked) SPDIF_RxClk else extal. + + - tx-clk-source : The clock cources for Tx. There're three sources, each for + different supported sample rate, sequentially 32000Hz, 44100Hz and 48000Hz. + Need to set this source according to the SoC datasheet in SPDIF_STC section. + If absent, the default source is value 0x1 - CCM spdif0_clk_root input. + + - tx-clk-div : The clock divider factor for Tx clock. There're three values, + each for different supported sample rate, sequentially 32000Hz 44100Hz 48000Hz. + Need to set this source according to the clock rate from the clock source. + If absent, the default divider factor is <37 23 37> by using spdif0_clk source. + +Example: + +spdif: spdif@02004000 { + compatible = "fsl,fsl-spdif"; + reg = <0x02004000 0x4000>; + interrupts = <0 52 0x04>; + clocks = <&clks 197>; + fsl,spdif-dma-events = <15 14>; + + rx-clk-source = <0x0>; + tx-clk-source = < + 0x1 /* Tx clk src for 32KHz: CCM spdif0_clk_root input */ + 0x1 /* Tx clk src for 44KHz: CCM spdif0_clk_root input */ + 0x1 /* Tx clk src for 48KHz: CCM spdif0_clk_root input */ + >; + + /* + * In i.MX6Q SoC, the clock rate of spdif0_clk_root will be 454.7MHz. + * 32KHz: 454.7MHz / 6(ccm) / 37(spdif) = 32,003 Hz ~ 0.01% error + * 44.1KHz: 454.7MHz / 7(ccm) / 23(spdif) = 44,128 Hz ~ 0.06% error + * 48KHz: 454.7MHz / 4(ccm) / 37(spdif) = 48,004 Hz ~ 0.01% error + */ + tx-clk-div = <37 23 37>; + + status = "okay"; +}; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3a79d01..156b794 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,6 +1,9 @@ config SND_SOC_FSL_SSI tristate
+config SND_SOC_FSL_SPDIF + tristate + config SND_SOC_FSL_UTILS tristate
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index d4b4aa8..4b5970e 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -12,9 +12,11 @@ obj-$(CONFIG_SND_SOC_P1022_RDK) += snd-soc-p1022-rdk.o
# Freescale PowerPC SSI/DMA Platform Support snd-soc-fsl-ssi-objs := fsl_ssi.o +snd-soc-fsl-spdif-objs := fsl_spdif.o snd-soc-fsl-utils-objs := fsl_utils.o snd-soc-fsl-dma-objs := fsl_dma.o obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o +obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c new file mode 100644 index 0000000..7b10bed --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.c @@ -0,0 +1,1276 @@ +/* + * Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Based on stmp3xxx_spdif_dai.c + * Vladimir Barinov <vbarinov@embeddedalley.com> + * Copyright 2008 SigmaTel, Inc + * Copyright 2008 Embedded Alley Solutions, Inc + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_irq.h> + +#include <sound/asoundef.h> +#include <sound/soc.h> +#include <sound/dmaengine_pcm.h> + +#include "fsl_spdif.h" +#include "imx-pcm.h" + +#define FSL_SPDIF_TXFIFO_WML 0x8 +#define FSL_SPDIF_RXFIFO_WML 0x8 + +#define INTR_FOR_PLAYBACK (INT_TXFIFO_RESYNC) +#define INTR_FOR_CAPTURE (INT_SYM_ERR | INT_BIT_ERR | INT_URX_FUL | INT_URX_OV|\ + INT_QRX_FUL | INT_QRX_OV | INT_UQ_SYNC | INT_UQ_ERR |\ + INT_RXFIFO_RESYNC | INT_LOSS_LOCK | INT_DPLL_LOCKED) + +enum fsl_spdif_type { + FSL_IMX6Q_SPDIF, + FSL_IMX6SL_SPDIF, +}; + +static struct platform_device_id fsl_spdif_devtype[] = { + { + .name = "imx6q-spdif", + .driver_data = FSL_IMX6Q_SPDIF, + }, + { + .name = "imx6sl-spdif", + .driver_data = FSL_IMX6SL_SPDIF, + }, +}; +MODULE_DEVICE_TABLE(platform, fsl_spdif_devtype); + +static const struct of_device_id fsl_spdif_dt_ids[] = { + { .compatible = "fsl,fsl-spdif", .data = &fsl_spdif_devtype[FSL_IMX6Q_SPDIF], }, + { .compatible = "fsl,imx6q-spdif", .data = &fsl_spdif_devtype[FSL_IMX6Q_SPDIF], }, + { .compatible = "fsl,imx6dl-spdif", .data = &fsl_spdif_devtype[FSL_IMX6SL_SPDIF], }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_spdif_dt_ids); + +/* spdif_devtype indicates which module version is being used */ +static u8 spdif_devtype; + +/* + * SPDIF control structure + * Defines channel status, subcode and Q sub + */ +struct spdif_mixer_control { + /* spinlock to access control data */ + spinlock_t ctl_lock; + + /* IEC958 channel tx status bit */ + unsigned char ch_status[4]; + + /* User bits */ + unsigned char subcode[2 * SPDIF_UBITS_SIZE]; + + /* Q subcode part of user bits */ + unsigned char qsub[2 * SPDIF_QSUB_SIZE]; + + /* buffer ptrs for writer */ + u32 upos; + u32 qpos; + + /* ready buffer index of the two buffers */ + u32 ready_buf; +}; + +struct fsl_spdif_priv { + struct spdif_mixer_control fsl_spdif_control; + struct snd_soc_dai_driver cpu_dai_drv; + struct reg_spdif __iomem *spdif; + struct platform_device *pdev; + dma_addr_t spdif_phys; + atomic_t dpll_locked; + u32 irq; + u8 rxclk_src; + u8 txclk_src[SPDIF_TXRATE_MAX]; + u8 txclk_div[SPDIF_TXRATE_MAX]; + struct clk *clk; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_dai_dma_data dma_params_rx; + struct imx_dma_data filter_data_tx; + struct imx_dma_data filter_data_rx; + + char name[1]; +}; + + +/* All the registers of SPDIF are 24-bit implemented */ +static u32 spdif_read(u32 __iomem *addr) +{ + return __raw_readl(addr) & 0xffffff; +} + +static void spdif_write(u32 __iomem *addr, u32 val) +{ + __raw_writel(val & 0xffffff, addr); +} + +static void spdif_setbits(u32 __iomem *addr, u32 bits) +{ + u32 val; + + val = spdif_read(addr); + val |= bits; + spdif_write(addr, val); +} + +static void spdif_clrbits(u32 __iomem *addr, u32 bits) +{ + u32 val; + + val = spdif_read(addr); + val &= ~bits; + spdif_write(addr, val); +} + +static void spdif_setmask(u32 __iomem *addr, u32 mask, u32 bits) +{ + u32 val; + + val = spdif_read(addr); + val = (val & ~mask) | (bits & mask); + spdif_write(addr, val); +} + +#ifdef DEBUG +static void dumpregs(struct fsl_spdif_priv *spdif_priv) +{ + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + u32 val, i; + + clk_enable(spdif_priv->clk); + + /* Valid address set of SPDIF is {[0x0-0x38], 0x44, 0x50} */ + for (i = 0 ; i <= 0x38 ; i += 4) { + val = spdif_read(&spdif->scr + i / 4); + dev_dbg(&pdev->dev, "REG 0x%02x = 0x%06x\n", i, val); + } + + i = 0x44; + val = spdif_read(&spdif->scr + i / 4) & 0xffffff; + dev_dbg(&pdev->dev, "REG 0x%02x = 0x%06x\n", i, val); + + i = 0x50; + val = spdif_read(&spdif->scr + i / 4) & 0xffffff; + dev_dbg(&pdev->dev, "REG 0x%02x = 0x%06x\n", i, val); + + clk_disable(spdif_priv->clk); +} +#else +static void dumpregs(struct fsl_spdif_priv *spdif_priv) {} +#endif + + +/* DPLL locked and lock loss interrupt handler */ +static void spdif_irq_dpll_lock(struct fsl_spdif_priv *spdif_priv) +{ + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + u32 locked = spdif_read(&spdif->srpc) & SRPC_DPLL_LOCKED; + + dev_dbg(&pdev->dev, "isr: Rx dpll %s \n", + locked ? "locked" : "loss lock"); + atomic_set(&spdif_priv->dpll_locked, locked ? 1 : 0); +} + +/* Receiver found illegal symbol interrupt handler */ +static void spdif_irq_sym_error(struct fsl_spdif_priv *spdif_priv) +{ + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + + dev_dbg(&pdev->dev, "isr: receiver found illegal symbol\n"); + if (!atomic_read(&spdif_priv->dpll_locked)) { + /* dpll unlocked seems no audio stream */ + spdif_clrbits(&spdif->sie, INT_SYM_ERR); + } +} + +/* U/Q Channel receive register full */ +static void spdif_irq_uqrx_full(struct fsl_spdif_priv *spdif_priv, char name) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + u32 *pos, size, val; + u32 __iomem *reg; + + switch (name) { + case 'U': + pos = &ctrl->upos; + size = SPDIF_UBITS_SIZE; + reg = &spdif->sru; + break; + case 'Q': + pos = &ctrl->qpos; + size = SPDIF_QSUB_SIZE; + reg = &spdif->srq; + break; + default: + return; + } + + dev_dbg(&pdev->dev, "isr: %c Channel receive register full\n", name); + + if (*pos >= size * 2) { + *pos = 0; + } else if (unlikely((*pos % size) + 3 > size)) { + dev_err(&pdev->dev, "User bit receivce buffer overflow\n"); + return; + } + val = spdif_read(reg); + ctrl->subcode[*pos++] = val >> 16; + ctrl->subcode[*pos++] = val >> 8; + ctrl->subcode[*pos++] = val; +} + +/* U/Q Channel sync found */ +static void spdif_irq_uq_sync(struct fsl_spdif_priv *spdif_priv) { + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct platform_device *pdev = spdif_priv->pdev; + + dev_dbg(&pdev->dev, "isr: U/Q Channel sync found\n"); + + /* U/Q buffer reset */ + if (ctrl->qpos == 0) + return; + + /* set ready to this buffer */ + ctrl->ready_buf = (ctrl->qpos - 1) / SPDIF_QSUB_SIZE + 1; +} + +/* U/Q Channel framing error */ +static void spdif_irq_uq_err(struct fsl_spdif_priv *spdif_priv) { + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + u32 val; + + dev_dbg(&pdev->dev, "isr: U/Q Channel framing error\n"); + + /* read U/Q data and do buffer reset */ + val = spdif_read(&spdif->sru); + val = spdif_read(&spdif->srq); + + /* drop this U/Q buffer */ + ctrl->ready_buf = 0; + ctrl->upos = 0; + ctrl->qpos = 0; +} + +/* Get spdif interrupt status and clear the interrupt */ +static u32 spdif_intr_status_clear(struct fsl_spdif_priv *spdif_priv) +{ + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + u32 val = spdif_read(&spdif->sisc); + + val &= spdif_read(&spdif->sie); + spdif_write(&spdif->sisc, val); + + return val; +} + +static irqreturn_t spdif_isr(int irq, void *devid) +{ + struct fsl_spdif_priv *spdif_priv = (struct fsl_spdif_priv *)devid; + struct platform_device *pdev = spdif_priv->pdev; + u32 sis; + + sis = spdif_intr_status_clear(spdif_priv); + + if (sis & INT_DPLL_LOCKED) + spdif_irq_dpll_lock(spdif_priv); + + if (sis & INT_TXFIFO_UNOV) + dev_dbg(&pdev->dev, "isr: Tx FIFO under/overrun\n"); + + if (sis & INT_TXFIFO_RESYNC) + dev_dbg(&pdev->dev, "isr: Tx FIFO resync\n"); + + if (sis & INT_CNEW) + dev_dbg(&pdev->dev, "isr: cstatus new\n"); + + if (sis & INT_VAL_NOGOOD) + dev_dbg(&pdev->dev, "isr: validity flag no good\n"); + + if (sis & INT_SYM_ERR) + spdif_irq_sym_error(spdif_priv); + + if (sis & INT_BIT_ERR) + dev_dbg(&pdev->dev, "isr: receiver found parity bit error\n"); + + if (sis & INT_URX_FUL) + spdif_irq_uqrx_full(spdif_priv, 'U'); + + if (sis & INT_URX_OV) + dev_dbg(&pdev->dev, "isr: U Channel receive register overrun\n"); + + if (sis & INT_QRX_FUL) + spdif_irq_uqrx_full(spdif_priv, 'Q'); + + if (sis & INT_QRX_OV) + dev_dbg(&pdev->dev, "isr: Q Channel receive register overrun\n"); + + if (sis & INT_UQ_SYNC) + spdif_irq_uq_sync(spdif_priv); + + if (sis & INT_UQ_ERR) + spdif_irq_uq_err(spdif_priv); + + if (sis & INT_RXFIFO_UNOV) + dev_dbg(&pdev->dev, "isr: Rx FIFO under/overrun\n"); + + if (sis & INT_RXFIFO_RESYNC) + dev_dbg(&pdev->dev, "isr: Rx FIFO resync\n"); + + if (sis & INT_LOSS_LOCK) + spdif_irq_dpll_lock(spdif_priv); + + /* FIXME: Write Tx FIFO to clear TxEm */ + if (sis & INT_TX_EM) + dev_dbg(&pdev->dev, "isr: Tx FIFO empty\n"); + + /* FIXME: Read Rx FIFO to clear RxFIFOFul */ + if (sis & INT_RXFIFO_FUL) + dev_dbg(&pdev->dev, "isr: Rx FIFO full\n"); + + return IRQ_HANDLED; +} + +static void spdif_softreset(struct fsl_spdif_priv *spdif_priv) +{ + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + int cycle = 100; + + spdif_write(&spdif->scr, SCR_SOFT_RESET); + + /* RESET bit would be cleared after finishing its reset procedure */ + while ((spdif_read(&spdif->scr) & SCR_SOFT_RESET) && cycle--); +} + +static void spdif_set_cstatus(struct spdif_mixer_control *ctrl, + u8 mask, u8 cstatus) +{ + ctrl->ch_status[3] &= ~mask; + ctrl->ch_status[3] |= cstatus & mask; +} + +static u8 reverse_bits(u8 input) +{ + u8 i, output = 0; + for (i = 8 ; i > 0 ; i--) { + output <<= 1; + output |= input & 0x01; + input >>= 1; + } + return output; +} + +static void spdif_write_channel_status(struct fsl_spdif_priv *spdif_priv) +{ + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + u32 ch_status; + + ch_status = (reverse_bits(ctrl->ch_status[0]) << 16) | + (reverse_bits(ctrl->ch_status[1]) << 8) | + reverse_bits(ctrl->ch_status[2]); + spdif_write(&spdif->stcsch, ch_status); + + ch_status = reverse_bits(ctrl->ch_status[3]) << 16; + spdif_write(&spdif->stcscl, ch_status); + + dev_dbg(&pdev->dev, "STCSCH: 0x%06x\n", spdif_read(&spdif->stcsch)); + dev_dbg(&pdev->dev, "STCSCL: 0x%06x\n", spdif_read(&spdif->stcscl)); +} + +/* + * Check if the clock source setting cares about DPLL Locked condition + * + * The DPLL locked condition should depend on SoC design, being different + * between different spdif_devtype. + */ +static inline bool spdif_rxclk_lock_check(enum spdif_rxclk_src clksrc) { + + switch (spdif_devtype) { + case FSL_IMX6Q_SPDIF: + if (clksrc <= SRPC_CLKSRC_4) + return true; + else if (clksrc <= SRPC_CLKSRC_9) + return false; + else if (clksrc <= SRPC_CLKSRC_11) + return true; + else + return false; + break; + case FSL_IMX6SL_SPDIF: + if (clksrc <= SRPC_CLKSRC_3) + return true; + else + return false; + break; + default: + return false; + } +} + +/* Set SPDIF PhaseConfig register for rx clock */ +static int spdif_set_rx_clksrc(struct fsl_spdif_priv *spdif_priv, + enum spdif_gainsel gainsel, int dpll_locked) +{ + enum spdif_rxclk_src clksrc = spdif_priv->rxclk_src; + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + + if (clksrc >= SRPC_CLKSRC_MAX || gainsel >= GAINSEL_MULTI_MAX) + return -EINVAL; + + if (!dpll_locked && spdif_rxclk_lock_check(clksrc)) + clksrc += SRPC_CLKSRC_SEL_LOCKED; + + spdif_setmask(&spdif->srpc, SRPC_CLKSRC_SEL_MASK | SRPC_GAINSEL_MASK, + SRPC_CLKSRC_SEL_SET(clksrc) | SRPC_GAINSEL_SET(gainsel)); + + return 0; +} + +static int spdif_clk_set_rate(struct clk *clk, unsigned long rate) +{ + unsigned long rate_actual; + + rate_actual = clk_round_rate(clk, rate); + clk_set_rate(clk, rate_actual); + + return 0; +} + +static int spdif_set_sample_rate(struct snd_pcm_substream *substream, + int sample_rate) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + unsigned long clk = -1, div = 1, csfs = 0; + u32 stc, mask; + + switch (sample_rate) { + case 32000: + clk = spdif_priv->txclk_src[SPDIF_TXRATE_32000]; + div = spdif_priv->txclk_div[SPDIF_TXRATE_32000]; + csfs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + clk = spdif_priv->txclk_src[SPDIF_TXRATE_44100]; + div = spdif_priv->txclk_div[SPDIF_TXRATE_44100]; + csfs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + clk = spdif_priv->txclk_src[SPDIF_TXRATE_48000]; + div = spdif_priv->txclk_div[SPDIF_TXRATE_48000]; + csfs = IEC958_AES3_CON_FS_48000; + break; + default: + dev_err(&pdev->dev, "unsupported samplerate %d\n", sample_rate); + return -EINVAL; + } + + if (clk < 0) { + dev_err(&pdev->dev, "no defined %d clk src\n", sample_rate); + return -EINVAL; + } + + /* + * The S/PDIF block needs a clock of 64 * fs * div. The S/PDIF block + * will divide by (div). So request 64 * fs * (div+1) which will + * get rounded. + */ + spdif_clk_set_rate(spdif_priv->clk, 64 * sample_rate * (div + 1)); + + dev_dbg(&pdev->dev, "expected clock rate = %d\n", + (int)(64 * sample_rate * div)); + dev_dbg(&pdev->dev, "acutal clock rate = %d\n", + (int)clk_get_rate(spdif_priv->clk)); + + /* set fs field in consumer channel status */ + spdif_set_cstatus(ctrl, IEC958_AES3_CON_FS, csfs); + + /* select clock source and divisor */ + stc = STC_TXCLK_ALL_EN | STC_TXCLK_SRC_SET(clk) | STC_TXCLK_DIV(div); + mask = STC_TXCLK_ALL_EN_MASK | STC_TXCLK_SRC_MASK | STC_TXCLK_DIV_MASK; + spdif_setmask(&spdif->stc, mask, stc); + + dev_dbg(&pdev->dev, "set sample rate to %d\n", sample_rate); + + return 0; +} + +int fsl_spdif_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + u32 scr, mask; + int ret = 0; + + /* enable spdif_xtal_clk */ + ret = clk_enable(spdif_priv->clk); + if (ret) + return ret; + + /* Reset module and interrupts only for first initialization */ + if (!cpu_dai->active) { + spdif_softreset(spdif_priv); + + /* disable all the interrupts */ + spdif_clrbits(&spdif->sie, 0xffffff); + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + scr = SCR_TXFIFO_AUTOSYNC | SCR_TXFIFO_CTRL_NORMAL | + SCR_TXSEL_NORMAL | SCR_USRC_SEL_CHIP | + SCR_TXFIFO_FSEL_IF8; + mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | + SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | + SCR_TXFIFO_FSEL_MASK; + } else { + scr = SCR_RXFIFO_FSEL_IF8 | SCR_RXFIFO_AUTOSYNC; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; + } + spdif_setmask(&spdif->scr, mask, scr); + + /* Power up SPDIF module */ + spdif_clrbits(&spdif->scr, SCR_LOW_POWER); + + return 0; +} + +static void fsl_spdif_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + u32 scr, mask; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + scr = 0; + mask = SCR_TXFIFO_AUTOSYNC_MASK | SCR_TXFIFO_CTRL_MASK | + SCR_TXSEL_MASK | SCR_USRC_SEL_MASK | + SCR_TXFIFO_FSEL_MASK; + } else { + scr = SCR_RXFIFO_OFF | SCR_RXFIFO_CTL_ZERO; + mask = SCR_RXFIFO_FSEL_MASK | SCR_RXFIFO_AUTOSYNC_MASK| + SCR_RXFIFO_CTL_MASK | SCR_RXFIFO_OFF_MASK; + } + spdif_setmask(&spdif->scr, mask, scr); + + /* Power down SPDIF module only if tx&rx are both inactive */ + if (!cpu_dai->active) { + spdif_intr_status_clear(spdif_priv); + spdif_setbits(&spdif->scr, SCR_LOW_POWER); + } + + /* disable spdif clock */ + clk_disable(spdif_priv->clk); +} + +static int fsl_spdif_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + struct platform_device *pdev = spdif_priv->pdev; + u32 sample_rate = params_rate(params); + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = spdif_set_sample_rate(substream, sample_rate); + if (ret) { + dev_err(&pdev->dev, "%s: set sample rate failed: %d\n", + __func__, sample_rate); + return ret; + } + spdif_set_cstatus(ctrl, IEC958_AES3_CON_CLOCK, + IEC958_AES3_CON_CLOCK_1000PPM); + spdif_write_channel_status(spdif_priv); + } else { + /* setup rx clock source */ + spdif_set_rx_clksrc(spdif_priv, SPDIF_DEFAULT_GAINSEL, 1); + } + + return ret; +} + +static int fsl_spdif_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai); + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + int is_playack = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + u32 intr = is_playack ? INTR_FOR_PLAYBACK : INTR_FOR_CAPTURE; + u32 dmaen = is_playack ? SCR_DMA_TX_EN : SCR_DMA_RX_EN;; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spdif_setbits(&spdif->sie, intr); + spdif_setbits(&spdif->scr, dmaen); + dumpregs(spdif_priv); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spdif_clrbits(&spdif->scr, dmaen); + spdif_clrbits(&spdif->sie, intr); + break; + default: + return -EINVAL; + } + + return 0; +} + +struct snd_soc_dai_ops fsl_spdif_dai_ops = { + .startup = fsl_spdif_startup, + .hw_params = fsl_spdif_hw_params, + .trigger = fsl_spdif_trigger, + .shutdown = fsl_spdif_shutdown, +}; + + +/* + * ============================================ + * FSL SPDIF IEC958 controller(mixer) functions + * + * Channel status get/put control + * User bit value get/put control + * Valid bit value get control + * DPLL lock status get control + * User bit sync mode selection control + * ============================================ + */ + +static int mxc_pb_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +static int mxc_pb_spdif_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + + uvalue->value.iec958.status[0] = ctrl->ch_status[0]; + uvalue->value.iec958.status[1] = ctrl->ch_status[1]; + uvalue->value.iec958.status[2] = ctrl->ch_status[2]; + uvalue->value.iec958.status[3] = ctrl->ch_status[3]; + + return 0; +} + +static int mxc_pb_spdif_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + + ctrl->ch_status[0] = uvalue->value.iec958.status[0]; + ctrl->ch_status[1] = uvalue->value.iec958.status[1]; + ctrl->ch_status[2] = uvalue->value.iec958.status[2]; + ctrl->ch_status[3] = uvalue->value.iec958.status[3]; + + clk_enable(spdif_priv->clk); + + spdif_write_channel_status(spdif_priv); + + clk_disable(spdif_priv->clk); + + return 0; +} + +static int fsl_spdif_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + + return 0; +} + +/* Get channel status from SPDIF_RX_CCHAN register */ +static int fsl_spdif_capture_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + u32 cstatus; + + clk_enable(spdif_priv->clk); + + if (!(spdif_read(&spdif->sisc) & INT_CNEW)) { + clk_disable(spdif_priv->clk); + return -EAGAIN; + } + + cstatus = spdif_read(&spdif->srcsch); + ucontrol->value.iec958.status[0] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[1] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[2] = cstatus & 0xFF; + + cstatus = spdif_read(&spdif->srcscl); + ucontrol->value.iec958.status[3] = (cstatus >> 16) & 0xFF; + ucontrol->value.iec958.status[4] = (cstatus >> 8) & 0xFF; + ucontrol->value.iec958.status[5] = cstatus & 0xFF; + + /* clear intr */ + spdif_write(&spdif->sisc, INT_CNEW); + + clk_disable(spdif_priv->clk); + + return 0; +} + +/* + * Get User bits (subcode) from chip value which readed out + * in UChannel register. + */ +static int fsl_spdif_subcode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ctrl->ctl_lock, flags); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_UBITS_SIZE; + memcpy(&ucontrol->value.iec958.subcode[0], + &ctrl->subcode[idx], SPDIF_UBITS_SIZE); + } else { + ret = -EAGAIN; + } + spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; +} + +/* Q-subcode infomation. The byte size is SPDIF_UBITS_SIZE/8 */ +static int fsl_spdif_qinfo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = SPDIF_QSUB_SIZE; + + return 0; +} + +/* Get Q subcode from chip value which readed out in QChannel register */ +static int fsl_spdif_qget(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct spdif_mixer_control *ctrl = &spdif_priv->fsl_spdif_control; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ctrl->ctl_lock, flags); + if (ctrl->ready_buf) { + int idx = (ctrl->ready_buf - 1) * SPDIF_QSUB_SIZE; + memcpy(&ucontrol->value.bytes.data[0], + &ctrl->qsub[idx], SPDIF_QSUB_SIZE); + } else { + ret = -EAGAIN; + } + spin_unlock_irqrestore(&ctrl->ctl_lock, flags); + + return ret; +} + +/* Valid bit infomation */ +static int fsl_spdif_vbit_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +/* Get valid good bit from interrupt status register */ +static int fsl_spdif_vbit_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + u32 int_val; + + clk_enable(spdif_priv->clk); + + int_val = spdif_read(&spdif->sisc); + ucontrol->value.integer.value[0] = (int_val & INT_VAL_NOGOOD) != 0; + spdif_write(&spdif->sisc, INT_VAL_NOGOOD); + + clk_disable(spdif_priv->clk); + + return 0; +} + +/* DPLL lock infomation */ +static int fsl_spdif_rxrate_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 16000; + uinfo->value.integer.max = 96000; + + return 0; +} + +static u32 gainsel_multi[GAINSEL_MULTI_MAX] = { + 24, 16, 12, 8, 6, 4, 3, +}; + +/* Get RX data clock rate given the SPDIF bus_clk */ +static int spdif_get_rxclk_rate(struct fsl_spdif_priv *spdif_priv, + enum spdif_gainsel gainsel) +{ + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + struct platform_device *pdev = spdif_priv->pdev; + u64 tmpval64, freqmeas, phaseconf, busclk_freq = 0; + enum spdif_rxclk_src clksrc; + + freqmeas = spdif_read(&spdif->srfm); + phaseconf = spdif_read(&spdif->srpc); + + clksrc = (phaseconf >> SRPC_CLKSRC_SEL_OFFSET) & 0xf; + if (spdif_rxclk_lock_check(clksrc) && (phaseconf & SRPC_DPLL_LOCKED)) { + /* get bus clock from system */ + busclk_freq = clk_get_rate(spdif_priv->clk); + } + + /* FreqMeas_CLK = (BUS_CLK * FreqMeas) / 2 ^ 10 / GAINSEL / 128 */ + tmpval64 = (u64) busclk_freq * freqmeas; + do_div(tmpval64, gainsel_multi[gainsel] * 1024); + do_div(tmpval64, 128 * 1024); + + dev_dbg(&pdev->dev, "FreqMeas: %d\n", (int)freqmeas); + dev_dbg(&pdev->dev, "BusclkFreq: %d\n", (int)busclk_freq); + dev_dbg(&pdev->dev, "RxRate: %d\n", (int)tmpval64); + + return (int)tmpval64; +} + +/* + * Get DPLL lock or not info from stable interrupt status register. + * User application must use this control to get locked, + * then can do next PCM operation + */ +static int fsl_spdif_rxrate_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + + if (atomic_read(&spdif_priv->dpll_locked)) { + clk_enable(spdif_priv->clk); + ucontrol->value.integer.value[0] = + spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL); + clk_disable(spdif_priv->clk); + } else { + ucontrol->value.integer.value[0] = 0; + } + + return 0; +} + +/* User bit sync mode info */ +static int fsl_spdif_usync_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +/* + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int fsl_spdif_usync_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + u32 int_val; + + clk_enable(spdif_priv->clk); + + int_val = spdif_read(&spdif->srcd); + ucontrol->value.integer.value[0] = (int_val & SRCD_CD_USER) != 0; + + clk_disable(spdif_priv->clk); + + return 0; +} + +/* + * User bit sync mode: + * 1 CD User channel subcode + * 0 Non-CD data + */ +static int fsl_spdif_usync_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); + struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(cpu_dai); + struct reg_spdif __iomem *spdif = spdif_priv->spdif; + u32 int_val = ucontrol->value.integer.value[0] << SRCD_CD_USER_OFFSET; + + clk_enable(spdif_priv->clk); + + spdif_setmask(&spdif->srcd, SRCD_CD_USER, int_val); + + clk_disable(spdif_priv->clk); + + return 0; +} + +/* FSL SPDIF IEC958 controller defines */ +static struct snd_kcontrol_new fsl_spdif_ctrls[] = { + /* status cchanel controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = mxc_pb_spdif_info, + .get = mxc_pb_spdif_get, + .put = mxc_pb_spdif_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_info, + .get = fsl_spdif_capture_get, + }, + /* user bits controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_info, + .get = fsl_spdif_subcode_get, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 Q-subcode Capture Default", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_qinfo, + .get = fsl_spdif_qget, + }, + /* valid bit error controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 V-Bit Errors", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_vbit_info, + .get = fsl_spdif_vbit_get, + }, + /* DPLL lock info get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "RX Sample Rate", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_rxrate_info, + .get = fsl_spdif_rxrate_get, + }, + /* User bit sync mode set/get controller */ + { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "IEC958 USyncMode CDText", + .access = SNDRV_CTL_ELEM_ACCESS_READ | + SNDRV_CTL_ELEM_ACCESS_WRITE | + SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = fsl_spdif_usync_info, + .get = fsl_spdif_usync_get, + .put = fsl_spdif_usync_put, + }, +}; + +static int fsl_spdif_dai_probe(struct snd_soc_dai *dai) +{ + struct fsl_spdif_priv *spdif_private = snd_soc_dai_get_drvdata(dai); + + dai->playback_dma_data = &spdif_private->dma_params_tx; + dai->capture_dma_data = &spdif_private->dma_params_rx; + + snd_soc_add_dai_controls(dai, fsl_spdif_ctrls, ARRAY_SIZE(fsl_spdif_ctrls)); + + return 0; +} + +struct snd_soc_dai_driver fsl_spdif_dai = { + .probe = &fsl_spdif_dai_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = FSL_SPDIF_RATES_PLAYBACK, + .formats = FSL_SPDIF_FORMATS_PLAYBACK, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = FSL_SPDIF_RATES_CAPTURE, + .formats = FSL_SPDIF_FORMATS_CAPTURE, + }, + .ops = &fsl_spdif_dai_ops, +}; + +static const struct snd_soc_component_driver fsl_spdif_component = { + .name = "fsl-spdif", +}; + +static int fsl_spdif_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(fsl_spdif_dt_ids, &pdev->dev); + struct fsl_spdif_priv *spdif_priv; + struct spdif_mixer_control *ctrl; + struct device_node *np = pdev->dev.of_node; + struct resource res; + const char *p; + u32 dma_events[2]; + int ret = 0, i; + + if (!of_device_is_available(np)) + return -ENODEV; + + /* The DAI name is the last part of the full name of the node. */ + p = strrchr(np->full_name, '/') + 1; + spdif_priv = devm_kzalloc(&pdev->dev, + sizeof(struct fsl_spdif_priv) + strlen(p), GFP_KERNEL); + if (!spdif_priv) { + dev_err(&pdev->dev, "could not allocate DAI object\n"); + return -ENOMEM; + } + + strcpy(spdif_priv->name, p); + + if (of_id) + pdev->id_entry = of_id->data; + spdif_devtype = pdev->id_entry->driver_data; + + /* Initialize this copy of the CPU DAI driver structure */ + memcpy(&spdif_priv->cpu_dai_drv, &fsl_spdif_dai, sizeof(fsl_spdif_dai)); + spdif_priv->cpu_dai_drv.name = spdif_priv->name; + + /* Get the addresses and IRQ */ + ret = of_address_to_resource(np, 0, &res); + if (ret) { + dev_err(&pdev->dev, "could not determine device resources\n"); + return ret; + } + spdif_priv->spdif = of_iomap(np, 0); + if (!spdif_priv->spdif) { + dev_err(&pdev->dev, "could not map device resources\n"); + return ret; + } + spdif_priv->spdif_phys = res.start; + + spdif_priv->irq = irq_of_parse_and_map(np, 0); + if (spdif_priv->irq == NO_IRQ) { + dev_err(&pdev->dev, "no irq for node %s\n", np->full_name); + ret = -ENXIO; + goto error_iomap; + } + + /* The 'name' should not have any slashes in it. */ + ret = devm_request_irq(&pdev->dev, spdif_priv->irq, spdif_isr, 0, + spdif_priv->name, spdif_priv); + if (ret) { + dev_err(&pdev->dev, "could not claim irq %u\n", spdif_priv->irq); + goto error_irqmap; + } + + spdif_priv->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(spdif_priv->clk)) { + ret = PTR_ERR(spdif_priv->clk); + dev_err(&pdev->dev, "failed to get clock: %d\n", ret); + goto error_irqmap; + } + clk_prepare(spdif_priv->clk); + + ret = of_property_read_u8(pdev->dev.of_node, + "rx-clk-source", &spdif_priv->rxclk_src); + if (ret) { + dev_warn(&pdev->dev, "using default rx-clk-source\n"); + spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC; + } + + ret = of_property_read_u8_array(pdev->dev.of_node, + "tx-clk-source", spdif_priv->txclk_src, 3); + if (ret) { + dev_warn(&pdev->dev, "using default tx-clk-source\n"); + for (i = 0; i < SPDIF_TXRATE_MAX; i++) + spdif_priv->txclk_src[i] = DEFAULT_TXCLK_SRC; + } + + ret = of_property_read_u8_array(pdev->dev.of_node, + "tx-clk-div", spdif_priv->txclk_div, 3); + if (ret) { + dev_warn(&pdev->dev, "using default tx-clk-div\n"); + for (i = 0; i < SPDIF_TXRATE_MAX; i++) + spdif_priv->txclk_div[i] = default_txclk_div[i]; + } + + ctrl = &spdif_priv->fsl_spdif_control; + /* initial spinlock for control data */ + spin_lock_init(&ctrl->ctl_lock); + + /* init tx channel status default value */ + ctrl->ch_status[0] = + IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS_5015; + ctrl->ch_status[1] = IEC958_AES1_CON_DIGDIGCONV_ID; + ctrl->ch_status[2] = 0x00; + ctrl->ch_status[3] = + IEC958_AES3_CON_FS_44100 | IEC958_AES3_CON_CLOCK_1000PPM; + + atomic_set(&spdif_priv->dpll_locked, 0); + + spdif_priv->dma_params_tx.maxburst = FSL_SPDIF_TXFIFO_WML; + spdif_priv->dma_params_rx.maxburst = FSL_SPDIF_RXFIFO_WML; + spdif_priv->dma_params_tx.addr = + spdif_priv->spdif_phys + offsetof(struct reg_spdif, stl); + spdif_priv->dma_params_rx.addr = + spdif_priv->spdif_phys + offsetof(struct reg_spdif, srl); + spdif_priv->dma_params_tx.filter_data = &spdif_priv->filter_data_tx; + spdif_priv->dma_params_rx.filter_data = &spdif_priv->filter_data_rx; + + ret = of_property_read_u32_array(pdev->dev.of_node, + "fsl,spdif-dma-events", dma_events, 2); + if (ret) { + dev_err(&pdev->dev, "failed to get dma events\n"); + goto error_clk; + } + + imx_pcm_dma_params_init_data(&spdif_priv->filter_data_tx, + dma_events[0], IMX_DMATYPE_SPDIF); + imx_pcm_dma_params_init_data(&spdif_priv->filter_data_rx, + dma_events[1], IMX_DMATYPE_SPDIF); + + /* Register with ASoC */ + dev_set_drvdata(&pdev->dev, spdif_priv); + + spdif_priv->pdev = pdev; + + ret = snd_soc_register_component(&pdev->dev, &fsl_spdif_component, + &spdif_priv->cpu_dai_drv, 1); + if (ret) { + dev_err(&pdev->dev, "failed to register DAI: %d\n", ret); + goto error_dev; + } + + ret = imx_pcm_dma_init(pdev); + if (ret) { + dev_err(&pdev->dev, "imx_pcm_dma_init failed: %d\n", ret); + goto error_component; + } + + return ret; + +error_component: + snd_soc_unregister_component(&pdev->dev); +error_dev: + dev_set_drvdata(&pdev->dev, NULL); +error_clk: + clk_unprepare(spdif_priv->clk); +error_irqmap: + irq_dispose_mapping(spdif_priv->irq); +error_iomap: + iounmap(spdif_priv->spdif); + + return ret; +} + +static int fsl_spdif_remove(struct platform_device *pdev) +{ + struct fsl_spdif_priv *spdif_priv = platform_get_drvdata(pdev); + + imx_pcm_dma_exit(pdev); + snd_soc_unregister_component(&pdev->dev); + + clk_unprepare(spdif_priv->clk); + irq_dispose_mapping(spdif_priv->irq); + iounmap(spdif_priv->spdif); + + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static struct platform_driver fsl_spdif_driver = { + .driver = { + .name = "fsl-spdif-dai", + .owner = THIS_MODULE, + .of_match_table = fsl_spdif_dt_ids, + }, + .probe = fsl_spdif_probe, + .remove = fsl_spdif_remove, +}; + +module_platform_driver(fsl_spdif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale S/PDIF CPU DAI Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:fsl_spdif"); diff --git a/sound/soc/fsl/fsl_spdif.h b/sound/soc/fsl/fsl_spdif.h new file mode 100644 index 0000000..4ef9658 --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.h @@ -0,0 +1,233 @@ +/* + * fsl_spdif.h - ALSA S/PDIF interface for the Freescale i.MX SoC + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen <b42378@freescale.com> + * + * Based on fsl_ssi.h + * Author: Timur Tabi <timur@freescale.com> + * Copyright 2007-2008 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#ifndef _FSL_SPDIF_DAI_H +#define _FSL_SPDIF_DAI_H + +/* S/PDIF Register Map */ +struct reg_spdif { + __le32 scr; /* 0x.0000 - SPDIF Configuration Register */ + __le32 srcd; /* 0x.0004 - CDText Control Register */ + __le32 srpc; /* 0x.0008 - PhaseConfig Register */ + __le32 sie; /* 0x.000C - InterruptEn Register */ + __le32 sisc; /* 0x.0010 - InterruptStat(R)/Clear(W) Register */ + __le32 srl; /* 0x.0014 - SPDIFRxLeft Register */ + __le32 srr; /* 0x.0018 - SPDIFRxRight Register */ + __le32 srcsch; /* 0x.001C - SPDIFRxCChannel_h Register */ + __le32 srcscl; /* 0x.0020 - SPDIFRxCChannel_l Register */ + __le32 sru; /* 0x.0024 - UchannelRx Register */ + __le32 srq; /* 0x.0028 - QchannelRx Register */ + __le32 stl; /* 0x.002C - SPDIFTxLeft Register */ + __le32 str; /* 0x.0030 - SPDIFTxRight Register */ + __le32 stcsch; /* 0x.0034 - SPDIFTxCChannelCons_h Register */ + __le32 stcscl; /* 0x.0038 - SPDIFTxCChannelCons_l Register */ + __le32 null1; /* 0x.003C - N/A */ + __le32 null2; /* 0x.0040 - N/A */ + __le32 srfm; /* 0x.0044 - FreqMeas Register */ + __le32 null3; /* 0x.0048 - N/A */ + __le32 null4; /* 0x.004C - N/A */ + __le32 stc; /* 0x.0050 - SPDIFTxClk Register */ +}; + +/* SPDIF Configuration register */ +#define SCR_RXFIFO_CTL_OFFSET 23 +#define SCR_RXFIFO_CTL_MASK (1 << SCR_RXFIFO_CTL_OFFSET) +#define SCR_RXFIFO_CTL_ZERO (1 << SCR_RXFIFO_CTL_OFFSET) +#define SCR_RXFIFO_OFF_OFFSET 22 +#define SCR_RXFIFO_OFF_MASK (1 << SCR_RXFIFO_OFF_OFFSET) +#define SCR_RXFIFO_OFF (1 << SCR_RXFIFO_OFF_OFFSET) +#define SCR_RXFIFO_RST_OFFSET 21 +#define SCR_RXFIFO_RST_MASK (1 << SCR_RXFIFO_RST_OFFSET) +#define SCR_RXFIFO_RST (1 << SCR_RXFIFO_RST_OFFSET) +#define SCR_RXFIFO_FSEL_OFFSET 19 +#define SCR_RXFIFO_FSEL_MASK (0x3 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF0 (0x0 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF4 (0x1 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF8 (0x2 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_FSEL_IF12 (0x3 << SCR_RXFIFO_FSEL_OFFSET) +#define SCR_RXFIFO_AUTOSYNC_OFFSET 18 +#define SCR_RXFIFO_AUTOSYNC_MASK (1 << SCR_RXFIFO_AUTOSYNC_OFFSET) +#define SCR_RXFIFO_AUTOSYNC (1 << SCR_RXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_AUTOSYNC_OFFSET 17 +#define SCR_TXFIFO_AUTOSYNC_MASK (1 << SCR_TXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_AUTOSYNC (1 << SCR_TXFIFO_AUTOSYNC_OFFSET) +#define SCR_TXFIFO_FSEL_OFFSET 15 +#define SCR_TXFIFO_FSEL_MASK (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF0 (0x0 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF4 (0x1 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF8 (0x2 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_TXFIFO_FSEL_IF12 (0x3 << SCR_TXFIFO_FSEL_OFFSET) +#define SCR_LOW_POWER (1 << 13) +#define SCR_SOFT_RESET (1 << 12) +#define SCR_TXFIFO_CTRL_OFFSET 10 +#define SCR_TXFIFO_CTRL_MASK (0x3 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_ZERO (0x0 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_NORMAL (0x1 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_TXFIFO_CTRL_ONESAMPLE (0x2 << SCR_TXFIFO_CTRL_OFFSET) +#define SCR_DMA_RX_EN_OFFSET 9 +#define SCR_DMA_RX_EN_MASK (1 << SCR_DMA_RX_EN_OFFSET) +#define SCR_DMA_RX_EN (1 << SCR_DMA_RX_EN_OFFSET) +#define SCR_DMA_TX_EN_OFFSET 8 +#define SCR_DMA_TX_EN_MASK (1 << SCR_DMA_TX_EN_OFFSET) +#define SCR_DMA_TX_EN (1 << SCR_DMA_TX_EN_OFFSET) +#define SCR_VAL_OFFSET 5 +#define SCR_VAL_MASK (1 << SCR_VAL_OFFSET) +#define SCR_VAL_CLEAR (1 << SCR_VAL_OFFSET) +#define SCR_TXSEL_OFFSET 2 +#define SCR_TXSEL_MASK (0x7 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_OFF (0 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_RX (1 << SCR_TXSEL_OFFSET) +#define SCR_TXSEL_NORMAL (0x5 << SCR_TXSEL_OFFSET) +#define SCR_USRC_SEL_OFFSET 0x0 +#define SCR_USRC_SEL_MASK (0x3 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_NONE (0x0 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_RECV (0x1 << SCR_USRC_SEL_OFFSET) +#define SCR_USRC_SEL_CHIP (0x3 << SCR_USRC_SEL_OFFSET) + +/* SPDIF CDText control */ +#define SRCD_CD_USER_OFFSET 1 +#define SRCD_CD_USER (1 << SRCD_CD_USER_OFFSET) + +/* SPDIF Phase Configuration register */ +#define SRPC_DPLL_LOCKED (1 << 6) +#define SRPC_CLKSRC_SEL_OFFSET 7 +#define SRPC_CLKSRC_SEL_MASK (0xf << SRPC_CLKSRC_SEL_OFFSET) +#define SRPC_CLKSRC_SEL_SET(x) ((x << SRPC_CLKSRC_SEL_OFFSET) & SRPC_CLKSRC_SEL_MASK) +#define SRPC_CLKSRC_SEL_LOCKED 5 +#define SRPC_GAINSEL_OFFSET 3 +#define SRPC_GAINSEL_MASK (0x7 << SRPC_GAINSEL_OFFSET) +#define SRPC_GAINSEL_SET(x) ((x << SRPC_GAINSEL_OFFSET) & SRPC_GAINSEL_MASK) + +/* SPDIF rx clock source */ +enum spdif_rxclk_src { + SRPC_CLKSRC_0 = 0, + SRPC_CLKSRC_1, + SRPC_CLKSRC_2, + SRPC_CLKSRC_3, + SRPC_CLKSRC_4, + SRPC_CLKSRC_5, + SRPC_CLKSRC_6, + SRPC_CLKSRC_7, + SRPC_CLKSRC_8, + SRPC_CLKSRC_9, + SRPC_CLKSRC_10, + SRPC_CLKSRC_11, + SRPC_CLKSRC_12, + SRPC_CLKSRC_13, + SRPC_CLKSRC_14, + SRPC_CLKSRC_15, +}; +#define SRPC_CLKSRC_MAX (SRPC_CLKSRC_15 + 1) +#define DEFAULT_RXCLK_SRC SRPC_CLKSRC_0 + +enum spdif_gainsel { + GAINSEL_MULTI_24 = 0, + GAINSEL_MULTI_16, + GAINSEL_MULTI_12, + GAINSEL_MULTI_8, + GAINSEL_MULTI_6, + GAINSEL_MULTI_4, + GAINSEL_MULTI_3, +}; +#define GAINSEL_MULTI_MAX (GAINSEL_MULTI_3 + 1) +#define SPDIF_DEFAULT_GAINSEL GAINSEL_MULTI_8 + +/* SPDIF interrupt mask define */ +#define INT_DPLL_LOCKED (1 << 20) +#define INT_TXFIFO_UNOV (1 << 19) +#define INT_TXFIFO_RESYNC (1 << 18) +#define INT_CNEW (1 << 17) +#define INT_VAL_NOGOOD (1 << 16) +#define INT_SYM_ERR (1 << 15) +#define INT_BIT_ERR (1 << 14) +#define INT_URX_FUL (1 << 10) +#define INT_URX_OV (1 << 9) +#define INT_QRX_FUL (1 << 8) +#define INT_QRX_OV (1 << 7) +#define INT_UQ_SYNC (1 << 6) +#define INT_UQ_ERR (1 << 5) +#define INT_RXFIFO_UNOV (1 << 4) +#define INT_RXFIFO_RESYNC (1 << 3) +#define INT_LOSS_LOCK (1 << 2) +#define INT_TX_EM (1 << 1) +#define INT_RXFIFO_FUL (1 << 0) + +/* SPDIF Clock register */ +#define STC_SYSCLK_DIV_OFFSET 11 +#define STC_SYSCLK_DIV_MASK (0x1ff << STC_TXCLK_SRC_OFFSET) +#define STC_SYSCLK_DIV(x) ((((x) - 1) << STC_TXCLK_DIV_OFFSET) & STC_SYSCLK_DIV_MASK) +#define STC_TXCLK_SRC_OFFSET 8 +#define STC_TXCLK_SRC_MASK (0x7 << STC_TXCLK_SRC_OFFSET) +#define STC_TXCLK_SRC_SET(x) ((x << STC_TXCLK_SRC_OFFSET) & STC_TXCLK_SRC_MASK) +#define STC_TXCLK_ALL_EN_OFFSET 7 +#define STC_TXCLK_ALL_EN_MASK (1 << STC_TXCLK_ALL_EN_OFFSET) +#define STC_TXCLK_ALL_EN (1 << STC_TXCLK_ALL_EN_OFFSET) +#define STC_TXCLK_DIV_OFFSET 0 +#define STC_TXCLK_DIV_MASK (0x7ff << STC_TXCLK_DIV_OFFSET) +#define STC_TXCLK_DIV(x) ((((x) - 1) << STC_TXCLK_DIV_OFFSET) & STC_TXCLK_DIV_MASK) + +/* SPDIF tx clksrc */ +enum spdif_txclk_src { + STC_TXCLK_SRC_0 = 0, + STC_TXCLK_SRC_1, + STC_TXCLK_SRC_2, + STC_TXCLK_SRC_3, + STC_TXCLK_SRC_4, + STC_TXCLK_SRC_5, + STC_TXCLK_SRC_6, + STC_TXCLK_SRC_7, +}; +#define STC_TXCLK_SRC_MAX (STC_TXCLK_SRC_7 + 1) +#define DEFAULT_TXCLK_SRC STC_TXCLK_SRC_1 + +/* SPDIF tx rate */ +enum spdif_txrate { + SPDIF_TXRATE_32000 = 0, + SPDIF_TXRATE_44100, + SPDIF_TXRATE_48000, +}; +#define SPDIF_TXRATE_MAX (SPDIF_TXRATE_48000 + 1) + +static u8 default_txclk_div[SPDIF_TXRATE_MAX] = { + 37, /* Tx clk div for 32000 */ + 23, /* Tx clk div for 44100 */ + 37, /* Tx clk div for 48000 */ +}; + + +#define SPDIF_CSTATUS_BYTE 6 +#define SPDIF_UBITS_SIZE 96 +#define SPDIF_QSUB_SIZE (SPDIF_UBITS_SIZE / 8) + + +#define FSL_SPDIF_RATES_PLAYBACK (SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000) + +#define FSL_SPDIF_RATES_CAPTURE (SNDRV_PCM_RATE_16000 | \ + SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_64000 | \ + SNDRV_PCM_RATE_96000) + +#define FSL_SPDIF_FORMATS_PLAYBACK (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +#define FSL_SPDIF_FORMATS_CAPTURE (SNDRV_PCM_FMTBIT_S24_LE) + +#endif /* _FSL_SPDIF_DAI_H */
[...] a/Documentation/devicetree/bindings/sound/fsl,spdif.txt b/Documentation/devicetree/bindings/sound/fsl,spdif.txt
new file mode 100644 index 0000000..a655800 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,spdif.txt @@ -0,0 +1,63 @@ +Freescale Sony/Philips Digital Interface Format (S/PDIF) Controller
+The Freescale S/PDIF audio block is a stereo transceiver that allows the +processor to receive and transmit digital audio via an coaxial cable or +a fibre cable.
+Required properties:
- compatible : Compatible list, contains "fsl,spdif".
That's not what the driver says though.
- reg : Offset and length of the register set for the device.
- interrupts : <a b> where a is the interrupt number and b is a field that
- represents an encoding of the sense and level information for the interrupt.
- This should be encoded based on the information in section 2) depending on
- the type of interrupt controller you have.
The exact layout of the cell depends on the parent interrupt controller, so you probably shouldn't describe it here.
- clocks : The phandle for the clock ID number registered in clock tree.
- fsl,spdif-dma-events: The dma event ID numbers for Tx and Rx.
Use the generic DMA bindings.
+Optional properties:
- rx-clk-source : The clock cource for Rx. Need to set this source according
- to the SoC datasheet in SPDIF_SRPC section. If absent, the default source is
- value 0x0 - if (DPLL Locked) SPDIF_RxClk else extal.
- tx-clk-source : The clock cources for Tx. There're three sources, each for
- different supported sample rate, sequentially 32000Hz, 44100Hz and 48000Hz.
- Need to set this source according to the SoC datasheet in SPDIF_STC section.
- If absent, the default source is value 0x1 - CCM spdif0_clk_root input.
- tx-clk-div : The clock divider factor for Tx clock. There're three values,
- each for different supported sample rate, sequentially 32000Hz 44100Hz 48000Hz.
- Need to set this source according to the clock rate from the clock source.
- If absent, the default divider factor is <37 23 37> by using spdif0_clk source.
Can't the driver figure out the divider values on its own based on the input clock rate?
- Lars
Hi Lars
Thank you for the sage advices.
I'll revise the patch and send the v2. ________________________________________ From: Lars-Peter Clausen [lars@metafoo.de] Sent: Wednesday, July 31, 2013 8:16 PM To: Chen Guangyu-B42378 Cc: broonie@kernel.org; timur@tabi.org; alsa-devel@alsa-project.org; linuxppc-dev@lists.ozlabs.org; devicetree-discuss@lists.ozlabs.org; rob.herring@calxeda.com Subject: Re: [alsa-devel] [PATCH 2/3] ASoC: fsl: Add S/PDIF CPU DAI driver
[...] a/Documentation/devicetree/bindings/sound/fsl,spdif.txt b/Documentation/devicetree/bindings/sound/fsl,spdif.txt
new file mode 100644 index 0000000..a655800 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,spdif.txt @@ -0,0 +1,63 @@ +Freescale Sony/Philips Digital Interface Format (S/PDIF) Controller
+The Freescale S/PDIF audio block is a stereo transceiver that allows the +processor to receive and transmit digital audio via an coaxial cable or +a fibre cable.
+Required properties:
- compatible : Compatible list, contains "fsl,spdif".
That's not what the driver says though.
- reg : Offset and length of the register set for the device.
- interrupts : <a b> where a is the interrupt number and b is a field that
- represents an encoding of the sense and level information for the interrupt.
- This should be encoded based on the information in section 2) depending on
- the type of interrupt controller you have.
The exact layout of the cell depends on the parent interrupt controller, so you probably shouldn't describe it here.
- clocks : The phandle for the clock ID number registered in clock tree.
- fsl,spdif-dma-events: The dma event ID numbers for Tx and Rx.
Use the generic DMA bindings.
+Optional properties:
- rx-clk-source : The clock cource for Rx. Need to set this source according
- to the SoC datasheet in SPDIF_SRPC section. If absent, the default source is
- value 0x0 - if (DPLL Locked) SPDIF_RxClk else extal.
- tx-clk-source : The clock cources for Tx. There're three sources, each for
- different supported sample rate, sequentially 32000Hz, 44100Hz and 48000Hz.
- Need to set this source according to the SoC datasheet in SPDIF_STC section.
- If absent, the default source is value 0x1 - CCM spdif0_clk_root input.
- tx-clk-div : The clock divider factor for Tx clock. There're three values,
- each for different supported sample rate, sequentially 32000Hz 44100Hz 48000Hz.
- Need to set this source according to the clock rate from the clock source.
- If absent, the default divider factor is <37 23 37> by using spdif0_clk source.
Can't the driver figure out the divider values on its own based on the input clock rate?
- Lars
Add S/PDIF machine driver for Freescale i.MX series SoC.
Signed-off-by: Nicolin Chen b42378@freescale.com --- .../devicetree/bindings/sound/imx-audio-spdif.txt | 29 +++++ sound/soc/fsl/Kconfig | 11 ++ sound/soc/fsl/Makefile | 2 + sound/soc/fsl/imx-spdif.c | 134 ++++++++++++++++++++ 4 files changed, 176 insertions(+), 0 deletions(-) create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-spdif.txt create mode 100644 sound/soc/fsl/imx-spdif.c
diff --git a/Documentation/devicetree/bindings/sound/imx-audio-spdif.txt b/Documentation/devicetree/bindings/sound/imx-audio-spdif.txt new file mode 100644 index 0000000..9a3fa26 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/imx-audio-spdif.txt @@ -0,0 +1,29 @@ +Freescale i.MX audio complex with S/PDIF transceiver + +Required properties: + + - compatible : "fsl,imx-audio-spdif" + + - model : The user-visible name of this sound complex + + - spdif-controller : The phandle of the i.MX S/PDIF controller + + +Optional properties: + + - spdif-transmitter : The phandle of the spdif-transmitter dummy codec + + - spdif-receiver : The phandle of the spdif-receiver dummy codec + +* Note: At least one of these two properties should be set in the DT binding. + + +Example: + +sound-spdif { + compatible = "fsl,imx-audio-spdif"; + model = "imx-spdif"; + spdif-controller = <&spdif>; + spdif-transmitter = <&spdif_tx_codec>; + spdif-receiver = <&spdif_rx_codec>; +}; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 156b794..ba0c7ff 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -195,6 +195,17 @@ config SND_SOC_IMX_SGTL5000 Say Y if you want to add support for SoC audio on an i.MX board with a sgtl5000 codec.
+config SND_SOC_IMX_SPDIF + tristate "SoC Audio support for i.MX boards with S/PDIF" + select SND_SOC_IMX_PCM_DMA + select SND_SOC_FSL_SPDIF + select SND_SOC_FSL_UTILS + select SND_SOC_SPDIF + help + SoC Audio support for i.MX boards with S/PDIF + Say Y if you want to add support for SoC audio on an i.MX board with + a S/DPDIF. + config SND_SOC_IMX_MC13783 tristate "SoC Audio support for I.MX boards with mc13783" depends on MFD_MC13783 && ARCH_ARM diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 4b5970e..e2aaff7 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -45,6 +45,7 @@ snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o snd-soc-imx-wm8962-objs := imx-wm8962.o +snd-soc-imx-spdif-objs :=imx-spdif.o snd-soc-imx-mc13783-objs := imx-mc13783.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o @@ -53,4 +54,5 @@ 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_SGTL5000) += snd-soc-imx-sgtl5000.o obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o +obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o obj-$(CONFIG_SND_SOC_IMX_MC13783) += snd-soc-imx-mc13783.o diff --git a/sound/soc/fsl/imx-spdif.c b/sound/soc/fsl/imx-spdif.c new file mode 100644 index 0000000..893f3d1 --- /dev/null +++ b/sound/soc/fsl/imx-spdif.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/module.h> +#include <linux/of_platform.h> +#include <sound/soc.h> + +struct imx_spdif_data { + struct snd_soc_dai_link dai[2]; + struct snd_soc_card card; +}; + +static int imx_spdif_audio_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *spdif_np, *codec_tx_np, *codec_rx_np; + struct platform_device *spdif_pdev; + struct imx_spdif_data *data; + int ret = 0, num_links = 0; + + spdif_np = of_parse_phandle(np, "spdif-controller", 0); + if (!spdif_np) { + dev_err(&pdev->dev, "failed to find spdif-controller\n"); + ret = -EINVAL; + goto fail; + } + + spdif_pdev = of_find_device_by_node(spdif_np); + if (!spdif_pdev) { + dev_err(&pdev->dev, "failed to find S/PDIF device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + ret = -ENOMEM; + goto fail; + } + + codec_tx_np = of_parse_phandle(np, "spdif-transmitter", 0); + if (codec_tx_np) { + data->dai[num_links].name = "S/PDIF TX"; + data->dai[num_links].stream_name = "S/PDIF PCM Playback"; + data->dai[num_links].codec_dai_name = "dit-hifi"; + data->dai[num_links].codec_of_node = codec_tx_np; + data->dai[num_links].cpu_of_node = spdif_np; + data->dai[num_links].platform_of_node = spdif_np; + num_links++; + } + + codec_rx_np = of_parse_phandle(np, "spdif-receiver", 0); + if (codec_rx_np) { + data->dai[num_links].name = "S/PDIF RX"; + data->dai[num_links].stream_name = "S/PDIF PCM Capture"; + data->dai[num_links].codec_dai_name = "dir-hifi"; + data->dai[num_links].codec_of_node = codec_rx_np; + data->dai[num_links].cpu_of_node = spdif_np; + data->dai[num_links].platform_of_node = spdif_np; + num_links++; + } + + if (!num_links) { + dev_err(&pdev->dev, "no enabled S/PDIF DAI link\n"); + goto fail; + } + + data->card.dev = &pdev->dev; + data->card.num_links = num_links; + data->card.dai_link = data->dai; + + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) + goto fail; + + ret = snd_soc_register_card(&data->card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret); + goto fail; + } + + platform_set_drvdata(pdev, data); + +fail: + if (codec_tx_np) + of_node_put(codec_tx_np); + if (codec_rx_np) + of_node_put(codec_rx_np); + if (spdif_np) + of_node_put(spdif_np); + + return ret; +} + +static int imx_spdif_audio_remove(struct platform_device *pdev) +{ + struct imx_spdif_data *data = platform_get_drvdata(pdev); + + snd_soc_unregister_card(&data->card); + + return 0; +} + +static const struct of_device_id imx_spdif_dt_ids[] = { + { .compatible = "fsl,imx-audio-spdif", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_spdif_dt_ids); + +static struct platform_driver imx_spdif_driver = { + .driver = { + .name = "imx-spdif", + .owner = THIS_MODULE, + .of_match_table = imx_spdif_dt_ids, + }, + .probe = imx_spdif_audio_probe, + .remove = imx_spdif_audio_remove, +}; + +module_platform_driver(imx_spdif_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Freescale i.MX S/PDIF machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-spdif");
participants (4)
-
Chen Guangyu-B42378
-
Lars-Peter Clausen
-
Mark Brown
-
Nicolin Chen