[v2,1/2] ASoC: Add driver for CLPS711X DAI interface
diff mbox

Message ID 1397283328-633-1-git-send-email-shc_work@mail.ru
State New, archived
Headers show

Commit Message

Alexander Shiyan April 12, 2014, 6:15 a.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 | 506 ++++++++++++++++++++++++++++++++++++++++
 sound/soc/cirrus/clps711x-dai.h |  61 +++++
 sound/soc/cirrus/clps711x-fiq.S |  56 +++++
 5 files changed, 636 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

Alexander Shiyan April 24, 2014, 5:04 a.m. UTC | #1
On Sat, 12 Apr 2014 10:15:28 +0400
Alexander Shiyan <shc_work@mail.ru> wrote:

Ping.

> 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 | 506 ++++++++++++++++++++++++++++++++++++++++
>  sound/soc/cirrus/clps711x-dai.h |  61 +++++
>  sound/soc/cirrus/clps711x-fiq.S |  56 +++++
>  5 files changed, 636 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
> 
> diff --git a/sound/soc/cirrus/Kconfig b/sound/soc/cirrus/Kconfig
> index 5477c54..3c8d040 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..ed677c8
> --- /dev/null
> +++ b/sound/soc/cirrus/clps711x-dai.c
> @@ -0,0 +1,506 @@
> +/*
> + *  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/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;
> +
> +	unsigned long			pll_hz;
> +	unsigned long			ext_hz;
> +
> +	void __iomem			*base;
> +	int				irq;
> +
> +	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_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;
> +
> +	writel(DAI64FS_I2SF64 | DAI64FS_AUDIOCLKEN | DAI64FS_MCLK256EN | 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_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)
> +{
> +	/* Silence is provided in the FIQ interrupt routine */
> +	/* This empty function is necessary */
> +	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,
> +};
> +
> +static int clps711x_pcm_new(struct snd_soc_pcm_runtime *rtd)
> +{
> +	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
> +	const char *devname = dev_name(rtd->platform->dev);
> +	int ret;
> +
> +	/* Request FIQ */
> +	dai->fiq.name = devname;
> +	ret = claim_fiq(&dai->fiq);
> +	if (ret)
> +		return ret;
> +
> +	/* Request FIQ interrupt */
> +	ret = request_irq(dai->irq, no_action, 0, devname, 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);
> +
> +	free_irq(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;
> +
> +	return 0;
> +}
> +
> +static int clps711x_dai_platform_probe(struct platform_device *pdev)
> +{
> +	u32 dai64 = DAI64FS_AUDIOCLKEN | DAI64FS_MCLK256EN | DAI64FS_I2SF64;
> +	struct device *dev = &pdev->dev;
> +	struct clps711x_dai *dai;
> +	struct regmap *syscon;
> +	struct resource *res;
> +	struct clk *clk;
> +	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;
> +
> +	syscon = syscon_regmap_lookup_by_compatible("cirrus,clps711x-syscon3");
> +	if (IS_ERR(syscon))
> +		return PTR_ERR(syscon);
> +
> +	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);
> +
> +	clk = devm_clk_get(dev, "pll");
> +	if (IS_ERR(clk))
> +		return PTR_ERR(clk);
> +
> +	dai->pll_hz = clk_get_rate(clk) / 2;
> +	if (!dai->pll_hz) {
> +		dai64 |= DAI64FS_AUDIOCLKSRC;
> +		dev_notice(dev, "External MCLK will be used only\n");
> +	}
> +
> +	clk = devm_clk_get(dev, "mclk");
> +	if (IS_ERR(clk)) {
> +		if (PTR_ERR(clk) == -EPROBE_DEFER)
> +			return -EPROBE_DEFER;
> +	} else
> +		dai->ext_hz = clk_get_rate(clk);
> +
> +	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(syscon, SYSCON_OFFSET,
> +			   SYSCON3_DAISEL | SYSCON3_128FS, SYSCON3_DAISEL);
> +
> +	/* 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);
> +	if (!ret)
> +		return 0;
> +
> +out_err:
> +	/* Disable DAI */
> +	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:
> -- 
> 1.8.3.2
> 
>
Mark Brown April 29, 2014, 4:33 p.m. UTC | #2
On Thu, Apr 24, 2014 at 09:04:56AM +0400, Alexander Shiyan wrote:
> On Sat, 12 Apr 2014 10:15:28 +0400
> Alexander Shiyan <shc_work@mail.ru> wrote:
> 
> Ping.

Don't top post and don't send contentless pings please, it's just more
mail to read and dealing with the ping makes me think I did something
with the patch.
Mark Brown May 1, 2014, 6:29 p.m. UTC | #3
On Sat, Apr 12, 2014 at 10:15:28AM +0400, Alexander Shiyan wrote:

> +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;
> +}

This machinery seems pretty much identical to the FIQ code for i.MX -
I know you don't think anything can be shared but can you please be more
explicit about why?  The contents of the FIQ are different but the
start and stop mechanics don't look so different (and given that the
general idea of what they do is very similar this isn't surprising to
me).

> +static int clps711x_pcm_silence(struct snd_pcm_substream *substream,
> +				int channel, snd_pcm_uframes_t pos,
> +				snd_pcm_uframes_t count)
> +{
> +	/* Silence is provided in the FIQ interrupt routine */
> +	/* This empty function is necessary */
> +	return 0;
> +}

Why is it required?  It seems like what actually happens here is that
the FIQ handles underflow by playing zero bytes but that's not quite the
same thing as what silence does - we can still underflow after sending
silence, this is about sending an explicit amount of silence.
Mark Brown May 1, 2014, 7:01 p.m. UTC | #4
On Thu, May 01, 2014 at 10:54:32PM +0400, Alexander Shiyan wrote:
> Thu, 1 May 2014 11:29:35 -0700 ?? Mark Brown <broonie@kernel.org>:

> > This machinery seems pretty much identical to the FIQ code for i.MX -
> > I know you don't think anything can be shared but can you please be more
> > explicit about why?  The contents of the FIQ are different but the
> > start and stop mechanics don't look so different (and given that the
> > general idea of what they do is very similar this isn't surprising to
> > me).

> That is, you really want to make some common code for this?

Well, I'd like to understand why we're not doing common code in more
detail - it's pretty fiddly and detailed stuff to be cut'n'pasting.  On
the other hand if it looks horrible to do that then perhaps that's
actually the best way to go.  Either factor it out or give a bit more
detail on why that's not the way forwards.

> OK, I will review this again, but where do you think it should be located?

A file in sound/soc (soc-pcm-fiq.c or something) perhaps?

Patch
diff mbox

diff --git a/sound/soc/cirrus/Kconfig b/sound/soc/cirrus/Kconfig
index 5477c54..3c8d040 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..ed677c8
--- /dev/null
+++ b/sound/soc/cirrus/clps711x-dai.c
@@ -0,0 +1,506 @@ 
+/*
+ *  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/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;
+
+	unsigned long			pll_hz;
+	unsigned long			ext_hz;
+
+	void __iomem			*base;
+	int				irq;
+
+	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_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;
+
+	writel(DAI64FS_I2SF64 | DAI64FS_AUDIOCLKEN | DAI64FS_MCLK256EN | 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_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)
+{
+	/* Silence is provided in the FIQ interrupt routine */
+	/* This empty function is necessary */
+	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,
+};
+
+static int clps711x_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct clps711x_dai *dai = snd_soc_platform_get_drvdata(rtd->platform);
+	const char *devname = dev_name(rtd->platform->dev);
+	int ret;
+
+	/* Request FIQ */
+	dai->fiq.name = devname;
+	ret = claim_fiq(&dai->fiq);
+	if (ret)
+		return ret;
+
+	/* Request FIQ interrupt */
+	ret = request_irq(dai->irq, no_action, 0, devname, 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);
+
+	free_irq(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;
+
+	return 0;
+}
+
+static int clps711x_dai_platform_probe(struct platform_device *pdev)
+{
+	u32 dai64 = DAI64FS_AUDIOCLKEN | DAI64FS_MCLK256EN | DAI64FS_I2SF64;
+	struct device *dev = &pdev->dev;
+	struct clps711x_dai *dai;
+	struct regmap *syscon;
+	struct resource *res;
+	struct clk *clk;
+	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;
+
+	syscon = syscon_regmap_lookup_by_compatible("cirrus,clps711x-syscon3");
+	if (IS_ERR(syscon))
+		return PTR_ERR(syscon);
+
+	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);
+
+	clk = devm_clk_get(dev, "pll");
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	dai->pll_hz = clk_get_rate(clk) / 2;
+	if (!dai->pll_hz) {
+		dai64 |= DAI64FS_AUDIOCLKSRC;
+		dev_notice(dev, "External MCLK will be used only\n");
+	}
+
+	clk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(clk)) {
+		if (PTR_ERR(clk) == -EPROBE_DEFER)
+			return -EPROBE_DEFER;
+	} else
+		dai->ext_hz = clk_get_rate(clk);
+
+	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(syscon, SYSCON_OFFSET,
+			   SYSCON3_DAISEL | SYSCON3_128FS, SYSCON3_DAISEL);
+
+	/* 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);
+	if (!ret)
+		return 0;
+
+out_err:
+	/* Disable DAI */
+	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: