diff mbox

[1/2] ASoC: Add driver for CLPS711X DAI interface

Message ID 1393430741-16272-2-git-send-email-shc_work@mail.ru (mailing list archive)
State New, archived
Headers show

Commit Message

Alexander Shiyan Feb. 26, 2014, 4:05 p.m. UTC
This patch adds a driver for the DAI interface found on Cirrus Logic
CLPS711X family CPUs.

Signed-off-by: Alexander Shiyan <shc_work@mail.ru>
---
 sound/soc/cirrus/Kconfig        |   8 +
 sound/soc/cirrus/Makefile       |   5 +
 sound/soc/cirrus/clps711x-dai.c | 522 ++++++++++++++++++++++++++++++++++++++++
 sound/soc/cirrus/clps711x-dai.h |  61 +++++
 sound/soc/cirrus/clps711x-fiq.S |  56 +++++
 5 files changed, 652 insertions(+)
 create mode 100644 sound/soc/cirrus/clps711x-dai.c
 create mode 100644 sound/soc/cirrus/clps711x-dai.h
 create mode 100644 sound/soc/cirrus/clps711x-fiq.S

Comments

Mark Brown Feb. 27, 2014, 3:44 a.m. UTC | #1
On Wed, Feb 26, 2014 at 08:05:41PM +0400, Alexander Shiyan wrote:

> +static int clps711x_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
> +{
> +	struct clps711x_dai *s = dev_get_drvdata(dai->dev);
> +
> +	s->div2 = div;
> +
> +	return 0;
> +}

What is this divider and why does the machine driver need to manually
configure it?

> +static int clps711x_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +		atomic_set(&dai->running, 1);
> +		hrtimer_start(&dai->hrt, ns_to_ktime(dai->reload),
> +			      HRTIMER_MODE_REL);
> +		break;

I'm wondering if the FIQ code can be shared with the i.MX FIQ code?  Not
something that should block merging but it seems like it might be
helpful for other platforms that need to use FIQ support.

> +static int clps711x_pcm_silence(struct snd_pcm_substream *substream,
> +				int channel, snd_pcm_uframes_t pos,
> +				snd_pcm_uframes_t count)
> +{
> +	return 0;
> +}

Omit empty functions.

> +#if defined(CONFIG_SND_CLPS711X_SOC_MODULE)
> +irqreturn_t no_action(int irq, void *dev_id)
> +{
> +	return IRQ_NONE;
> +}
> +#endif

Eh?  Waht's this about?

> +static void clps711x_pcm_free(struct snd_pcm *pcm)
> +{
> +	struct snd_soc_pcm_runtime *rtd = pcm->private_data;
> +	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
> +
> +	/* Manually free IRQ */
> +	devm_free_irq(rtd->platform->dev, dai->irq, NULL);

Why is this using devm_?
Alexander Shiyan Feb. 27, 2014, 7:17 a.m. UTC | #2
???????, 27 ??????? 2014, 12:44 +09:00 ?? Mark Brown <broonie@kernel.org>:
> On Wed, Feb 26, 2014 at 08:05:41PM +0400, Alexander Shiyan wrote:
> 
> > +static int clps711x_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
> > +{
> > +	struct clps711x_dai *s = dev_get_drvdata(dai->dev);
> > +
> > +	s->div2 = div;
> > +
> > +	return 0;
> > +}
> 
> What is this divider and why does the machine driver need to manually
> configure it?

I'll think about it ...

> > +static int clps711x_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> > +{
> > +	struct snd_soc_pcm_runtime *rtd = substream->private_data;
> > +	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
> > +
> > +	switch (cmd) {
> > +	case SNDRV_PCM_TRIGGER_START:
> > +		atomic_set(&dai->running, 1);
> > +		hrtimer_start(&dai->hrt, ns_to_ktime(dai->reload),
> > +			      HRTIMER_MODE_REL);
> > +		break;
> 
> I'm wondering if the FIQ code can be shared with the i.MX FIQ code?  Not
> something that should block merging but it seems like it might be
> helpful for other platforms that need to use FIQ support.

This FIQ implementation and i.MX FIQ is totally different.
I am fully confident that we can not use any common features/calls.

> > +static int clps711x_pcm_silence(struct snd_pcm_substream *substream,
> > +				int channel, snd_pcm_uframes_t pos,
> > +				snd_pcm_uframes_t count)
> > +{
> > +	return 0;
> > +}
> 
> Omit empty functions.

So designed. I'll add a comment in the next version.

> > +#if defined(CONFIG_SND_CLPS711X_SOC_MODULE)
> > +irqreturn_t no_action(int irq, void *dev_id)
> > +{
> > +	return IRQ_NONE;
> > +}
> > +#endif
> 
> Eh?  Waht's this about?

In case using this driver as non-module, "no_action" handler is used
from kernel/irq/handle.c, but since it is not exported it cannot be used
in module case.
I'll remade this to use own empty handler always.

---
Mark Brown Feb. 28, 2014, 3:57 a.m. UTC | #3
On Thu, Feb 27, 2014 at 11:17:27AM +0400, Alexander Shiyan wrote:
> ???????, 27 ??????? 2014, 12:44 +09:00 ?? Mark Brown <broonie@kernel.org>:

> > I'm wondering if the FIQ code can be shared with the i.MX FIQ code?  Not
> > something that should block merging but it seems like it might be
> > helpful for other platforms that need to use FIQ support.

> This FIQ implementation and i.MX FIQ is totally different.
> I am fully confident that we can not use any common features/calls.

The timer stuff looked a bit familiar...

> > > +#if defined(CONFIG_SND_CLPS711X_SOC_MODULE)
> > > +irqreturn_t no_action(int irq, void *dev_id)
> > > +{
> > > +	return IRQ_NONE;
> > > +}
> > > +#endif

> > Eh?  Waht's this about?

> In case using this driver as non-module, "no_action" handler is used
> from kernel/irq/handle.c, but since it is not exported it cannot be used
> in module case.
> I'll remade this to use own empty handler always.

Please don't do this, fix the problem properly (for example by exporting
it if that's the sensible thing to do).
diff mbox

Patch

diff --git a/sound/soc/cirrus/Kconfig b/sound/soc/cirrus/Kconfig
index c872dac..bbd25cd 100644
--- a/sound/soc/cirrus/Kconfig
+++ b/sound/soc/cirrus/Kconfig
@@ -1,3 +1,11 @@ 
+config SND_CLPS711X_SOC
+	tristate "SoC Audio support for the Cirrus Logic CLPS711X CPUs"
+	depends on ARCH_CLPS711X || (ARM && COMPILE_TEST)
+	select FIQ
+	help
+	  Say Y or M if you want to add support for codecs attached to
+	  the CLPS711X DAI interface.
+
 config SND_EP93XX_SOC
 	tristate "SoC Audio support for the Cirrus Logic EP93xx series"
 	depends on ARCH_EP93XX || COMPILE_TEST
diff --git a/sound/soc/cirrus/Makefile b/sound/soc/cirrus/Makefile
index 5514146..f5d72fc 100644
--- a/sound/soc/cirrus/Makefile
+++ b/sound/soc/cirrus/Makefile
@@ -1,3 +1,8 @@ 
+# CLPS711X Platform Support
+snd-soc-clps711x-objs				:= clps711x-dai.o clps711x-fiq.o
+
+obj-$(CONFIG_SND_CLPS711X_SOC)			+= snd-soc-clps711x.o
+
 # EP93xx Platform Support
 snd-soc-ep93xx-objs				:= ep93xx-pcm.o
 snd-soc-ep93xx-i2s-objs	 			:= ep93xx-i2s.o
diff --git a/sound/soc/cirrus/clps711x-dai.c b/sound/soc/cirrus/clps711x-dai.c
new file mode 100644
index 0000000..1910903
--- /dev/null
+++ b/sound/soc/cirrus/clps711x-dai.c
@@ -0,0 +1,522 @@ 
+/*
+ *  Currus Logic CLPS711X DAI driver
+ *
+ *  Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#include <linux/mfd/syscon.h>
+#include <linux/mfd/syscon/clps711x.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/fiq.h>
+
+#include "clps711x-dai.h"
+
+#define CLPS711X_FMTS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)
+#define CLPS711X_RATES	(SNDRV_PCM_RATE_8000_48000)
+
+struct clps711x_dai {
+	u32				head;
+	u32				last_ptr;
+
+	struct hrtimer			hrt;
+	unsigned long			reload;
+
+	struct clk			*pll_clk;
+	unsigned long			pll_hz;
+	unsigned long			ext_hz;
+	int				div2;
+
+	void __iomem			*base;
+	int				irq;
+	struct regmap			*syscon;
+
+	struct pt_regs			regs;
+	struct fiq_handler		fiq;
+	atomic_t			running;
+
+	struct snd_pcm_substream	*substream;
+};
+
+static int clps711x_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	if ((fmt & SND_SOC_DAIFMT_FORMAT_MASK) != SND_SOC_DAIFMT_LEFT_J)
+		return -EINVAL;
+
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+		return -EINVAL;
+
+	if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int clps711x_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+				   unsigned int freq, int dir)
+{
+	struct clps711x_dai *s = dev_get_drvdata(dai->dev);
+
+	s->ext_hz = freq;
+
+	return 0;
+}
+
+static int clps711x_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div)
+{
+	struct clps711x_dai *s = dev_get_drvdata(dai->dev);
+
+	s->div2 = div;
+
+	return 0;
+}
+
+static int clps711x_dai_update_best_err(unsigned int clk, unsigned int div,
+					unsigned int rate, int *besterr)
+{
+	int err = abs(DIV_ROUND_CLOSEST(clk, div) - rate);
+
+	if ((*besterr < 0) || (*besterr > err)) {
+		*besterr = err;
+		return 0;
+	}
+
+	return 1;
+}
+
+static int clps711x_dai_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct clps711x_dai *s = dev_get_drvdata(dai->dev);
+	int i, besterr = -1;
+	u32 dai64 = 0;
+
+	if ((params_rate(params) < 8000) || (params_rate(params) > 48000))
+		return -EINVAL;
+
+	if (!s->pll_hz && !s->ext_hz)
+		return -EINVAL;
+
+	/* Find best settings for desired samplerate */
+	for (i = 0x01; (i < 0x80) && besterr && s->pll_hz; i++)
+		if (!clps711x_dai_update_best_err(s->pll_hz, 128 * i,
+						  params_rate(params),
+						  &besterr))
+			dai64 = DAI64FS_AUDIV(i);
+	for (i = 0x01; (i < 0x80) && besterr && s->ext_hz; i++)
+		if (!clps711x_dai_update_best_err(s->ext_hz, 128 * i,
+						  params_rate(params),
+						  &besterr))
+			dai64 = DAI64FS_AUDIV(i) | DAI64FS_AUDIOCLKSRC;
+
+	regmap_update_bits(s->syscon, SYSCON_OFFSET, SYSCON3_128FS,
+			   s->div2 ? SYSCON3_128FS : 0);
+
+	dai64 |= DAI64FS_AUDIOCLKEN | DAI64FS_MCLK256EN;
+	dai64 |= s->div2 ? 0 : DAI64FS_I2SF64;
+	writel(dai64, s->base + DAI64FS);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops clps711x_dai_ops = {
+	.hw_params	= clps711x_dai_hw_params,
+	.set_fmt	= clps711x_dai_set_fmt,
+	.set_clkdiv	= clps711x_dai_set_clkdiv,
+	.set_sysclk	= clps711x_dai_set_sysclk,
+};
+
+static int clps711x_dai_probe(struct snd_soc_dai *dai)
+{
+	struct clps711x_dai *s = dev_get_drvdata(dai->dev);
+
+	snd_soc_dai_set_drvdata(dai, s);
+
+	return 0;
+}
+
+static struct snd_soc_dai_driver clps711x_dai_driver = {
+	.ops	= &clps711x_dai_ops,
+	.probe	= clps711x_dai_probe,
+	.playback = {
+		.stream_name	= "Playback",
+		.formats	= CLPS711X_FMTS,
+		.rates		= CLPS711X_RATES,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+};
+
+static const struct snd_soc_component_driver clps711x_i2s_component = {
+	.name = "clps711x-i2s",
+};
+
+static int clps711x_pcm_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+	int ret;
+
+	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (ret < 0)
+		return ret;
+
+	dai->substream = substream;
+	dai->reload = (1000000000 / params_rate(params)) *
+		    params_period_size(params) / 2;
+
+	local_fiq_disable();
+	get_fiq_regs(&dai->regs);
+	dai->regs.ARM_r8 = (u32)substream->runtime->dma_area;
+	set_fiq_regs(&dai->regs);
+	local_fiq_enable();
+
+	return 0;
+}
+
+static int clps711x_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		atomic_set(&dai->running, 1);
+		hrtimer_start(&dai->hrt, ns_to_ktime(dai->reload),
+			      HRTIMER_MODE_REL);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		atomic_set(&dai->running, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t clps711x_pcm_ptr(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+
+	return bytes_to_frames(substream->runtime, dai->last_ptr);
+}
+
+static int clps711x_pcm_copy(struct snd_pcm_substream *substream, int channel,
+			     snd_pcm_uframes_t pos, void __user *buf,
+			     snd_pcm_uframes_t count)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+	int sz = frames_to_bytes(substream->runtime, count);
+
+	if (copy_from_user(substream->runtime->dma_area + dai->head, buf, sz))
+		return -EFAULT;
+
+	local_fiq_disable();
+	dai->head += sz;
+	dai->head %= CLPS711X_SNDBUF_SIZE;
+	local_fiq_enable();
+
+	return 0;
+}
+
+static int clps711x_pcm_silence(struct snd_pcm_substream *substream,
+				int channel, snd_pcm_uframes_t pos,
+				snd_pcm_uframes_t count)
+{
+	return 0;
+}
+
+static enum hrtimer_restart clps711x_pcm_timer(struct hrtimer *hrt)
+{
+	struct clps711x_dai *dai = container_of(hrt, struct clps711x_dai, hrt);
+	u32 delta;
+
+	if (!atomic_read(&dai->running)) {
+		dai->head = 0;
+		dai->last_ptr = 0;
+
+		local_fiq_disable();
+		get_fiq_regs(&dai->regs);
+		dai->regs.ARM_r10 = 0;
+		set_fiq_regs(&dai->regs);
+		local_fiq_enable();
+
+		return HRTIMER_NORESTART;
+	}
+
+	get_fiq_regs(&dai->regs);
+
+	delta = CLPS711X_SNDBUF_SIZE + dai->regs.ARM_r10 - dai->last_ptr;
+	delta %= CLPS711X_SNDBUF_SIZE;
+	if ((delta >= dai->substream->runtime->period_size) ||
+	    (dai->regs.ARM_r10 == dai->head)) {
+		dai->last_ptr = dai->regs.ARM_r10;
+		snd_pcm_period_elapsed(dai->substream);
+	}
+
+	hrtimer_forward_now(hrt, ns_to_ktime(dai->reload));
+
+	return HRTIMER_RESTART;
+}
+
+static const struct snd_pcm_hardware clps711x_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_BLOCK_TRANSFER,
+	.formats		= CLPS711X_FMTS,
+	.buffer_bytes_max	= CLPS711X_SNDBUF_SIZE,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= CLPS711X_SNDBUF_SIZE / 8,
+	.periods_min		= 4,
+	.periods_max		= CLPS711X_SNDBUF_SIZE / 64 - 1,
+};
+
+static int clps711x_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+	int ret;
+
+	atomic_set(&dai->running, 0);
+	hrtimer_init(&dai->hrt, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	dai->hrt.function = clps711x_pcm_timer;
+
+	dai->head = 0;
+	dai->last_ptr = 0;
+
+	get_fiq_regs(&dai->regs);
+	dai->regs.ARM_r9 = (u32)&dai->head;
+	dai->regs.ARM_r10 = 0;
+	dai->regs.ARM_fp = (u32)dai->base;
+	set_fiq_regs(&dai->regs);
+
+	enable_irq(dai->irq);
+
+	ret = snd_pcm_hw_constraint_integer(substream->runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0)
+		return ret;
+
+	return snd_soc_set_runtime_hwparams(substream, &clps711x_pcm_hardware);
+}
+
+static int clps711x_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+
+	disable_irq(dai->irq);
+	hrtimer_cancel(&dai->hrt);
+
+	return 0;
+}
+
+static const struct snd_pcm_ops clps711x_pcm_ops = {
+	.open		= clps711x_pcm_open,
+	.close		= clps711x_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= clps711x_pcm_hw_params,
+	.hw_free	= snd_pcm_lib_free_pages,
+	.trigger	= clps711x_pcm_trigger,
+	.pointer	= clps711x_pcm_ptr,
+	.copy		= clps711x_pcm_copy,
+	.silence	= clps711x_pcm_silence,
+};
+
+#if defined(CONFIG_SND_CLPS711X_SOC_MODULE)
+irqreturn_t no_action(int irq, void *dev_id)
+{
+	return IRQ_NONE;
+}
+#endif
+
+static int clps711x_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+	int ret;
+
+	/* Request FIQ */
+	dai->fiq.name = dev_name(rtd->platform->dev);
+	ret = claim_fiq(&dai->fiq);
+	if (ret)
+		return ret;
+
+	/* Request FIQ interrupt */
+	ret = devm_request_irq(rtd->platform->dev, dai->irq, no_action, 0,
+			       dev_name(rtd->platform->dev), NULL);
+	if (ret)
+		return ret;
+
+	/* Install FIQ handler */
+	set_fiq_handler(&daifiq_start, &daifiq_end - &daifiq_start);
+
+	return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
+		SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data(GFP_KERNEL),
+		CLPS711X_SNDBUF_SIZE, CLPS711X_SNDBUF_SIZE);
+}
+
+static void clps711x_pcm_free(struct snd_pcm *pcm)
+{
+	struct snd_soc_pcm_runtime *rtd = pcm->private_data;
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+
+	/* Manually free IRQ */
+	devm_free_irq(rtd->platform->dev, dai->irq, NULL);
+
+	release_fiq(&dai->fiq);
+
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static struct snd_soc_platform_driver clps711x_soc_platform_drv = {
+	.ops		= &clps711x_pcm_ops,
+	.pcm_new	= clps711x_pcm_new,
+	.pcm_free	= clps711x_pcm_free,
+};
+
+static int clps711x_enable_fifo(void __iomem *base, u32 channel)
+{
+	unsigned long timeout;
+
+	writel(DAIDR2_FIFOEN | channel, base + DAIDR2);
+	timeout = jiffies + msecs_to_jiffies(5);
+	while (!(readl(base + DAISR) & DAISR_FIFO)) {
+		if (time_is_before_jiffies(timeout))
+			return -ETIMEDOUT;
+		usleep_range(100, 1000);
+	}
+
+	return 0;
+}
+
+static int clps711x_dai_platform_probe(struct platform_device *pdev)
+{
+	unsigned int dai64 = DAI64FS_AUDIOCLKEN | DAI64FS_MCLK256EN;
+	struct device *dev = &pdev->dev;
+	struct clps711x_dai *dai;
+	struct resource *res;
+	int ret;
+
+	/* Check for proper buffer size */
+	BUG_ON(!is_power_of_2(CLPS711X_SNDBUF_SIZE));
+
+	dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL);
+	if (!dai)
+		return -ENOMEM;
+
+	dai->irq = platform_get_irq(pdev, 0);
+	if (dai->irq < 0)
+		return dai->irq;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	dai->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(dai->base))
+		return PTR_ERR(dai->base);
+
+	dai->syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon");
+	if (IS_ERR(dai->syscon))
+		return PTR_ERR(dai->syscon);
+
+	dai->pll_clk = devm_clk_get(dev, "pll");
+	if (IS_ERR(dai->pll_clk))
+		return PTR_ERR(dai->pll_clk);
+
+	dai->pll_hz = clk_get_rate(dai->pll_clk) / 2;
+	if (!dai->pll_hz) {
+		dai64 |= DAI64FS_AUDIOCLKSRC;
+		dev_notice(dev, "Work with external MCLK only\n");
+	}
+
+	platform_set_drvdata(pdev, dai);
+
+	ret = devm_snd_soc_register_component(dev, &clps711x_i2s_component,
+					      &clps711x_dai_driver, 1);
+	if (ret)
+		return ret;
+
+	/* Enable DAI interface */
+	regmap_update_bits(dai->syscon, SYSCON_OFFSET,
+			   SYSCON3_DAISEL | SYSCON3_128FS,
+			   SYSCON3_DAISEL | SYSCON3_128FS);
+
+	/* Clear interrupt flags */
+	writel(~0, dai->base + DAISR);
+
+	/* Enable DAI */
+	writel(DAIR_RESERVED | DAIR_DAIEN | DAIR_ECS | DAIR_LCTM | DAIR_RCTM,
+	       dai->base + DAIR);
+
+	/* Set initial value to DAI register */
+	writel(dai64 | DAI64FS_AUDIV(10), dai->base + DAI64FS);
+
+	/* Enable FIFOs */
+	ret = clps711x_enable_fifo(dai->base, DAIDR2_FIFOLEFT);
+	if (ret)
+		goto out_err;
+	ret = clps711x_enable_fifo(dai->base, DAIDR2_FIFORIGHT);
+	if (ret)
+		goto out_err;
+
+	ret = snd_soc_register_platform(dev, &clps711x_soc_platform_drv);
+
+out_err:
+	if (ret)
+		writel(DAIR_RESERVED, dai->base + DAIR);
+
+	return ret;
+}
+
+static int clps711x_dai_platform_remove(struct platform_device *pdev)
+{
+	struct clps711x_dai *dai = platform_get_drvdata(pdev);
+
+	/* Disable DAI */
+	writel(DAIR_RESERVED, dai->base + DAIR);
+
+	snd_soc_unregister_platform(&pdev->dev);
+
+	return 0;
+}
+
+static const struct of_device_id clps711x_dai_dt_ids[] = {
+	{ .compatible = "cirrus,clps711x-dai", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, clps711x_dai_dt_ids);
+
+static struct platform_driver clps711x_dai_platform_driver = {
+	.driver	= {
+		.name		= "clps711x-dai",
+		.owner		= THIS_MODULE,
+		.of_match_table	= clps711x_dai_dt_ids,
+	},
+	.probe	= clps711x_dai_platform_probe,
+	.remove	= clps711x_dai_platform_remove,
+};
+module_platform_driver(clps711x_dai_platform_driver);
+
+MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
+MODULE_DESCRIPTION("CLPS711X DAI driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/cirrus/clps711x-dai.h b/sound/soc/cirrus/clps711x-dai.h
new file mode 100644
index 0000000..c79d26a
--- /dev/null
+++ b/sound/soc/cirrus/clps711x-dai.h
@@ -0,0 +1,61 @@ 
+/*
+ *  Currus Logic CLPS711X DAI definitions
+ *
+ *  Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
+ *
+ * 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.
+ */
+
+#ifndef __CLPS711X_DAI_H
+#define __CLPS711X_DAI_H
+
+#include <linux/sizes.h>
+
+#define CLPS711X_SNDBUF_SIZE	SZ_128K
+
+#define DAIR			(0x0000)
+# define DAIR_RESERVED		(0x0404)
+# define DAIR_DAIEN		(1 << 16)
+# define DAIR_ECS		(1 << 17)
+# define DAIR_LCTM		(1 << 19)
+# define DAIR_LCRM		(1 << 20)
+# define DAIR_RCTM		(1 << 21)
+# define DAIR_RCRM		(1 << 22)
+# define DAIR_LBM		(1 << 23)
+#define DAIDR0			(0x0040)
+#define DAIDR1			(0x0080)
+#define DAIDR2			(0x00c0)
+# define DAIDR2_FIFOEN		(1 << 15)
+# define DAIDR2_FIFOLEFT	(0x0d << 16)
+# define DAIDR2_FIFORIGHT	(0x11 << 16)
+#define DAISR			(0x0100)
+# define DAISR_RCTS		(1 << 0)
+# define DAISR_RCRS		(1 << 1)
+# define DAISR_LCTS		(1 << 2)
+# define DAISR_LCRS		(1 << 3)
+# define DAISR_RCTU		(1 << 4)
+# define DAISR_RCRO		(1 << 5)
+# define DAISR_LCTU		(1 << 6)
+# define DAISR_LCRO		(1 << 7)
+# define DAISR_RCNF		(1 << 8)
+# define DAISR_RCNE		(1 << 9)
+# define DAISR_LCNF		(1 << 10)
+# define DAISR_LCNE		(1 << 11)
+# define DAISR_FIFO		(1 << 12)
+#define DAI64FS			(0x0600)
+# define DAI64FS_I2SF64		(1 << 0)
+# define DAI64FS_AUDIOCLKEN	(1 << 1)
+# define DAI64FS_AUDIOCLKSRC	(1 << 2)
+# define DAI64FS_MCLK256EN	(1 << 3)
+# define DAI64FS_LOOPBACK	(1 << 5)
+# define DAI64FS_AUDIV_MASK	(0x7f00)
+# define DAI64FS_AUDIV(x)	(((x) << 8) & DAI64FS_AUDIV_MASK)
+
+#ifndef __ASSEMBLY__
+extern unsigned char daifiq_start, daifiq_end;
+#endif
+
+#endif
diff --git a/sound/soc/cirrus/clps711x-fiq.S b/sound/soc/cirrus/clps711x-fiq.S
new file mode 100644
index 0000000..aa2dfb9
--- /dev/null
+++ b/sound/soc/cirrus/clps711x-fiq.S
@@ -0,0 +1,56 @@ 
+/*
+ *  Currus Logic CLPS711X DAI FIQ Handler
+ *
+ *  Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <asm/assembler.h>
+#include <linux/linkage.h>
+
+#include "clps711x-dai.h"
+
+	.text
+	.arm
+
+	.global daifiq_end
+
+ENTRY(daifiq_start)
+	@ r8  - Buffer address
+	@ r9  - Head Pointer
+	@ r10 - Tail Pointer
+	@ r11 - Registers base virtual address
+	# r12 - Temp
+
+daifiq_loop:
+	ldr	r12, [r9]			@ Get Head
+	subs	r12, r12, r10			@ Compare Head & Tail
+	beq	daifiq_play			@ Send zeroes if Head = Tail
+
+	ldr	r12, [r8, r10]			@ Get buffer value
+	add	r10, r10, #4			@ Increment pointer
+	bic	r10, r10, #CLPS711X_SNDBUF_SIZE	@ Mask with buffer size
+
+daifiq_play:
+	@ Put data to FIFOs
+	strh	r12, [r11, #DAIDR1]		@ Left channel
+	mov	r12, r12, lsr #16
+	strh	r12, [r11, #DAIDR0]		@ Right channel
+
+	@ Check DAI Flags (FIFOs not full)
+	ldr	r12, [r11, #DAISR]
+	and	r12, r12, #DAISR_RCNF | DAISR_LCNF
+	cmp	r12, #DAISR_RCNF | DAISR_LCNF
+	beq	daifiq_loop
+
+	@ Clear DAI Interrupt Flags
+	mvn	r12, #0
+	str	r12, [r11, #DAISR]
+
+	@ Return from FIQ
+	subs	pc, lr, #4
+daifiq_end: