From patchwork Mon Aug 5 07:29:40 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nicolin Chen X-Patchwork-Id: 264581 Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from ozlabs.org (localhost [IPv6:::1]) by ozlabs.org (Postfix) with ESMTP id 3A5042C00A7 for ; Mon, 5 Aug 2013 17:20:41 +1000 (EST) Received: from am1outboundpool.messaging.microsoft.com (am1ehsobe005.messaging.microsoft.com [213.199.154.208]) (using TLSv1 with cipher AES128-SHA (128/128 bits)) (Client CN "mail.global.frontbridge.com", Issuer "MSIT Machine Auth CA 2" (not verified)) by ozlabs.org (Postfix) with ESMTPS id 7BAD02C00AB for ; Mon, 5 Aug 2013 17:19:32 +1000 (EST) Received: from mail37-am1-R.bigfish.com (10.3.201.239) by AM1EHSOBE011.bigfish.com (10.3.207.133) with Microsoft SMTP Server id 14.1.225.22; Mon, 5 Aug 2013 07:19:27 +0000 Received: from mail37-am1 (localhost [127.0.0.1]) by mail37-am1-R.bigfish.com (Postfix) with ESMTP id 0595CE0092; Mon, 5 Aug 2013 07:19:27 +0000 (UTC) X-Forefront-Antispam-Report: CIP:70.37.183.190; KIP:(null); UIP:(null); IPV:NLI; H:mail.freescale.net; RD:none; EFVD:NLI X-SpamScore: 4 X-BigFish: VS4(z551bizzz1f42h208ch1ee6h1de0h1fdah2073h1202h1e76h1d1ah1d2ah1fc6h1082kzz1de098h8275bh1de097h84d07hz2dh2a8h668h839hd24he5bhf0ah1288h12a5h12a9h12bdh12e5h137ah139eh13b6h1441h1504h1537h162dh1631h1758h1898h18e1h1946h19b5h1ad9h1b0ah1b2fh1fb3h1d0ch1d2eh1d3fh1dfeh1dffh1e23h1155h) Received: from mail37-am1 (localhost.localdomain [127.0.0.1]) by mail37-am1 (MessageSwitch) id 1375687163918210_28634; Mon, 5 Aug 2013 07:19:23 +0000 (UTC) Received: from AM1EHSMHS001.bigfish.com (unknown [10.3.201.252]) by mail37-am1.bigfish.com (Postfix) with ESMTP id D07841000D4; Mon, 5 Aug 2013 07:19:23 +0000 (UTC) Received: from mail.freescale.net (70.37.183.190) by AM1EHSMHS001.bigfish.com (10.3.207.101) with Microsoft SMTP Server (TLS) id 14.16.227.3; Mon, 5 Aug 2013 07:19:23 +0000 Received: from az84smr01.freescale.net (10.64.34.197) by 039-SN1MMR1-002.039d.mgd.msft.net (10.84.1.15) with Microsoft SMTP Server (TLS) id 14.3.136.1; Mon, 5 Aug 2013 07:19:21 +0000 Received: from rio.ap.freescale.net (rio.ap.freescale.net [10.192.242.9]) by az84smr01.freescale.net (8.14.3/8.14.0) with ESMTP id r757JFDh013864; Mon, 5 Aug 2013 00:19:18 -0700 From: Nicolin Chen To: , Subject: [PATCH v3 1/2] ASoC: fsl: Add S/PDIF CPU DAI driver Date: Mon, 5 Aug 2013 15:29:40 +0800 Message-ID: <1375687781-23225-2-git-send-email-b42378@freescale.com> X-Mailer: git-send-email 1.7.1 In-Reply-To: <1375687781-23225-1-git-send-email-b42378@freescale.com> References: <1375687781-23225-1-git-send-email-b42378@freescale.com> MIME-Version: 1.0 X-OriginatorOrg: freescale.com X-FOPE-CONNECTOR: Id%0$Dn%*$RO%0$TLS%0$FQDN%$TlsDn% Cc: devicetree@vger.kernel.org, alsa-devel@alsa-project.org, linuxppc-dev@lists.ozlabs.org, timur@tabi.org, rob.herring@calxeda.com X-BeenThere: linuxppc-dev@lists.ozlabs.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: Linux on PowerPC Developers Mail List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@lists.ozlabs.org Sender: "Linuxppc-dev" This patch add S/PDIF controller driver for Freescale SoC. Signed-off-by: Nicolin Chen --- .../devicetree/bindings/sound/fsl,spdif.txt | 62 + sound/soc/fsl/Kconfig | 3 + sound/soc/fsl/Makefile | 2 + sound/soc/fsl/fsl_spdif.c | 1311 ++++++++++++++++++++ sound/soc/fsl/fsl_spdif.h | 227 ++++ 5 files changed, 1605 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..8b1bfe2 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/fsl,spdif.txt @@ -0,0 +1,62 @@ +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,fsl-spdif" or SoC specific + "fsl,imx6q-spdif", "fsl,imx6sl-spdif". + + - reg : Offset and length of the register set for the device. + + - interrupts : Should contain spdif interrupt. + + - dmas : Generic dma devicetree binding as described in + Documentation/devicetree/bindings/dma/dma.txt. + + - dma-names : Two dmas have to be defined, "tx" and "rx". + + - clocks : (First) The phandle for the clock ID registered in clock tree. + There could be two clocks being set here, but only the first one, CORE + clock, is must. + + +Optional properties: + + - clocks : (Second) The phandle for the clock ID registered in clock tree. + There could be two clocks being set here, but the second one, TX clock is + optional. If absent, the TX clock will use CORE clock as default. + + - clock-names : The names for the two clocks. It will be required only when + the second clock's present. If absent, the TX clock will use CORE clock as + default. + + - tx-clk-source : The clock cources for Tx. 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. + + - 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. + + +Example: + +spdif: spdif@02004000 { + compatible = "fsl,fsl-spdif"; + reg = <0x02004000 0x4000>; + interrupts = <0 52 0x04>; + dmas = <&sdma 14 18 0>, + <&sdma 15 18 0>; + dma-names = "rx", "tx"; + + clocks = <&clks 197>, <&clks 197>; + clock-names = "core", "tx"; + + tx-clk-source = <0x1>; + rx-clk-source = <0x0>; + + status = "okay"; +}; diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index c26449b..74f533b 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..8865499 --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.c @@ -0,0 +1,1311 @@ +/* + * Freescale S/PDIF ALSA SoC Digital Audio Interface (DAI) driver + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Based on stmp3xxx_spdif_dai.c + * Vladimir Barinov + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#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_IMX6Q_SPDIF], }, + { .compatible = "fsl,imx6sl-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; + u8 txclk_div[SPDIF_TXRATE_MAX]; + struct clk *txclk; + struct clk *coreclk; + struct snd_dmaengine_dai_dma_data dma_params_tx; + struct snd_dmaengine_dai_dma_data dma_params_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->coreclk); + + /* 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->coreclk); +} +#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 = 1000; + + 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; + div = spdif_priv->txclk_div[SPDIF_TXRATE_32000]; + csfs = IEC958_AES3_CON_FS_32000; + break; + case 44100: + clk = spdif_priv->txclk_src; + div = spdif_priv->txclk_div[SPDIF_TXRATE_44100]; + csfs = IEC958_AES3_CON_FS_44100; + break; + case 48000: + clk = spdif_priv->txclk_src; + 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->txclk, 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->txclk)); + + /* 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; + + clk_enable(spdif_priv->coreclk); + clk_enable(spdif_priv->txclk); + + /* 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->txclk); + clk_disable(spdif_priv->coreclk); +} + +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 */ + ret = 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 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; +} + +static int fsl_spdif_pb_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 fsl_spdif_pb_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->coreclk); + + spdif_write_channel_status(spdif_priv); + + clk_disable(spdif_priv->coreclk); + + 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->coreclk); + + if (!(spdif_read(&spdif->sisc) & INT_CNEW)) { + clk_disable(spdif_priv->coreclk); + 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->coreclk); + + 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->coreclk); + + 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->coreclk); + + 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; + + clk_enable(spdif_priv->coreclk); + + 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->coreclk); + } + + /* 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); + + clk_disable(spdif_priv->coreclk); + + 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); + int rate = spdif_get_rxclk_rate(spdif_priv, SPDIF_DEFAULT_GAINSEL); + + if (atomic_read(&spdif_priv->dpll_locked)) + ucontrol->value.integer.value[0] = rate; + 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->coreclk); + + int_val = spdif_read(&spdif->srcd); + ucontrol->value.integer.value[0] = (int_val & SRCD_CD_USER) != 0; + + clk_disable(spdif_priv->coreclk); + + 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->coreclk); + + spdif_setmask(&spdif->srcd, SRCD_CD_USER, int_val); + + clk_disable(spdif_priv->coreclk); + + 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 = fsl_spdif_info, + .get = fsl_spdif_pb_get, + .put = fsl_spdif_pb_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 void spdif_clk_cal_txdiv(struct fsl_spdif_priv *spdif_priv) +{ + struct platform_device *pdev = spdif_priv->pdev; + struct clk *clk = spdif_priv->coreclk; + u64 rate_ideal, rate_actual, sub, savesub; + u32 i, div, arate, rate[] = {32000, 44100, 48000}; + + for (i = 0; i < SPDIF_TXRATE_MAX; i++, savesub = 100000) { + for (div = 1; div <= 128; div++) { + rate_ideal = rate[i] * (div + 1) * 64; + rate_actual = clk_round_rate(clk, rate_ideal); + + arate = rate_actual / 64; + arate /= div; + if (arate == rate[i]) { + savesub = 0; + spdif_priv->txclk_div[i] = div; + break; + } else if (arate / rate[i] == 1) { + sub = (arate - rate[i]) * 100000; + do_div(sub, rate[i]); + if (sub < savesub) { + savesub = sub; + spdif_priv->txclk_div[i] = div; + } + } else if (rate[i] / arate == 1) { + sub = (rate[i] - arate) * 100000; + do_div(sub, rate[i]); + if (sub < savesub) { + savesub = sub; + spdif_priv->txclk_div[i] = div; + } + } + } + dev_dbg(&pdev->dev, "calculated %dHz div: %d\n", + rate[i], spdif_priv->txclk_div[i]); + } +} + +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; + int ret = 0; + + 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); + + spdif_priv->pdev = pdev; + + 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->coreclk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(spdif_priv->coreclk)) { + ret = PTR_ERR(spdif_priv->coreclk); + dev_err(&pdev->dev, "failed to get clock: %d\n", ret); + goto error_irqmap; + } + + clk_prepare(spdif_priv->coreclk); + + spdif_priv->txclk = devm_clk_get(&pdev->dev, "tx"); + if (IS_ERR(spdif_priv->txclk)) { + /* Use coreclk as default txclk */ + spdif_priv->txclk = spdif_priv->coreclk; + dev_warn(&pdev->dev, "using core clock as tx clk\n"); + } else { + clk_prepare(spdif_priv->txclk); + } + + 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(pdev->dev.of_node, + "tx-clk-source", &spdif_priv->txclk_src); + if (ret) { + dev_warn(&pdev->dev, "using default tx-clk-source\n"); + spdif_priv->txclk_src = DEFAULT_TXCLK_SRC; + } + + spdif_clk_cal_txdiv(spdif_priv); + + 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); + + /* Register with ASoC */ + dev_set_drvdata(&pdev->dev, spdif_priv); + + 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); + if (spdif_priv->txclk != spdif_priv->coreclk) { + clk_unprepare(spdif_priv->txclk); + clk_put(spdif_priv->txclk); + } + clk_unprepare(spdif_priv->coreclk); + clk_put(spdif_priv->coreclk); +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); + + if (spdif_priv->txclk != spdif_priv->coreclk) { + clk_unprepare(spdif_priv->txclk); + clk_put(spdif_priv->txclk); + } + clk_unprepare(spdif_priv->coreclk); + clk_put(spdif_priv->coreclk); + + 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..de8b383 --- /dev/null +++ b/sound/soc/fsl/fsl_spdif.h @@ -0,0 +1,227 @@ +/* + * fsl_spdif.h - ALSA S/PDIF interface for the Freescale i.MX SoC + * + * Copyright (C) 2013 Freescale Semiconductor, Inc. + * + * Author: Nicolin Chen + * + * Based on fsl_ssi.h + * Author: Timur Tabi + * 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) + + +#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 */