summaryrefslogtreecommitdiff
path: root/sound/soc/imx/imx-ssi.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/soc/imx/imx-ssi.c')
-rw-r--r--sound/soc/imx/imx-ssi.c1102
1 files changed, 566 insertions, 536 deletions
diff --git a/sound/soc/imx/imx-ssi.c b/sound/soc/imx/imx-ssi.c
index 80b4fee2442b..372854a75caf 100644
--- a/sound/soc/imx/imx-ssi.c
+++ b/sound/soc/imx/imx-ssi.c
@@ -1,81 +1,227 @@
/*
- * imx-ssi.c -- ALSA Soc Audio Layer
+ * imx-ssi.c -- SSI driver for Freescale IMX
*
- * Copyright 2009 Sascha Hauer <s.hauer@pengutronix.de>
+ * Copyright 2006 Wolfson Microelectronics PLC.
+ * Author: Liam Girdwood
+ * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
*
- * This code is based on code copyrighted by Freescale,
- * Liam Girdwood, Javier Martin and probably others.
+ * Copyright (C) 2006-2010 Freescale Semiconductor, Inc.
+ * Based on mxc-alsa-mc13783
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
+ * Revision history
+ * 29th Aug 2006 Initial version.
+ *
+ * TODO:
+ * Need to rework SSI register defs when new defs go into mainline.
+ * Add support for TDM and FIFO 1.
*
- * The i.MX SSI core has some nasty limitations in AC97 mode. While most
- * sane processor vendors have a FIFO per AC97 slot, the i.MX has only
- * one FIFO which combines all valid receive slots. We cannot even select
- * which slots we want to receive. The WM9712 with which this driver
- * was developped with always sends GPIO status data in slot 12 which
- * we receive in our (PCM-) data stream. The only chance we have is to
- * manually skip this data in the FIQ handler. With sampling rates different
- * from 48000Hz not every frame has valid receive data, so the ratio
- * between pcm data and GPIO status data changes. Our FIQ handler is not
- * able to handle this, hence this driver only works with 48000Hz sampling
- * rate.
- * Reading and writing AC97 registers is another challange. The core
- * provides us status bits when the read register is updated with *another*
- * value. When we read the same register two times (and the register still
- * contains the same value) these status bits are not set. We work
- * around this by not polling these bits but only wait a fixed delay.
- *
*/
-#include <linux/clk.h>
-#include <linux/delay.h>
-#include <linux/device.h>
-#include <linux/dma-mapping.h>
-#include <linux/init.h>
-#include <linux/interrupt.h>
#include <linux/module.h>
+#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
-
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
#include <sound/core.h>
-#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
-
-#include <mach/ssi.h>
+#include <mach/dma.h>
+#include <mach/clock.h>
+#include <asm/mach-types.h>
#include <mach/hardware.h>
#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+#define SSI_STX0 (0x00)
+#define SSI_STX1 (0x04)
+#define SSI_SRX0 (0x08)
+#define SSI_SRX1 (0x0c)
+#define SSI_SCR (0x10)
+#define SSI_SISR (0x14)
+#define SSI_SIER (0x18)
+#define SSI_STCR (0x1c)
+#define SSI_SRCR (0x20)
+#define SSI_STCCR (0x24)
+#define SSI_SRCCR (0x28)
+#define SSI_SFCSR (0x2c)
+#define SSI_STR (0x30)
+#define SSI_SOR (0x34)
+#define SSI_SACNT (0x38)
+#define SSI_SACADD (0x3c)
+#define SSI_SACDAT (0x40)
+#define SSI_SATAG (0x44)
+#define SSI_STMSK (0x48)
+#define SSI_SRMSK (0x4c)
+#define SSI_SACCST (0x50)
+#define SSI_SACCEN (0x54)
+#define SSI_SACCDIS (0x58)
+
+/* debug */
+#define IMX_SSI_DEBUG 0
+#if IMX_SSI_DEBUG
+#define dbg(format, arg...) printk(format, ## arg)
+#else
+#define dbg(format, arg...)
+#endif
+
+#define IMX_SSI_DUMP 0
+#if IMX_SSI_DUMP
+#define SSI_DUMP() \
+ do { \
+ printk(KERN_INFO "dump @ %s\n", __func__); \
+ printk(KERN_INFO "scr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SCR), \
+ __raw_readl(ioaddr + SSI_SCR)); \
+ printk(KERN_INFO "sisr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SISR), \
+ __raw_readl(ioaddr + SSI_SISR)); \
+ printk(KERN_INFO "stcr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_STCR), \
+ __raw_readl(ioaddr + SSI_STCR)); \
+ printk(KERN_INFO "srcr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SRCR), \
+ __raw_readl(ioaddr + SSI_SRCR)); \
+ printk(KERN_INFO "stccr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_STCCR), \
+ __raw_readl(ioaddr + SSI_STCCR)); \
+ printk(KERN_INFO "srccr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SRCCR), \
+ __raw_readl(ioaddr + SSI_SRCCR)); \
+ printk(KERN_INFO "sfcsr %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SFCSR), \
+ __raw_readl(ioaddr + SSI_SFCSR)); \
+ printk(KERN_INFO "stmsk %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_STMSK), \
+ __raw_readl(ioaddr + SSI_STMSK)); \
+ printk(KERN_INFO "srmsk %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SRMSK), \
+ __raw_readl(ioaddr + SSI_SRMSK)); \
+ printk(KERN_INFO "sier %x\t, %x\n", \
+ __raw_readl(ioaddr + SSI_SIER), \
+ __raw_readl(ioaddr + SSI_SIER)); \
+ } while (0);
+#else
+#define SSI_DUMP()
+#endif
+
+/*
+ * SSI system clock configuration.
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 scr;
+
+ scr = __raw_readl(ioaddr + SSI_SCR);
+
+ if (scr & SSI_SCR_SSIEN)
+ return 0;
+
+ switch (clk_id) {
+ case IMX_SSP_SYS_CLK:
+ if (dir == SND_SOC_CLOCK_OUT)
+ scr |= SSI_SCR_SYS_CLK_EN;
+ else
+ scr &= ~SSI_SCR_SYS_CLK_EN;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ __raw_writel(scr, ioaddr + SSI_SCR);
+
+ return 0;
+}
+
+/*
+ * SSI Clock dividers
+ * Should only be called when port is inactive (i.e. SSIEN = 0).
+ */
+static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
+ int div_id, int div)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stccr, srccr;
+
+ if (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN)
+ return 0;
+
+ srccr = __raw_readl(ioaddr + SSI_SRCCR);
+ stccr = __raw_readl(ioaddr + SSI_STCCR);
+
+ switch (div_id) {
+ case IMX_SSI_TX_DIV_2:
+ stccr &= ~SSI_STCCR_DIV2;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PSR:
+ stccr &= ~SSI_STCCR_PSR;
+ stccr |= div;
+ break;
+ case IMX_SSI_TX_DIV_PM:
+ stccr &= ~0xff;
+ stccr |= SSI_STCCR_PM(div);
+ break;
+ case IMX_SSI_RX_DIV_2:
+ stccr &= ~SSI_STCCR_DIV2;
+ stccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PSR:
+ stccr &= ~SSI_STCCR_PSR;
+ stccr |= div;
+ break;
+ case IMX_SSI_RX_DIV_PM:
+ stccr &= ~0xff;
+ stccr |= SSI_STCCR_PM(div);
+ break;
+ default:
+ return -EINVAL;
+ }
-#define SSI_SACNT_DEFAULT (SSI_SACNT_AC97EN | SSI_SACNT_FV)
+ __raw_writel(stccr, ioaddr + SSI_STCCR);
+ __raw_writel(srccr, ioaddr + SSI_SRCCR);
+
+ return 0;
+}
/*
* SSI Network Mode or TDM slots configuration.
* Should only be called when port is inactive (i.e. SSIEN = 0).
*/
static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
- unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width)
+ unsigned int tx_mask,
+ unsigned int rx_mask,
+ int slots, int slot_width)
{
- struct imx_ssi *ssi = cpu_dai->private_data;
- u32 sccr;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stmsk, srmsk, stccr;
- sccr = readl(ssi->base + SSI_STCCR);
- sccr &= ~SSI_STCCR_DC_MASK;
- sccr |= SSI_STCCR_DC(slots - 1);
- writel(sccr, ssi->base + SSI_STCCR);
+ if (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN)
+ return 0;
+ stccr = __raw_readl(ioaddr + SSI_STCCR);
- sccr = readl(ssi->base + SSI_SRCCR);
- sccr &= ~SSI_STCCR_DC_MASK;
- sccr |= SSI_STCCR_DC(slots - 1);
- writel(sccr, ssi->base + SSI_SRCCR);
+ stmsk = tx_mask;
+ srmsk = rx_mask;
+ stccr &= ~SSI_STCCR_DC_MASK;
+ stccr |= SSI_STCCR_DC(slots - 1);
- writel(tx_mask, ssi->base + SSI_STMSK);
- writel(rx_mask, ssi->base + SSI_SRMSK);
+ __raw_writel(stmsk, ioaddr + SSI_STMSK);
+ __raw_writel(srmsk, ioaddr + SSI_SRMSK);
+ __raw_writel(stccr, ioaddr + SSI_STCCR);
+ __raw_writel(stccr, ioaddr + SSI_SRCCR);
return 0;
}
@@ -84,144 +230,231 @@ static int imx_ssi_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai,
* SSI DAI format configuration.
* Should only be called when port is inactive (i.e. SSIEN = 0).
* Note: We don't use the I2S modes but instead manually configure the
- * SSI for I2S because the I2S mode is only a register preset.
+ * SSI for I2S.
*/
static int imx_ssi_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
{
- struct imx_ssi *ssi = cpu_dai->private_data;
- u32 strcr = 0, scr;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stcr = 0, srcr = 0, scr;
- scr = readl(ssi->base + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
+ scr = __raw_readl(ioaddr + SSI_SCR) & ~(SSI_SCR_SYN | SSI_SCR_NET);
+
+ if (scr & SSI_SCR_SSIEN)
+ return 0;
/* DAI mode */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
/* data on rising edge of bclk, frame low 1clk before data */
- strcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
- scr |= SSI_SCR_NET;
+ stcr |= SSI_STCR_TFSI | SSI_STCR_TEFS | SSI_STCR_TXBIT0;
+ srcr |= SSI_SRCR_RFSI | SSI_SRCR_REFS | SSI_SRCR_RXBIT0;
break;
case SND_SOC_DAIFMT_LEFT_J:
/* data on rising edge of bclk, frame high with data */
- strcr |= SSI_STCR_TXBIT0;
+ stcr |= SSI_STCR_TXBIT0;
+ srcr |= SSI_SRCR_RXBIT0;
break;
case SND_SOC_DAIFMT_DSP_B:
/* data on rising edge of bclk, frame high with data */
- strcr |= SSI_STCR_TFSL;
+ stcr |= SSI_STCR_TFSL;
+ srcr |= SSI_SRCR_RFSL;
break;
case SND_SOC_DAIFMT_DSP_A:
/* data on rising edge of bclk, frame high 1clk before data */
- strcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
+ stcr |= SSI_STCR_TFSL | SSI_STCR_TEFS;
+ srcr |= SSI_SRCR_RFSL | SSI_SRCR_REFS;
break;
}
/* DAI clock inversion */
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
case SND_SOC_DAIFMT_IB_IF:
- strcr |= SSI_STCR_TFSI;
- strcr &= ~SSI_STCR_TSCKP;
+ stcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
+ srcr &= ~(SSI_SRCR_RSCKP | SSI_SRCR_RFSI);
break;
case SND_SOC_DAIFMT_IB_NF:
- strcr &= ~(SSI_STCR_TSCKP | SSI_STCR_TFSI);
+ stcr |= SSI_STCR_TFSI;
+ stcr &= ~SSI_STCR_TSCKP;
+ srcr |= SSI_SRCR_RFSI;
+ srcr &= ~SSI_SRCR_RSCKP;
break;
case SND_SOC_DAIFMT_NB_IF:
- strcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
+ stcr &= ~SSI_STCR_TFSI;
+ stcr |= SSI_STCR_TSCKP;
+ srcr &= ~SSI_SRCR_RFSI;
+ srcr |= SSI_SRCR_RSCKP;
break;
case SND_SOC_DAIFMT_NB_NF:
- strcr &= ~SSI_STCR_TFSI;
- strcr |= SSI_STCR_TSCKP;
+ stcr |= SSI_STCR_TFSI | SSI_STCR_TSCKP;
+ srcr |= SSI_SRCR_RFSI | SSI_SRCR_RSCKP;
break;
}
/* DAI clock master masks */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ stcr |= SSI_STCR_TFDIR | SSI_STCR_TXDIR;
+ if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S)
+ && priv->network_mode) {
+ scr &= ~SSI_SCR_I2S_MODE_MASK;
+ scr |= SSI_SCR_I2S_MODE_MSTR;
+ }
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ stcr |= SSI_STCR_TFDIR;
+ srcr |= SSI_SRCR_RFDIR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ stcr |= SSI_STCR_TXDIR;
+ srcr |= SSI_SRCR_RXDIR;
+ break;
case SND_SOC_DAIFMT_CBM_CFM:
+ if (((fmt & SND_SOC_DAIFMT_FORMAT_MASK) == SND_SOC_DAIFMT_I2S)
+ && priv->network_mode) {
+ scr &= ~SSI_SCR_I2S_MODE_MASK;
+ scr |= SSI_SCR_I2S_MODE_SLAVE;
+ }
break;
- default:
- /* Master mode not implemented, needs handling of clocks. */
- return -EINVAL;
}
- strcr |= SSI_STCR_TFEN0;
+ /* sync */
+ if (priv->sync_mode)
+ scr |= SSI_SCR_SYN;
- writel(strcr, ssi->base + SSI_STCR);
- writel(strcr, ssi->base + SSI_SRCR);
- writel(scr, ssi->base + SSI_SCR);
+ /* tdm - only for stereo atm */
+ if (priv->network_mode)
+ scr |= SSI_SCR_NET;
+#ifdef CONFIG_MXC_SSI_DUAL_FIFO
+ if (cpu_is_mx51() || cpu_is_mx53()) {
+ stcr |= SSI_STCR_TFEN1;
+ srcr |= SSI_SRCR_RFEN1;
+ scr |= SSI_SCR_TCH_EN;
+ }
+#endif
+ __raw_writel(stcr, ioaddr + SSI_STCR);
+ __raw_writel(srcr, ioaddr + SSI_SRCR);
+ __raw_writel(scr, ioaddr + SSI_SCR);
+ SSI_DUMP();
return 0;
}
-/*
- * SSI system clock configuration.
- * Should only be called when port is inactive (i.e. SSIEN = 0).
- */
-static int imx_ssi_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
- int clk_id, unsigned int freq, int dir)
+static int imx_ssi_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
{
- struct imx_ssi *ssi = cpu_dai->private_data;
- u32 scr;
-
- scr = readl(ssi->base + SSI_SCR);
-
- switch (clk_id) {
- case IMX_SSP_SYS_CLK:
- if (dir == SND_SOC_CLOCK_OUT)
- scr |= SSI_SCR_SYS_CLK_EN;
- else
- scr &= ~SSI_SCR_SYS_CLK_EN;
- break;
- default:
- return -EINVAL;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+
+ /* we cant really change any SSI values after SSI is enabled
+ * need to fix in software for max flexibility - lrg */
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ return 0;
+
+ /* reset the SSI port - Sect 45.4.4 */
+ if (clk_get_usecount(priv->ssi_clk) != 0) {
+ clk_enable(priv->ssi_clk);
+ return 0;
}
- writel(scr, ssi->base + SSI_SCR);
+ __raw_writel(0, ioaddr + SSI_SCR);
+ clk_enable(priv->ssi_clk);
+
+ /* BIG FAT WARNING
+ * SDMA FIFO watermark must == SSI FIFO watermark for best results.
+ */
+ __raw_writel((SSI_SFCSR_RFWM1(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_RFWM0(SSI_RXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM1(SSI_TXFIFO_WATERMARK) |
+ SSI_SFCSR_TFWM0(SSI_TXFIFO_WATERMARK)),
+ ioaddr + SSI_SFCSR);
+ __raw_writel(0, ioaddr + SSI_SIER);
+ SSI_DUMP();
return 0;
}
-/*
- * SSI Clock dividers
- * Should only be called when port is inactive (i.e. SSIEN = 0).
- */
-static int imx_ssi_set_dai_clkdiv(struct snd_soc_dai *cpu_dai,
- int div_id, int div)
+static int imx_ssi_hw_tx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
{
- struct imx_ssi *ssi = cpu_dai->private_data;
- u32 stccr, srccr;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 stccr, stcr, sier;
- stccr = readl(ssi->base + SSI_STCCR);
- srccr = readl(ssi->base + SSI_SRCCR);
+ stccr = __raw_readl(ioaddr + SSI_STCCR) & ~SSI_STCCR_WL_MASK;
+ stcr = __raw_readl(ioaddr + SSI_STCR);
+ sier = __raw_readl(ioaddr + SSI_SIER);
- switch (div_id) {
- case IMX_SSI_TX_DIV_2:
- stccr &= ~SSI_STCCR_DIV2;
- stccr |= div;
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ stccr |= SSI_STCCR_WL(16);
break;
- case IMX_SSI_TX_DIV_PSR:
- stccr &= ~SSI_STCCR_PSR;
- stccr |= div;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ stccr |= SSI_STCCR_WL(20);
break;
- case IMX_SSI_TX_DIV_PM:
- stccr &= ~0xff;
- stccr |= SSI_STCCR_PM(div);
+ case SNDRV_PCM_FORMAT_S24_LE:
+ stccr |= SSI_STCCR_WL(24);
break;
- case IMX_SSI_RX_DIV_2:
- stccr &= ~SSI_STCCR_DIV2;
- stccr |= div;
+ }
+
+ /* enable interrupts */
+ if ((cpu_dai->id & 0x1) == 0)
+ stcr |= SSI_STCR_TFEN0;
+ else
+ stcr |= SSI_STCR_TFEN1;
+ sier |= SSI_SIER_TDMAE | SSI_SIER_TIE | SSI_SIER_TUE0_EN;
+
+ __raw_writel(stcr, ioaddr + SSI_STCR);
+ __raw_writel(stccr, ioaddr + SSI_STCCR);
+ __raw_writel(sier, ioaddr + SSI_SIER);
+
+ return 0;
+}
+
+static int imx_ssi_hw_rx_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *cpu_dai)
+{
+ u32 srccr, srcr, sier;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ bool sync_mode = priv->sync_mode;
+
+ srccr =
+ sync_mode ? __raw_readl(ioaddr + SSI_STCCR) :
+ __raw_readl(ioaddr + SSI_SRCCR);
+ srcr = __raw_readl(ioaddr + SSI_SRCR);
+ sier = __raw_readl(ioaddr + SSI_SIER);
+ srccr &= ~SSI_SRCCR_WL_MASK;
+
+ /* DAI data (word) size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ srccr |= SSI_SRCCR_WL(16);
break;
- case IMX_SSI_RX_DIV_PSR:
- stccr &= ~SSI_STCCR_PSR;
- stccr |= div;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ srccr |= SSI_SRCCR_WL(20);
break;
- case IMX_SSI_RX_DIV_PM:
- stccr &= ~0xff;
- stccr |= SSI_STCCR_PM(div);
+ case SNDRV_PCM_FORMAT_S24_LE:
+ srccr |= SSI_SRCCR_WL(24);
break;
- default:
- return -EINVAL;
}
- writel(stccr, ssi->base + SSI_STCCR);
- writel(srccr, ssi->base + SSI_SRCCR);
+ /* enable interrupts */
+ if ((cpu_dai->id & 0x1) == 0)
+ srcr |= SSI_SRCR_RFEN0;
+ else
+ srcr |= SSI_SRCR_RFEN1;
+ sier |= SSI_SIER_RDMAE | SSI_SIER_RIE | SSI_SIER_ROE0_EN;
+
+ __raw_writel(srcr, ioaddr + SSI_SRCR);
+ if (sync_mode)
+ __raw_writel(srccr, ioaddr + SSI_STCCR);
+ else
+ __raw_writel(srccr, ioaddr + SSI_SRCCR);
+ __raw_writel(sier, ioaddr + SSI_SIER);
return 0;
}
@@ -234,530 +467,327 @@ static int imx_ssi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *cpu_dai)
{
- struct imx_ssi *ssi = cpu_dai->private_data;
- struct imx_pcm_dma_params *dma_data;
- u32 reg, sccr;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ int id;
+
+ id = cpu_dai->id;
/* Tx/Rx config */
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- reg = SSI_STCCR;
- dma_data = &ssi->dma_params_tx;
+ /* cant change any parameters when SSI is running */
+ if ((__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN) &&
+ (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_TE))
+ return 0;
+ return imx_ssi_hw_tx_params(substream, params, cpu_dai);
} else {
- reg = SSI_SRCCR;
- dma_data = &ssi->dma_params_rx;
+ /* cant change any parameters when SSI is running */
+ if ((__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_SSIEN) &&
+ (__raw_readl(ioaddr + SSI_SCR) & SSI_SCR_RE))
+ return 0;
+ return imx_ssi_hw_rx_params(substream, params, cpu_dai);
}
+}
- snd_soc_dai_set_dma_data(cpu_dai, substream, dma_data);
-
- sccr = readl(ssi->base + reg) & ~SSI_STCCR_WL_MASK;
-
- /* DAI data (word) size */
- switch (params_format(params)) {
- case SNDRV_PCM_FORMAT_S16_LE:
- sccr |= SSI_SRCCR_WL(16);
- break;
- case SNDRV_PCM_FORMAT_S20_3LE:
- sccr |= SSI_SRCCR_WL(20);
- break;
- case SNDRV_PCM_FORMAT_S24_LE:
- sccr |= SSI_SRCCR_WL(24);
- break;
- }
+static int imx_ssi_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 scr;
- writel(sccr, ssi->base + reg);
+ /* enable the SSI port, note that no other port config
+ * should happen after SSIEN is set */
+ scr = __raw_readl(ioaddr + SSI_SCR);
+ __raw_writel((scr | SSI_SCR_SSIEN), ioaddr + SSI_SCR);
+ SSI_DUMP();
return 0;
}
static int imx_ssi_trigger(struct snd_pcm_substream *substream, int cmd,
- struct snd_soc_dai *dai)
+ struct snd_soc_dai *cpu_dai)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
- struct imx_ssi *ssi = cpu_dai->private_data;
- unsigned int sier_bits, sier;
- unsigned int scr;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ u32 scr;
- scr = readl(ssi->base + SSI_SCR);
- sier = readl(ssi->base + SSI_SIER);
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- if (ssi->flags & IMX_SSI_DMA)
- sier_bits = SSI_SIER_TDMAE;
- else
- sier_bits = SSI_SIER_TIE | SSI_SIER_TFE0_EN;
- } else {
- if (ssi->flags & IMX_SSI_DMA)
- sier_bits = SSI_SIER_RDMAE;
- else
- sier_bits = SSI_SIER_RIE | SSI_SIER_RFF0_EN;
- }
+ scr = __raw_readl(ioaddr + SSI_SCR);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (scr & SSI_SCR_RE) {
+ __raw_writel(0, ioaddr + SSI_SCR);
+ }
scr |= SSI_SCR_TE;
- else
+ } else
scr |= SSI_SCR_RE;
- sier |= sier_bits;
-
- if (++ssi->enabled == 1)
- scr |= SSI_SCR_SSIEN;
-
break;
-
- case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
scr &= ~SSI_SCR_TE;
else
scr &= ~SSI_SCR_RE;
- sier &= ~sier_bits;
-
- if (--ssi->enabled == 0)
- scr &= ~SSI_SCR_SSIEN;
-
break;
default:
return -EINVAL;
}
+ __raw_writel(scr, ioaddr + SSI_SCR);
- if (!(ssi->flags & IMX_SSI_USE_AC97))
- /* rx/tx are always enabled to access ac97 registers */
- writel(scr, ssi->base + SSI_SCR);
-
- writel(sier, ssi->base + SSI_SIER);
-
+ SSI_DUMP();
return 0;
}
-static struct snd_soc_dai_ops imx_ssi_pcm_dai_ops = {
- .hw_params = imx_ssi_hw_params,
- .set_fmt = imx_ssi_set_dai_fmt,
- .set_clkdiv = imx_ssi_set_dai_clkdiv,
- .set_sysclk = imx_ssi_set_dai_sysclk,
- .set_tdm_slot = imx_ssi_set_dai_tdm_slot,
- .trigger = imx_ssi_trigger,
-};
-
-static struct snd_soc_dai imx_ssi_dai = {
- .playback = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_8000_96000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_8000_96000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .ops = &imx_ssi_pcm_dai_ops,
-};
-
-int snd_imx_pcm_mmap(struct snd_pcm_substream *substream,
- struct vm_area_struct *vma)
-{
- struct snd_pcm_runtime *runtime = substream->runtime;
- int ret;
-
- ret = dma_mmap_coherent(NULL, vma, runtime->dma_area,
- runtime->dma_addr, runtime->dma_bytes);
-
- pr_debug("%s: ret: %d %p 0x%08x 0x%08x\n", __func__, ret,
- runtime->dma_area,
- runtime->dma_addr,
- runtime->dma_bytes);
- return ret;
-}
-
-static int imx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+static void imx_ssi_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *cpu_dai)
{
- struct snd_pcm_substream *substream = pcm->streams[stream].substream;
- struct snd_dma_buffer *buf = &substream->dma_buffer;
- size_t size = IMX_SSI_DMABUF_SIZE;
-
- buf->dev.type = SNDRV_DMA_TYPE_DEV;
- buf->dev.dev = pcm->card->dev;
- buf->private_data = NULL;
- buf->area = dma_alloc_writecombine(pcm->card->dev, size,
- &buf->addr, GFP_KERNEL);
- if (!buf->area)
- return -ENOMEM;
- buf->bytes = size;
+ struct imx_ssi *priv = (struct imx_ssi *)cpu_dai->private_data;
+ void __iomem *ioaddr = priv->ioaddr;
+ int id;
- return 0;
-}
+ id = cpu_dai->id;
-static u64 imx_pcm_dmamask = DMA_BIT_MASK(32);
-
-int imx_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
- struct snd_pcm *pcm)
-{
+ /* shutdown SSI if neither Tx or Rx is active */
+ if (cpu_dai->playback.active || cpu_dai->capture.active)
+ return;
- int ret = 0;
-
- if (!card->dev->dma_mask)
- card->dev->dma_mask = &imx_pcm_dmamask;
- if (!card->dev->coherent_dma_mask)
- card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
- if (dai->playback.channels_min) {
- ret = imx_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_PLAYBACK);
- if (ret)
- goto out;
+ if (clk_get_usecount(priv->ssi_clk) > 1) {
+ clk_disable(priv->ssi_clk);
+ return;
}
- if (dai->capture.channels_min) {
- ret = imx_pcm_preallocate_dma_buffer(pcm,
- SNDRV_PCM_STREAM_CAPTURE);
- if (ret)
- goto out;
- }
+ __raw_writel(0, ioaddr + SSI_SCR);
-out:
- return ret;
+ clk_disable(priv->ssi_clk);
}
-void imx_pcm_free(struct snd_pcm *pcm)
+#ifdef CONFIG_PM
+static int imx_ssi_suspend(struct snd_soc_dai *dai)
{
- struct snd_pcm_substream *substream;
- struct snd_dma_buffer *buf;
- int stream;
-
- for (stream = 0; stream < 2; stream++) {
- substream = pcm->streams[stream].substream;
- if (!substream)
- continue;
-
- buf = &substream->dma_buffer;
- if (!buf->area)
- continue;
-
- dma_free_writecombine(pcm->card->dev, buf->bytes,
- buf->area, buf->addr);
- buf->area = NULL;
- }
-}
-
-struct snd_soc_platform imx_soc_platform = {
- .name = "imx-audio",
-};
-EXPORT_SYMBOL_GPL(imx_soc_platform);
-
-static struct snd_soc_dai imx_ac97_dai = {
- .name = "AC97",
- .ac97_control = 1,
- .playback = {
- .stream_name = "AC97 Playback",
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
- .stream_name = "AC97 Capture",
- .channels_min = 2,
- .channels_max = 2,
- .rates = SNDRV_PCM_RATE_48000,
- .formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .ops = &imx_ssi_pcm_dai_ops,
-};
-
-static void setup_channel_to_ac97(struct imx_ssi *imx_ssi)
-{
- void __iomem *base = imx_ssi->base;
-
- writel(0x0, base + SSI_SCR);
- writel(0x0, base + SSI_STCR);
- writel(0x0, base + SSI_SRCR);
+ if (!dai->active)
+ return 0;
- writel(SSI_SCR_SYN | SSI_SCR_NET, base + SSI_SCR);
+ /* do we need to disable any clocks? */
- writel(SSI_SFCSR_RFWM0(8) |
- SSI_SFCSR_TFWM0(8) |
- SSI_SFCSR_RFWM1(8) |
- SSI_SFCSR_TFWM1(8), base + SSI_SFCSR);
-
- writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_STCCR);
- writel(SSI_STCCR_WL(16) | SSI_STCCR_DC(12), base + SSI_SRCCR);
-
- writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN, base + SSI_SCR);
- writel(SSI_SOR_WAIT(3), base + SSI_SOR);
-
- writel(SSI_SCR_SYN | SSI_SCR_NET | SSI_SCR_SSIEN |
- SSI_SCR_TE | SSI_SCR_RE,
- base + SSI_SCR);
-
- writel(SSI_SACNT_DEFAULT, base + SSI_SACNT);
- writel(0xff, base + SSI_SACCDIS);
- writel(0x300, base + SSI_SACCEN);
+ return 0;
}
-static struct imx_ssi *ac97_ssi;
-
-static void imx_ssi_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
- unsigned short val)
+static int imx_ssi_resume(struct snd_soc_dai *dai)
{
- struct imx_ssi *imx_ssi = ac97_ssi;
- void __iomem *base = imx_ssi->base;
- unsigned int lreg;
- unsigned int lval;
-
- if (reg > 0x7f)
- return;
-
- pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+ if (!dai->active)
+ return 0;
- lreg = reg << 12;
- writel(lreg, base + SSI_SACADD);
+ /* do we need to enable any clocks? */
- lval = val << 4;
- writel(lval , base + SSI_SACDAT);
-
- writel(SSI_SACNT_DEFAULT | SSI_SACNT_WR, base + SSI_SACNT);
- udelay(100);
+ return 0;
}
+#else
+#define imx_ssi_suspend NULL
+#define imx_ssi_resume NULL
+#endif
-static unsigned short imx_ssi_ac97_read(struct snd_ac97 *ac97,
- unsigned short reg)
-{
- struct imx_ssi *imx_ssi = ac97_ssi;
- void __iomem *base = imx_ssi->base;
-
- unsigned short val = -1;
- unsigned int lreg;
-
- lreg = (reg & 0x7f) << 12 ;
- writel(lreg, base + SSI_SACADD);
- writel(SSI_SACNT_DEFAULT | SSI_SACNT_RD, base + SSI_SACNT);
-
- udelay(100);
-
- val = (readl(base + SSI_SACDAT) >> 4) & 0xffff;
-
- pr_debug("%s: 0x%02x 0x%04x\n", __func__, reg, val);
+static int fifo_err_counter;
- return val;
+static irqreturn_t imx_ssi_irq(int irq, void *dev_id)
+{
+ struct imx_ssi *priv = (struct imx_ssi *)dev_id;
+ void __iomem *ioaddr = priv->ioaddr;
+ if (fifo_err_counter++ % 1000 == 0)
+ printk(KERN_ERR "%s %s SISR %x SIER %x fifo_errs=%d\n",
+ __func__, priv->pdev->name,
+ __raw_readl(ioaddr + SSI_SISR),
+ __raw_readl(ioaddr + SSI_SIER), fifo_err_counter);
+ __raw_writel((SSI_SIER_TUE0_EN | SSI_SIER_ROE0_EN), ioaddr + SSI_SISR);
+ return IRQ_HANDLED;
}
-static void imx_ssi_ac97_reset(struct snd_ac97 *ac97)
+static int imx_ssi_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
{
- struct imx_ssi *imx_ssi = ac97_ssi;
+ struct imx_ssi *priv = (struct imx_ssi *)dai->private_data;
+
+ if (priv->irq >= 0) {
+ if (request_irq(priv->irq, imx_ssi_irq, IRQF_SHARED,
+ pdev->name, priv)) {
+ printk(KERN_ERR "%s: failure requesting irq for %s\n",
+ __func__, pdev->name);
+ return -EBUSY;
+ }
+ }
- if (imx_ssi->ac97_reset)
- imx_ssi->ac97_reset(ac97);
+ return 0;
}
-static void imx_ssi_ac97_warm_reset(struct snd_ac97 *ac97)
+static void imx_ssi_remove(struct platform_device *pdev,
+ struct snd_soc_dai *dai)
{
- struct imx_ssi *imx_ssi = ac97_ssi;
+ struct imx_ssi *priv = (struct imx_ssi *)dai->private_data;
- if (imx_ssi->ac97_warm_reset)
- imx_ssi->ac97_warm_reset(ac97);
+ if (priv->irq >= 0)
+ free_irq(priv->irq, dai);
}
-struct snd_ac97_bus_ops soc_ac97_ops = {
- .read = imx_ssi_ac97_read,
- .write = imx_ssi_ac97_write,
- .reset = imx_ssi_ac97_reset,
- .warm_reset = imx_ssi_ac97_warm_reset
+#define IMX_SSI_RATES \
+ (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+#define IMX_SSI_FORMATS \
+ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops imx_ssi_dai_ops = {
+ .startup = imx_ssi_startup,
+ .shutdown = imx_ssi_shutdown,
+ .trigger = imx_ssi_trigger,
+ .prepare = imx_ssi_prepare,
+ .hw_params = imx_ssi_hw_params,
+ .set_sysclk = imx_ssi_set_dai_sysclk,
+ .set_clkdiv = imx_ssi_set_dai_clkdiv,
+ .set_fmt = imx_ssi_set_dai_fmt,
+ .set_tdm_slot = imx_ssi_set_dai_tdm_slot,
};
-EXPORT_SYMBOL_GPL(soc_ac97_ops);
-struct snd_soc_dai imx_ssi_pcm_dai[2];
-EXPORT_SYMBOL_GPL(imx_ssi_pcm_dai);
+struct snd_soc_dai *imx_ssi_dai[MAX_SSI_CHANNELS];
+EXPORT_SYMBOL_GPL(imx_ssi_dai);
-static int imx_ssi_probe(struct platform_device *pdev)
+static int imx_ssi_dev_probe(struct platform_device *pdev)
{
+ int fifo0_channel = pdev->id * 2;
+ struct snd_soc_dai *dai;
+ struct imx_ssi *priv;
+ int fifo, channel;
struct resource *res;
- struct imx_ssi *ssi;
- struct imx_ssi_platform_data *pdata = pdev->dev.platform_data;
- struct snd_soc_platform *platform;
- int ret = 0;
- unsigned int val;
- struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
-
- if (dai->id >= ARRAY_SIZE(imx_ssi_pcm_dai))
- return -EINVAL;
+ int ret;
+
+ BUG_ON(fifo0_channel >= MAX_SSI_CHANNELS);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
- ssi = kzalloc(sizeof(*ssi), GFP_KERNEL);
- if (!ssi)
+ priv = kzalloc(sizeof(struct imx_ssi), GFP_KERNEL);
+ if (!priv)
return -ENOMEM;
- if (pdata) {
- ssi->ac97_reset = pdata->ac97_reset;
- ssi->ac97_warm_reset = pdata->ac97_warm_reset;
- ssi->flags = pdata->flags;
- }
+ /* Each SSI block has 2 fifos which share the same
+ private data (struct imx_ssi) */
+ priv->baseaddr = res->start;
+ priv->ioaddr = ioremap(res->start, 0x5C);
+ priv->irq = platform_get_irq(pdev, 0);
+ priv->ssi_clk = clk_get(&pdev->dev, "ssi_clk");
+ priv->pdev = pdev;
+
+ for (fifo = 0; fifo < 2; fifo++) {
+ channel = (pdev->id * 2) + fifo;
+
+ dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
+ if (IS_ERR(dai)) {
+ ret = -ENOMEM;
+ goto DAI_ERR;
+ }
- ssi->irq = platform_get_irq(pdev, 0);
+ dai->name = kasprintf(GFP_KERNEL, "imx-ssi-%d-%d",
+ pdev->id + 1, fifo);
+ if (IS_ERR(dai->name)) {
+ kfree(dai);
+ ret = -ENOMEM;
+ goto DAI_ERR;
+ }
- ssi->clk = clk_get(&pdev->dev, NULL);
- if (IS_ERR(ssi->clk)) {
- ret = PTR_ERR(ssi->clk);
- dev_err(&pdev->dev, "Cannot get the clock: %d\n",
- ret);
- goto failed_clk;
- }
- clk_enable(ssi->clk);
+ dai->probe = imx_ssi_probe;
+ dai->suspend = imx_ssi_suspend;
+ dai->remove = imx_ssi_remove;
+ dai->resume = imx_ssi_resume;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- ret = -ENODEV;
- goto failed_get_resource;
- }
+ dai->playback.channels_min = 1;
+ dai->playback.channels_max = 2;
+ dai->playback.rates = IMX_SSI_RATES;
+ dai->playback.formats = IMX_SSI_FORMATS;
- if (!request_mem_region(res->start, resource_size(res), DRV_NAME)) {
- dev_err(&pdev->dev, "request_mem_region failed\n");
- ret = -EBUSY;
- goto failed_get_resource;
- }
+ dai->capture.channels_min = 1;
+ dai->capture.channels_max = 2;
+ dai->capture.rates = IMX_SSI_RATES;
+ dai->capture.formats = IMX_SSI_FORMATS;
- ssi->base = ioremap(res->start, resource_size(res));
- if (!ssi->base) {
- dev_err(&pdev->dev, "ioremap failed\n");
- ret = -ENODEV;
- goto failed_ioremap;
- }
+ dai->ops = &imx_ssi_dai_ops;
- if (ssi->flags & IMX_SSI_USE_AC97) {
- if (ac97_ssi) {
- ret = -EBUSY;
- goto failed_ac97;
- }
- ac97_ssi = ssi;
- setup_channel_to_ac97(ssi);
- memcpy(dai, &imx_ac97_dai, sizeof(imx_ac97_dai));
- } else
- memcpy(dai, &imx_ssi_dai, sizeof(imx_ssi_dai));
-
- writel(0x0, ssi->base + SSI_SIER);
-
- ssi->dma_params_rx.dma_addr = res->start + SSI_SRX0;
- ssi->dma_params_tx.dma_addr = res->start + SSI_STX0;
-
- res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "tx0");
- if (res)
- ssi->dma_params_tx.dma = res->start;
-
- res = platform_get_resource_byname(pdev, IORESOURCE_DMA, "rx0");
- if (res)
- ssi->dma_params_rx.dma = res->start;
-
- dai->id = pdev->id;
- dai->dev = &pdev->dev;
- dai->name = kasprintf(GFP_KERNEL, "imx-ssi.%d", pdev->id);
- dai->private_data = ssi;
-
- if ((cpu_is_mx27() || cpu_is_mx21()) &&
- !(ssi->flags & IMX_SSI_USE_AC97) &&
- (ssi->flags & IMX_SSI_DMA)) {
- ssi->flags |= IMX_SSI_DMA;
- platform = imx_ssi_dma_mx2_init(pdev, ssi);
- } else
- platform = imx_ssi_fiq_init(pdev, ssi);
-
- imx_soc_platform.pcm_ops = platform->pcm_ops;
- imx_soc_platform.pcm_new = platform->pcm_new;
- imx_soc_platform.pcm_free = platform->pcm_free;
-
- val = SSI_SFCSR_TFWM0(ssi->dma_params_tx.burstsize) |
- SSI_SFCSR_RFWM0(ssi->dma_params_rx.burstsize);
- writel(val, ssi->base + SSI_SFCSR);
-
- ret = snd_soc_register_dai(dai);
- if (ret) {
- dev_err(&pdev->dev, "register DAI failed\n");
- goto failed_register;
- }
+ dai->private_data = priv;
- platform_set_drvdata(pdev, ssi);
+ dai->id = channel;
+ imx_ssi_dai[channel] = dai;
+ ret = snd_soc_register_dai(dai);
+ if (ret < 0) {
+ kfree(dai->name);
+ kfree(dai);
+ goto DAI_ERR;
+ }
+ }
return 0;
-failed_register:
-failed_ac97:
- iounmap(ssi->base);
-failed_ioremap:
- release_mem_region(res->start, resource_size(res));
-failed_get_resource:
- clk_disable(ssi->clk);
- clk_put(ssi->clk);
-failed_clk:
- kfree(ssi);
+DAI_ERR:
+ if (fifo == 1) {
+ dai = imx_ssi_dai[fifo0_channel];
+ snd_soc_unregister_dai(dai);
+ kfree(dai->name);
+ kfree(dai);
+ }
+ clk_put(priv->ssi_clk);
+ iounmap(priv->ioaddr);
+ kfree(priv);
return ret;
}
-static int __devexit imx_ssi_remove(struct platform_device *pdev)
+static int __devexit imx_ssi_dev_remove(struct platform_device *pdev)
{
- struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- struct imx_ssi *ssi = platform_get_drvdata(pdev);
- struct snd_soc_dai *dai = &imx_ssi_pcm_dai[pdev->id];
-
- snd_soc_unregister_dai(dai);
+ int fifo0_channel = pdev->id * 2;
+ int fifo1_channel = (pdev->id * 2) + 1;
+ struct imx_ssi *priv = imx_ssi_dai[fifo0_channel]->private_data;
- if (ssi->flags & IMX_SSI_USE_AC97)
- ac97_ssi = NULL;
+ snd_soc_unregister_dai(imx_ssi_dai[fifo0_channel]);
+ snd_soc_unregister_dai(imx_ssi_dai[fifo1_channel]);
- if (!(ssi->flags & IMX_SSI_DMA))
- imx_ssi_fiq_exit(pdev, ssi);
+ kfree(imx_ssi_dai[fifo0_channel]->name);
+ kfree(imx_ssi_dai[fifo0_channel]);
- iounmap(ssi->base);
- release_mem_region(res->start, resource_size(res));
- clk_disable(ssi->clk);
- clk_put(ssi->clk);
- kfree(ssi);
+ kfree(imx_ssi_dai[fifo1_channel]->name);
+ kfree(imx_ssi_dai[fifo1_channel]);
+ clk_put(priv->ssi_clk);
+ iounmap(priv->ioaddr);
+ kfree(priv);
return 0;
}
static struct platform_driver imx_ssi_driver = {
- .probe = imx_ssi_probe,
- .remove = __devexit_p(imx_ssi_remove),
-
+ .probe = imx_ssi_dev_probe,
+ .remove = __devexit_p(imx_ssi_dev_remove),
.driver = {
- .name = DRV_NAME,
- .owner = THIS_MODULE,
- },
+ .name = "mxc_ssi",
+ },
};
static int __init imx_ssi_init(void)
{
- int ret;
-
- ret = snd_soc_register_platform(&imx_soc_platform);
- if (ret) {
- pr_err("failed to register soc platform: %d\n", ret);
- return ret;
- }
-
- ret = platform_driver_register(&imx_ssi_driver);
- if (ret) {
- snd_soc_unregister_platform(&imx_soc_platform);
- return ret;
- }
-
- return 0;
+ return platform_driver_register(&imx_ssi_driver);
}
static void __exit imx_ssi_exit(void)
{
platform_driver_unregister(&imx_ssi_driver);
- snd_soc_unregister_platform(&imx_soc_platform);
}
module_init(imx_ssi_init);
module_exit(imx_ssi_exit);
-
-/* Module information */
-MODULE_AUTHOR("Sascha Hauer, <s.hauer@pengutronix.de>");
-MODULE_DESCRIPTION("i.MX I2S/ac97 SoC Interface");
+MODULE_AUTHOR
+ ("Liam Girdwood, liam.girdwood@wolfsonmicro.com, www.wolfsonmicro.com");
+MODULE_DESCRIPTION("i.MX ASoC I2S driver");
MODULE_LICENSE("GPL");
-