This patch adds a driver for the DAI interface found on Cirrus Logic
CLPS711X family CPUs.
Signed-off-by: Alexander Shiyan <shc_work(a)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(a)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(a)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(a)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(a)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