diff mbox

[V2,02/10] ASoc: mxs: add mxs-saif driver

Message ID 1310483085-31442-3-git-send-email-b29396@freescale.com (mailing list archive)
State New, archived
Headers show

Commit Message

Aisheng Dong July 12, 2011, 3:04 p.m. UTC
Signed-off-by: Dong Aisheng <b29396@freescale.com>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@ti.com>
Cc: Sascha Hauer <s.hauer@pengutronix.de>
---
 sound/soc/mxs/mxs-saif.c |  656 ++++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/mxs/mxs-saif.h |  130 +++++++++
 2 files changed, 786 insertions(+), 0 deletions(-)

Comments

Mark Brown July 13, 2011, 1:03 a.m. UTC | #1
On Tue, Jul 12, 2011 at 11:04:37PM +0800, Dong Aisheng wrote:

Looks pretty good, a few small issues below.

> +	/* The SAIF clock should be either 384*fs or 512*fs */
> +	if (saif->mclk_in_use) {
> +		if (mclk % 32 == 0) {
> +			scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
> +			ret = clk_set_rate(saif->clk, 512 * rate);
> +		} else if (mclk % 48 == 0) {
> +			scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
> +			ret = clk_set_rate(saif->clk, 384 * rate);
> +		}

What if MCLK is not set corectly for some reason?  We'll just silently
do nothing.

> +	if (ret)
> +		return  -EINVAL;

Should pass through any error codes we get.

In hw_params()...
> +	stat = __raw_readl(saif->base + SAIF_STAT);
> +	if (stat & BM_SAIF_STAT_BUSY) {
> +		dev_err(cpu_dai->dev, "error: busy\n");
> +		return -EBUSY;
> +	}

Does this work for simultaneous playback and record?  Perhaps you need
to set symmetric_rates in the DAI (if the hardware needs the same sample
rate for playback and record) and have a check here to see if we're
trying to apply the same configuration as we already have.

> +	struct mxs_saif *saif = dev_id;
> +	unsigned int stat;
> +
> +	stat = __raw_readl(saif->base + SAIF_STAT);
> +	if (stat & BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ)
> +		dev_dbg(saif->dev, "underrun!!! %d\n", ++saif->fifo_underrun);
> +
> +	if (stat & BM_SAIF_STAT_FIFO_OVERFLOW_IRQ)
> +		dev_dbg(saif->dev, "overrun!!! %d\n", ++saif->fifo_overrun);

Probably dev_err()?

> +	__raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ |
> +			BM_SAIF_STAT_FIFO_OVERFLOW_IRQ,
> +			saif->base + SAIF_STAT + MXS_CLR_ADDR);
> +
> +	return IRQ_HANDLED;

Should really check to see if underflow or overflow occurred and only
report handled if they did - otherwise we might have trouble with new
hardware blocks that have other interrupts.

> +	if (pdev->id >= 2)
> +		return -EINVAL;
> +	mxs_saif[pdev->id] = saif;

The id check should check for ARRAY_SIZE() mxs_saif[] to make updates
easier if a new chip has more ports.

> +	saif->dma_param.chan_irq = platform_get_irq(pdev, 1);
> +	if (saif->dma_param.chan_irq < 0) {

Does the IRQ requesting need to happen after you've set the base
address?  Otherwise there's a potential race if for some reason the IRQ
fires before we've got the data structures set up.
Aisheng Dong July 13, 2011, 8:14 a.m. UTC | #2
> -----Original Message-----
> From: alsa-devel-bounces@alsa-project.org [mailto:alsa-devel-
> bounces@alsa-project.org] On Behalf Of Mark Brown
> Sent: Wednesday, July 13, 2011 9:04 AM
> To: Dong Aisheng-B29396
> Cc: alsa-devel@alsa-project.org; s.hauer@pengutronix.de; lrg@ti.com;
> linux-arm-kernel@lists.infradead.org; u.kleine-koenig@pengutronix.de
> Subject: Re: [alsa-devel] [PATCH V2 02/10] ASoc: mxs: add mxs-saif driver
> 
> On Tue, Jul 12, 2011 at 11:04:37PM +0800, Dong Aisheng wrote:
> 
> Looks pretty good, a few small issues below.
> 
> > +	/* The SAIF clock should be either 384*fs or 512*fs */
> > +	if (saif->mclk_in_use) {
> > +		if (mclk % 32 == 0) {
> > +			scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
> > +			ret = clk_set_rate(saif->clk, 512 * rate);
> > +		} else if (mclk % 48 == 0) {
> > +			scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
> > +			ret = clk_set_rate(saif->clk, 384 * rate);
> > +		}
> 
> What if MCLK is not set corectly for some reason?  We'll just silently do
> nothing.
We will just return an error if MCLK is not correct (not 32x or 48x).
        /* SAIF MCLK should be either 32*fs or 48*fs */
        if (saif->mclk_in_use && (mclk % 32 != 0) && (mclk % 48 != 0))
                return -EINVAL;
Machine driver should guarantee to set a correct value since it's HW limitation.
Is that ok?

> > +	if (ret)
> > +		return  -EINVAL;
> 
> Should pass through any error codes we get.
> 
> In hw_params()...
OK, I will change it.

> > +	stat = __raw_readl(saif->base + SAIF_STAT);
> > +	if (stat & BM_SAIF_STAT_BUSY) {
> > +		dev_err(cpu_dai->dev, "error: busy\n");
> > +		return -EBUSY;
> > +	}
> 
> Does this work for simultaneous playback and record?  Perhaps you need to
> set symmetric_rates in the DAI (if the hardware needs the same sample
> rate for playback and record) and have a check here to see if we're
> trying to apply the same configuration as we already have.
The SAIF does not support full-duplex (simultaneous playback & record).
However, it allows to use two saif instances to implement it (one playback
While another capture).
For the two SAIFs, one can master or driver the clock pins while the other
SAIF, in slave mode, receives clock from the master.
This also means that both SAIFs must operate at the same sample rate.

So it seems, as you said, we may need to set symmetric_rates in the DAI.

> > +	struct mxs_saif *saif = dev_id;
> > +	unsigned int stat;
> > +
> > +	stat = __raw_readl(saif->base + SAIF_STAT);
> > +	if (stat & BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ)
> > +		dev_dbg(saif->dev, "underrun!!! %d\n", ++saif->fifo_underrun);
> > +
> > +	if (stat & BM_SAIF_STAT_FIFO_OVERFLOW_IRQ)
> > +		dev_dbg(saif->dev, "overrun!!! %d\n", ++saif->fifo_overrun);
> 
> Probably dev_err()?
I met an issue that during each playback when I pressed CTRL+C to stop,
I would always see a line of "underrun!!!" message.
This might because we actually do not stop SAIF (clear HW_SAIF_CTRL RUN bit)
When SNDRV_PCM_TRIGGER_STOP due to codec still needs the clock.
(clear HW_SAIF_CTRL RUN bit may cause mclk stop)
So I observed that there was at least one underrun happened.
It's limitation that sgtl5000 codec needs SAIF mclk.
Bothered by this "underrun!!!", i use the dev_dbg only for debug purpose.

> > +	__raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ |
> > +			BM_SAIF_STAT_FIFO_OVERFLOW_IRQ,
> > +			saif->base + SAIF_STAT + MXS_CLR_ADDR);
> > +
> > +	return IRQ_HANDLED;
> 
> Should really check to see if underflow or overflow occurred and only
> report handled if they did - otherwise we might have trouble with new
> hardware blocks that have other interrupts.
I may clear those two interrupts separately.
But I'm not quite understand how it may cause trouble with new hardware
Block that have other interrupts since it only clear underrun&overrun irq.

> > +	if (pdev->id >= 2)
> > +		return -EINVAL;
> > +	mxs_saif[pdev->id] = saif;
> 
> The id check should check for ARRAY_SIZE() mxs_saif[] to make updates
> easier if a new chip has more ports.
Yes, got it.
 
> > +	saif->dma_param.chan_irq = platform_get_irq(pdev, 1);
> > +	if (saif->dma_param.chan_irq < 0) {
> 
> Does the IRQ requesting need to happen after you've set the base address?
> Otherwise there's a potential race if for some reason the IRQ fires
> before we've got the data structures set up.
Yes, neglect it. Thanks for reminder. :)

Regards
Dong Aisheng
Mark Brown July 14, 2011, 3:06 a.m. UTC | #3
On Wed, Jul 13, 2011 at 08:14:09AM +0000, Dong Aisheng-B29396 wrote:

> > What if MCLK is not set corectly for some reason?  We'll just silently do
> > nothing.

> We will just return an error if MCLK is not correct (not 32x or 48x).
>         /* SAIF MCLK should be either 32*fs or 48*fs */
>         if (saif->mclk_in_use && (mclk % 32 != 0) && (mclk % 48 != 0))
>                 return -EINVAL;
> Machine driver should guarantee to set a correct value since it's HW limitation.
> Is that ok?

Please fix the code so you only check if the ratio is valid in one
place, the code currently is too hard to read.

> So it seems, as you said, we may need to set symmetric_rates in the DAI.

Not if there are two separate SAIFs used for each direction - this is
only relevant if one interface provides both playback and capture.

> > > +	stat = __raw_readl(saif->base + SAIF_STAT);
> > > +	if (stat & BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ)
> > > +		dev_dbg(saif->dev, "underrun!!! %d\n", ++saif->fifo_underrun);

> > > +	if (stat & BM_SAIF_STAT_FIFO_OVERFLOW_IRQ)
> > > +		dev_dbg(saif->dev, "overrun!!! %d\n", ++saif->fifo_overrun);

> > Probably dev_err()?
> I met an issue that during each playback when I pressed CTRL+C to stop,
> I would always see a line of "underrun!!!" message.
> This might because we actually do not stop SAIF (clear HW_SAIF_CTRL RUN bit)
> When SNDRV_PCM_TRIGGER_STOP due to codec still needs the clock.
> (clear HW_SAIF_CTRL RUN bit may cause mclk stop)
> So I observed that there was at least one underrun happened.
> It's limitation that sgtl5000 codec needs SAIF mclk.
> Bothered by this "underrun!!!", i use the dev_dbg only for debug purpose.

Hrm, OK.  I guess that's reasonable.

> > > +	__raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ |
> > > +			BM_SAIF_STAT_FIFO_OVERFLOW_IRQ,
> > > +			saif->base + SAIF_STAT + MXS_CLR_ADDR);

> > > +	return IRQ_HANDLED;

> > Should really check to see if underflow or overflow occurred and only
> > report handled if they did - otherwise we might have trouble with new
> > hardware blocks that have other interrupts.

> I may clear those two interrupts separately.
> But I'm not quite understand how it may cause trouble with new hardware
> Block that have other interrupts since it only clear underrun&overrun irq.

Because you return IRQ_HANDLED you're telling the IRQ infrastructure
that you handled the interrupt but you didn't.  If you didn't handle an
interrupt you should return IRQ_NONE instead.
Aisheng Dong July 14, 2011, 10:39 a.m. UTC | #4
> -----Original Message-----
> From: Mark Brown [mailto:broonie@opensource.wolfsonmicro.com]
> Sent: Thursday, July 14, 2011 11:07 AM
> To: Dong Aisheng-B29396
> Cc: alsa-devel@alsa-project.org; s.hauer@pengutronix.de; lrg@ti.com;
> linux-arm-kernel@lists.infradead.org; u.kleine-koenig@pengutronix.de
> Subject: Re: [alsa-devel] [PATCH V2 02/10] ASoc: mxs: add mxs-saif driver
> 
> On Wed, Jul 13, 2011 at 08:14:09AM +0000, Dong Aisheng-B29396 wrote:
> 
> > > What if MCLK is not set corectly for some reason?  We'll just
> > > silently do nothing.
> 
> > We will just return an error if MCLK is not correct (not 32x or 48x).
> >         /* SAIF MCLK should be either 32*fs or 48*fs */
> >         if (saif->mclk_in_use && (mclk % 32 != 0) && (mclk % 48 != 0))
> >                 return -EINVAL;
> > Machine driver should guarantee to set a correct value since it's HW
> limitation.
> > Is that ok?
> 
> Please fix the code so you only check if the ratio is valid in one place,
> the code currently is too hard to read.

Hi Mark,

So far, thanks a lot for your kindly review.
I removed the duplicated checking as you indicated and changed the clk setting
flow a bit(also adding more comments) to try to make it more easy to read.

The code is as follows.
Is it ok for you?
Or you have any better suggestions?

/*
 * Set SAIF clock and MCLK
 */
static int mxs_saif_set_clk(struct mxs_saif *saif,
                                  unsigned int mclk,
                                  unsigned int rate)
{
        u32 scr;
        int ret;

        scr = __raw_readl(saif->base + SAIF_CTRL);
        scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE;
        scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;

        /*
         * Set SAIF clock
         *
         * The SAIF clock should be either 384*fs or 512*fs.
         * If MCLK is used, the SAIF clk ratio needs to match mclk ratio.
         *  For 32x mclk, set saif clk as 512*fs.
         *  For 48x mclk, set saif clk as 384*fs.
         *
         * If MCLK is not used, we just set saif clk to 512*fs.
         */
        if (saif->mclk_in_use) {
                if (mclk % 32 == 0) {
                        scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
                        ret = clk_set_rate(saif->clk, 512 * rate);
                } else if (mclk % 48 == 0) {
                        scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
                        ret = clk_set_rate(saif->clk, 384 * rate);
                } else {
                        /* SAIF MCLK should be either 32x or 48x */
                        return -EINVAL;
                }
        } else {
                ret = clk_set_rate(saif->clk, 512 * rate);
                scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
        }

        if (ret)
                return ret;

        if (!saif->mclk_in_use) {
                __raw_writel(scr, saif->base + SAIF_CTRL);
                return 0;
        }

        /*
         * Program the over-sample rate for MCLK output if used
         *
         * The available MCLK range is 32x, 48x... 512x. The rate
         * could be from 8kHz to 192kH.
         */
        switch (mclk / rate) {
        case 32:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(4);
                break;
        case 64:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3);
                break;
        case 128:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2);
                break;
        case 256:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1);
                break;
        case 512:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0);
                break;
        case 48:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3);
                break;
        case 96:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2);
                break;
        case 192:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1);
                break;
        case 384:
                scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0);
                break;
        default:
                return -EINVAL;
        }

        __raw_writel(scr, saif->base + SAIF_CTRL);

        return 0;
}

Regards
Dong Aisheng
Mark Brown July 14, 2011, 11:05 a.m. UTC | #5
On Thu, Jul 14, 2011 at 10:39:13AM +0000, Dong Aisheng-B29396 wrote:

> The code is as follows.
> Is it ok for you?
> Or you have any better suggestions?

That looks much clearer, I've no better ideas right now.  Thanks!
Dong Aisheng July 14, 2011, 4:06 p.m. UTC | #6
2011/7/14 Mark Brown <broonie@opensource.wolfsonmicro.com>:
> On Thu, Jul 14, 2011 at 10:39:13AM +0000, Dong Aisheng-B29396 wrote:
>
>> The code is as follows.
>> Is it ok for you?
>> Or you have any better suggestions?
>
> That looks much clearer, I've no better ideas right now.  Thanks!

Thanks, Mark.

Regards
Dong Aisheng
diff mbox

Patch

diff --git a/sound/soc/mxs/mxs-saif.c b/sound/soc/mxs/mxs-saif.c
new file mode 100644
index 0000000..08cb893
--- /dev/null
+++ b/sound/soc/mxs/mxs-saif.c
@@ -0,0 +1,656 @@ 
+/*
+ * Copyright 2011 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#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 <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <mach/dma.h>
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include <mach/mxs.h>
+
+#include "mxs-saif.h"
+
+static struct mxs_saif *mxs_saif[2];
+
+static int mxs_saif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+			int clk_id, unsigned int freq, int dir)
+{
+	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai);
+
+	switch (clk_id) {
+	case MXS_SAIF_MCLK:
+		saif->mclk = freq;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * Set SAIF clock and MCLK
+ */
+static int mxs_saif_set_clk(struct mxs_saif *saif,
+				  unsigned int mclk,
+				  unsigned int rate)
+{
+	u32 scr;
+	int ret;
+
+	/* SAIF MCLK should be either 32*fs or 48*fs */
+	if (saif->mclk_in_use && (mclk % 32 != 0) && (mclk % 48 != 0))
+		return -EINVAL;
+
+	scr = __raw_readl(saif->base + SAIF_CTRL);
+	scr &= ~BM_SAIF_CTRL_BITCLK_MULT_RATE;
+	scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+
+	/* The SAIF clock should be either 384*fs or 512*fs */
+	if (saif->mclk_in_use) {
+		if (mclk % 32 == 0) {
+			scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+			ret = clk_set_rate(saif->clk, 512 * rate);
+		} else if (mclk % 48 == 0) {
+			scr |= BM_SAIF_CTRL_BITCLK_BASE_RATE;
+			ret = clk_set_rate(saif->clk, 384 * rate);
+		}
+	} else {
+		/* mclk is not used, just set saif clock to 512*fs */
+		ret = clk_set_rate(saif->clk, 512 * rate);
+		scr &= ~BM_SAIF_CTRL_BITCLK_BASE_RATE;
+	}
+
+	if (ret)
+		return  -EINVAL;
+
+	if (!saif->mclk_in_use) {
+		__raw_writel(scr, saif->base + SAIF_CTRL);
+		return 0;
+	}
+
+	/* select the multiple of the base frequency rate of MCLK */
+	switch (mclk / rate) {
+	case 32:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(4);
+		break;
+	case 64:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3);
+		break;
+	case 128:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2);
+		break;
+	case 256:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1);
+		break;
+	case 512:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0);
+		break;
+	case 48:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(3);
+		break;
+	case 96:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(2);
+		break;
+	case 192:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(1);
+		break;
+	case 384:
+		scr |= BF_SAIF_CTRL_BITCLK_MULT_RATE(0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	__raw_writel(scr, saif->base + SAIF_CTRL);
+
+	return 0;
+}
+
+/*
+ * Put and disable MCLK.
+ */
+int mxs_saif_put_mclk(unsigned int saif_id)
+{
+	struct mxs_saif *saif = mxs_saif[saif_id];
+	u32 stat;
+
+	if (!saif)
+		return -EINVAL;
+
+	stat = __raw_readl(saif->base + SAIF_STAT);
+	if (stat & BM_SAIF_STAT_BUSY) {
+		dev_err(saif->dev, "error: busy\n");
+		return -EBUSY;
+	}
+
+	clk_disable(saif->clk);
+
+	/* disable MCLK output */
+	__raw_writel(BM_SAIF_CTRL_CLKGATE,
+		saif->base + SAIF_CTRL + MXS_SET_ADDR);
+	__raw_writel(BM_SAIF_CTRL_RUN,
+		saif->base + SAIF_CTRL + MXS_CLR_ADDR);
+
+	saif->mclk_in_use = 0;
+	return 0;
+}
+
+/*
+ * Get MCLK and set clock rate to mclk, then enable it
+ * The available MCLK range is 32x, 48x... 512x. The rate
+ * could be from 8kHz to 192kH.
+ *
+ * This interface is used for codecs which are using MCLK provided
+ * by saif.
+ */
+int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk,
+					unsigned int rate)
+{
+	struct mxs_saif *saif = mxs_saif[saif_id];
+	u32 stat;
+	int ret;
+
+	if (!saif)
+		return -EINVAL;
+
+	stat = __raw_readl(saif->base + SAIF_STAT);
+	if (stat & BM_SAIF_STAT_BUSY) {
+		dev_err(saif->dev, "error: busy\n");
+		return -EBUSY;
+	}
+
+	/* Clear Reset */
+	__raw_writel(BM_SAIF_CTRL_SFTRST,
+		saif->base + SAIF_CTRL + MXS_CLR_ADDR);
+
+	saif->mclk_in_use = 1;
+	ret = mxs_saif_set_clk(saif, mclk, rate);
+	if (ret)
+		return -EINVAL;
+
+	ret = clk_enable(saif->clk);
+	if (ret)
+		return -EINVAL;
+
+	/* enable MCLK output */
+	__raw_writel(BM_SAIF_CTRL_CLKGATE,
+		saif->base + SAIF_CTRL + MXS_CLR_ADDR);
+	__raw_writel(BM_SAIF_CTRL_RUN,
+		saif->base + SAIF_CTRL + MXS_SET_ADDR);
+
+	return 0;
+}
+
+/*
+ * SAIF DAI format configuration.
+ * Should only be called when port is inactive.
+ */
+static int mxs_saif_set_dai_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	u32 scr, stat;
+	u32 scr0;
+	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai);
+
+	stat = __raw_readl(saif->base + SAIF_STAT);
+	if (stat & BM_SAIF_STAT_BUSY) {
+		dev_err(cpu_dai->dev, "error: busy\n");
+		return -EBUSY;
+	}
+
+	scr0 = __raw_readl(saif->base + SAIF_CTRL);
+	scr0 = scr0 & ~BM_SAIF_CTRL_BITCLK_EDGE & ~BM_SAIF_CTRL_LRCLK_POLARITY \
+		& ~BM_SAIF_CTRL_JUSTIFY & ~BM_SAIF_CTRL_DELAY;
+	scr = 0;
+
+	/* DAI mode */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		/* data frame low 1clk before data */
+		scr |= BM_SAIF_CTRL_DELAY;
+		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		/* data frame high with data */
+		scr &= ~BM_SAIF_CTRL_DELAY;
+		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+		scr &= ~BM_SAIF_CTRL_JUSTIFY;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* DAI clock inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_IF:
+		scr |= BM_SAIF_CTRL_BITCLK_EDGE;
+		scr |= BM_SAIF_CTRL_LRCLK_POLARITY;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		scr |= BM_SAIF_CTRL_BITCLK_EDGE;
+		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		scr &= ~BM_SAIF_CTRL_BITCLK_EDGE;
+		scr |= BM_SAIF_CTRL_LRCLK_POLARITY;
+		break;
+	case SND_SOC_DAIFMT_NB_NF:
+		scr &= ~BM_SAIF_CTRL_BITCLK_EDGE;
+		scr &= ~BM_SAIF_CTRL_LRCLK_POLARITY;
+		break;
+	}
+
+	/*
+	 * Note: We simply just support master mode since SAIF TX can only
+	 * work as master.
+	 */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		scr &= ~BM_SAIF_CTRL_SLAVE_MODE;
+		__raw_writel(scr | scr0, saif->base + SAIF_CTRL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mxs_saif_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *cpu_dai)
+{
+	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai);
+	snd_soc_dai_set_dma_data(cpu_dai, substream, &saif->dma_param);
+
+	/* clear error status to 0 for each re-open */
+	saif->fifo_underrun = 0;
+	saif->fifo_overrun = 0;
+
+	/* Clear Reset for normal operations */
+	__raw_writel(BM_SAIF_CTRL_SFTRST,
+		saif->base + SAIF_CTRL + MXS_CLR_ADDR);
+
+	return 0;
+}
+
+/*
+ * Should only be called when port is inactive.
+ * although can be called multiple times by upper layers.
+ */
+static int mxs_saif_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *cpu_dai)
+{
+	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai);
+	u32 scr, stat;
+	int ret;
+
+	/* mclk should already be set */
+	if (!saif->mclk && saif->mclk_in_use) {
+		dev_err(cpu_dai->dev, "set mclk first\n");
+		return -EINVAL;
+	}
+
+	stat = __raw_readl(saif->base + SAIF_STAT);
+	if (stat & BM_SAIF_STAT_BUSY) {
+		dev_err(cpu_dai->dev, "error: busy\n");
+		return -EBUSY;
+	}
+
+	ret = mxs_saif_set_clk(saif, saif->mclk, params_rate(params));
+	if (ret) {
+		dev_err(cpu_dai->dev, "unable to get proper mclk\n");
+		return -EINVAL;
+	}
+
+	scr = __raw_readl(saif->base + SAIF_CTRL);
+
+	scr &= ~BM_SAIF_CTRL_WORD_LENGTH;
+	scr &= ~BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		scr |= BF_SAIF_CTRL_WORD_LENGTH(0);
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		scr |= BF_SAIF_CTRL_WORD_LENGTH(4);
+		scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		scr |= BF_SAIF_CTRL_WORD_LENGTH(8);
+		scr |= BM_SAIF_CTRL_BITCLK_48XFS_ENABLE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Tx/Rx config */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/* enable TX mode */
+		scr &= ~BM_SAIF_CTRL_READ_MODE;
+	} else {
+		/* enable RX mode */
+		scr |= BM_SAIF_CTRL_READ_MODE;
+	}
+
+	__raw_writel(scr, saif->base + SAIF_CTRL);
+	return 0;
+}
+
+static int mxs_saif_prepare(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *cpu_dai)
+{
+	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai);
+
+	/* clear clock gate */
+	__raw_writel(BM_SAIF_CTRL_CLKGATE,
+		saif->base + SAIF_CTRL + MXS_CLR_ADDR);
+
+	/* enable FIFO error irqs */
+	__raw_writel(BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN,
+		saif->base + SAIF_CTRL + MXS_SET_ADDR);
+
+	return 0;
+}
+
+static int mxs_saif_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *cpu_dai)
+{
+	struct mxs_saif *saif = snd_soc_dai_get_drvdata(cpu_dai);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		dev_dbg(cpu_dai->dev, "start\n");
+
+		clk_enable(saif->clk);
+		if (!saif->mclk_in_use)
+			__raw_writel(BM_SAIF_CTRL_RUN,
+				saif->base + SAIF_CTRL + MXS_SET_ADDR);
+
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			/*
+			 * write a data to saif data register to trigger
+			 * the transfer
+			 */
+			__raw_writel(0, saif->base + SAIF_DATA);
+		} else {
+			/*
+			 * read a data from saif data register to trigger
+			 * the receive
+			 */
+			__raw_readl(saif->base + SAIF_DATA);
+		}
+
+		dev_dbg(cpu_dai->dev, "CTRL 0x%x STAT 0x%x\n",
+			__raw_readl(saif->base + SAIF_CTRL),
+			__raw_readl(saif->base + SAIF_STAT));
+
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dev_dbg(cpu_dai->dev, "stop\n");
+
+		clk_disable(saif->clk);
+		if (!saif->mclk_in_use)
+			__raw_writel(BM_SAIF_CTRL_RUN,
+				saif->base + SAIF_CTRL + MXS_CLR_ADDR);
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+#define MXS_SAIF_RATES		SNDRV_PCM_RATE_8000_192000
+#define MXS_SAIF_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+	SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops mxs_saif_dai_ops = {
+	.startup = mxs_saif_startup,
+	.trigger = mxs_saif_trigger,
+	.prepare = mxs_saif_prepare,
+	.hw_params = mxs_saif_hw_params,
+	.set_sysclk = mxs_saif_set_dai_sysclk,
+	.set_fmt = mxs_saif_set_dai_fmt,
+};
+
+static int mxs_saif_dai_probe(struct snd_soc_dai *dai)
+{
+	struct mxs_saif *saif = dev_get_drvdata(dai->dev);
+
+	snd_soc_dai_set_drvdata(dai, saif);
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver mxs_saif_dai = {
+	.name = "mxs-saif",
+	.probe = mxs_saif_dai_probe,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = MXS_SAIF_RATES,
+		.formats = MXS_SAIF_FORMATS,
+	},
+	.capture = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = MXS_SAIF_RATES,
+		.formats = MXS_SAIF_FORMATS,
+	},
+	.ops = &mxs_saif_dai_ops,
+};
+
+static irqreturn_t mxs_saif_irq(int irq, void *dev_id)
+{
+	struct mxs_saif *saif = dev_id;
+	unsigned int stat;
+
+	stat = __raw_readl(saif->base + SAIF_STAT);
+	if (stat & BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ)
+		dev_dbg(saif->dev, "underrun!!! %d\n", ++saif->fifo_underrun);
+
+	if (stat & BM_SAIF_STAT_FIFO_OVERFLOW_IRQ)
+		dev_dbg(saif->dev, "overrun!!! %d\n", ++saif->fifo_overrun);
+
+	dev_dbg(saif->dev, "SAIF_CTRL %x SAIF_STAT %x\n",
+	       __raw_readl(saif->base + SAIF_CTRL),
+	       __raw_readl(saif->base + SAIF_STAT));
+
+	__raw_writel(BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ |
+			BM_SAIF_STAT_FIFO_OVERFLOW_IRQ,
+			saif->base + SAIF_STAT + MXS_CLR_ADDR);
+
+	return IRQ_HANDLED;
+}
+
+static int mxs_saif_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	struct mxs_saif *saif;
+	int ret = 0;
+
+	saif = devm_kzalloc(&pdev->dev, sizeof(*saif), GFP_KERNEL);
+	if (!saif)
+		return -ENOMEM;
+
+	if (pdev->id >= 2)
+		return -EINVAL;
+	mxs_saif[pdev->id] = saif;
+
+	saif->irq = platform_get_irq(pdev, 0);
+	if (saif->irq < 0) {
+		ret = saif->irq;
+		dev_err(&pdev->dev, "failed to get irq resource: %d\n",
+			ret);
+		goto failed_get_irq1;
+	}
+
+	saif->dev = &pdev->dev;
+	ret = request_irq(saif->irq, mxs_saif_irq, 0, "mxs-saif", saif);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to request irq\n");
+		goto failed_req_irq;
+	}
+
+	saif->dma_param.chan_irq = platform_get_irq(pdev, 1);
+	if (saif->dma_param.chan_irq < 0) {
+		ret = saif->dma_param.chan_irq;
+		dev_err(&pdev->dev, "failed to get dma irq resource: %d\n",
+			ret);
+		goto failed_get_irq2;
+	}
+
+	saif->clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(saif->clk)) {
+		ret = PTR_ERR(saif->clk);
+		dev_err(&pdev->dev, "Cannot get the clock: %d\n",
+			ret);
+		goto failed_clk;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "failed to get io resource: %d\n",
+			ret);
+		goto failed_get_resource;
+	}
+
+	if (!request_mem_region(res->start, resource_size(res), "mxs-saif")) {
+		dev_err(&pdev->dev, "request_mem_region failed\n");
+		ret = -EBUSY;
+		goto failed_get_resource;
+	}
+
+	saif->base = ioremap(res->start, resource_size(res));
+	if (!saif->base) {
+		dev_err(&pdev->dev, "ioremap failed\n");
+		ret = -ENODEV;
+		goto failed_ioremap;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!res) {
+		ret = -ENODEV;
+		dev_err(&pdev->dev, "failed to get dma resource: %d\n",
+			ret);
+		goto failed_get_resource;
+	}
+	saif->dma_param.chan_num = res->start;
+
+	platform_set_drvdata(pdev, saif);
+
+	ret = snd_soc_register_dai(&pdev->dev, &mxs_saif_dai);
+	if (ret) {
+		dev_err(&pdev->dev, "register DAI failed\n");
+		goto failed_register;
+	}
+
+	saif->soc_platform_pdev = platform_device_alloc(
+					"mxs-pcm-audio", pdev->id);
+	if (!saif->soc_platform_pdev) {
+		ret = -ENOMEM;
+		goto failed_pdev_alloc;
+	}
+
+	platform_set_drvdata(saif->soc_platform_pdev, saif);
+	ret = platform_device_add(saif->soc_platform_pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "failed to add soc platform device\n");
+		goto failed_pdev_add;
+	}
+
+	return 0;
+
+failed_pdev_add:
+	platform_device_put(saif->soc_platform_pdev);
+failed_pdev_alloc:
+	snd_soc_unregister_dai(&pdev->dev);
+failed_register:
+	iounmap(saif->base);
+failed_ioremap:
+	release_mem_region(res->start, resource_size(res));
+failed_get_resource:
+	clk_put(saif->clk);
+failed_clk:
+failed_get_irq2:
+	free_irq(saif->irq, saif);
+failed_req_irq:
+failed_get_irq1:
+	kfree(saif);
+
+	return ret;
+}
+
+static int __devexit mxs_saif_remove(struct platform_device *pdev)
+{
+	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	struct mxs_saif *saif = platform_get_drvdata(pdev);
+
+	platform_device_unregister(saif->soc_platform_pdev);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	iounmap(saif->base);
+	release_mem_region(res->start, resource_size(res));
+	free_irq(saif->irq, saif);
+
+	clk_put(saif->clk);
+
+	return 0;
+}
+
+static struct platform_driver mxs_saif_driver = {
+	.probe = mxs_saif_probe,
+	.remove = __devexit_p(mxs_saif_remove),
+
+	.driver = {
+		.name = "mxs-saif",
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init mxs_saif_init(void)
+{
+	return platform_driver_register(&mxs_saif_driver);
+}
+
+static void __exit mxs_saif_exit(void)
+{
+	platform_driver_unregister(&mxs_saif_driver);
+}
+
+module_init(mxs_saif_init);
+module_exit(mxs_saif_exit);
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXS ASoC SAIF driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/mxs/mxs-saif.h b/sound/soc/mxs/mxs-saif.h
new file mode 100644
index 0000000..0e2ff8c
--- /dev/null
+++ b/sound/soc/mxs/mxs-saif.h
@@ -0,0 +1,130 @@ 
+/*
+ * Copyright (C) 2011 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+
+#ifndef _MXS_SAIF_H
+#define _MXS_SAIF_H
+
+#define SAIF_CTRL	0x0
+#define SAIF_STAT	0x10
+#define SAIF_DATA	0x20
+#define SAIF_VERSION	0X30
+
+/* SAIF_CTRL */
+#define BM_SAIF_CTRL_SFTRST		0x80000000
+#define BM_SAIF_CTRL_CLKGATE		0x40000000
+#define BP_SAIF_CTRL_BITCLK_MULT_RATE	27
+#define BM_SAIF_CTRL_BITCLK_MULT_RATE	0x38000000
+#define BF_SAIF_CTRL_BITCLK_MULT_RATE(v) \
+		(((v) << 27) & BM_SAIF_CTRL_BITCLK_MULT_RATE)
+#define BM_SAIF_CTRL_BITCLK_BASE_RATE	0x04000000
+#define BM_SAIF_CTRL_FIFO_ERROR_IRQ_EN	0x02000000
+#define BM_SAIF_CTRL_FIFO_SERVICE_IRQ_EN	0x01000000
+#define BP_SAIF_CTRL_RSRVD2		21
+#define BM_SAIF_CTRL_RSRVD2		0x00E00000
+
+#define BP_SAIF_CTRL_DMAWAIT_COUNT	16
+#define BM_SAIF_CTRL_DMAWAIT_COUNT	0x001F0000
+#define BF_SAIF_CTRL_DMAWAIT_COUNT(v) \
+		(((v) << 16) & BM_SAIF_CTRL_DMAWAIT_COUNT)
+#define BP_SAIF_CTRL_CHANNEL_NUM_SELECT 14
+#define BM_SAIF_CTRL_CHANNEL_NUM_SELECT 0x0000C000
+#define BF_SAIF_CTRL_CHANNEL_NUM_SELECT(v) \
+		(((v) << 14) & BM_SAIF_CTRL_CHANNEL_NUM_SELECT)
+#define BM_SAIF_CTRL_LRCLK_PULSE	0x00002000
+#define BM_SAIF_CTRL_BIT_ORDER		0x00001000
+#define BM_SAIF_CTRL_DELAY		0x00000800
+#define BM_SAIF_CTRL_JUSTIFY		0x00000400
+#define BM_SAIF_CTRL_LRCLK_POLARITY	0x00000200
+#define BM_SAIF_CTRL_BITCLK_EDGE	0x00000100
+#define BP_SAIF_CTRL_WORD_LENGTH	4
+#define BM_SAIF_CTRL_WORD_LENGTH	0x000000F0
+#define BF_SAIF_CTRL_WORD_LENGTH(v) \
+		(((v) << 4) & BM_SAIF_CTRL_WORD_LENGTH)
+#define BM_SAIF_CTRL_BITCLK_48XFS_ENABLE	0x00000008
+#define BM_SAIF_CTRL_SLAVE_MODE		0x00000004
+#define BM_SAIF_CTRL_READ_MODE		0x00000002
+#define BM_SAIF_CTRL_RUN		0x00000001
+
+/* SAIF_STAT */
+#define BM_SAIF_STAT_PRESENT		0x80000000
+#define BP_SAIF_STAT_RSRVD2		17
+#define BM_SAIF_STAT_RSRVD2		0x7FFE0000
+#define BF_SAIF_STAT_RSRVD2(v) \
+		(((v) << 17) & BM_SAIF_STAT_RSRVD2)
+#define BM_SAIF_STAT_DMA_PREQ		0x00010000
+#define BP_SAIF_STAT_RSRVD1		7
+#define BM_SAIF_STAT_RSRVD1		0x0000FF80
+#define BF_SAIF_STAT_RSRVD1(v) \
+		(((v) << 7) & BM_SAIF_STAT_RSRVD1)
+
+#define BM_SAIF_STAT_FIFO_UNDERFLOW_IRQ 0x00000040
+#define BM_SAIF_STAT_FIFO_OVERFLOW_IRQ	0x00000020
+#define BM_SAIF_STAT_FIFO_SERVICE_IRQ	0x00000010
+#define BP_SAIF_STAT_RSRVD0		1
+#define BM_SAIF_STAT_RSRVD0		0x0000000E
+#define BF_SAIF_STAT_RSRVD0(v) \
+		(((v) << 1) & BM_SAIF_STAT_RSRVD0)
+#define BM_SAIF_STAT_BUSY		0x00000001
+
+/* SAFI_DATA */
+#define BP_SAIF_DATA_PCM_RIGHT		16
+#define BM_SAIF_DATA_PCM_RIGHT		0xFFFF0000
+#define BF_SAIF_DATA_PCM_RIGHT(v) \
+		(((v) << 16) & BM_SAIF_DATA_PCM_RIGHT)
+#define BP_SAIF_DATA_PCM_LEFT		0
+#define BM_SAIF_DATA_PCM_LEFT		0x0000FFFF
+#define BF_SAIF_DATA_PCM_LEFT(v)	\
+		(((v) << 0) & BM_SAIF_DATA_PCM_LEFT)
+
+/* SAIF_VERSION */
+#define BP_SAIF_VERSION_MAJOR		24
+#define BM_SAIF_VERSION_MAJOR		0xFF000000
+#define BF_SAIF_VERSION_MAJOR(v) \
+		(((v) << 24) & BM_SAIF_VERSION_MAJOR)
+#define BP_SAIF_VERSION_MINOR		16
+#define BM_SAIF_VERSION_MINOR		0x00FF0000
+#define BF_SAIF_VERSION_MINOR(v) \
+		(((v) << 16) & BM_SAIF_VERSION_MINOR)
+#define BP_SAIF_VERSION_STEP		0
+#define BM_SAIF_VERSION_STEP		0x0000FFFF
+#define BF_SAIF_VERSION_STEP(v) \
+		(((v) << 0) & BM_SAIF_VERSION_STEP)
+
+#define MXS_SAIF_MCLK		0
+
+#include "mxs-pcm.h"
+
+struct mxs_saif {
+	struct device *dev;
+	struct clk *clk;
+	unsigned int mclk;
+	unsigned int mclk_in_use;
+	void __iomem *base;
+	int irq;
+	struct mxs_pcm_dma_params dma_param;
+
+	struct platform_device *soc_platform_pdev;
+	u32 fifo_underrun;
+	u32 fifo_overrun;
+};
+
+extern int mxs_saif_put_mclk(unsigned int saif_id);
+extern int mxs_saif_get_mclk(unsigned int saif_id, unsigned int mclk,
+					unsigned int rate);
+#endif