From: Davide Bonfanti <davide.bonfa...@bticino.it>

    This driver implements a pcm interface without the use of a DMA but with
    a copy_from_user.
    There's a buffer in the driver that is filled with davinci_pcm_copy.
    When pcm is running, a TIMER interrupt is activated in order to fill
    HW FIFO.
    BUG: It happens sometimes that the peripheral stops working so there's a
    trap.

    This patch has been developed against the
    http://git.kernel.org/pub/scm/linux/kernel/git/khilman/linux-davinci.git
    git tree and tested on bmx board.

    Signed-off-by: Davide Bonfanti <davide.bonfa...@bticino.it>
    Signed-off-by: Raffaele Recalcati <raffaele.recalc...@bticino.it>
---
 sound/soc/davinci/Makefile                   |    1 +
 sound/soc/davinci/davinci-pcm-copy.h         |   52 +++++
 sound/soc/davinci/davinci-pcm-copyfromuser.c |  278 ++++++++++++++++++++++++++
 sound/soc/davinci/davinci-pcm.h              |    1 +
 4 files changed, 332 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/davinci/davinci-pcm-copy.h
 create mode 100644 sound/soc/davinci/davinci-pcm-copyfromuser.c

diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile
index a93679d..7d6a9a1 100644
--- a/sound/soc/davinci/Makefile
+++ b/sound/soc/davinci/Makefile
@@ -1,5 +1,6 @@
 # DAVINCI Platform Support
 snd-soc-davinci-objs := davinci-pcm.o
+snd-soc-davinci-objs += davinci-pcm-copyfromuser.o
 snd-soc-davinci-i2s-objs := davinci-i2s.o
 snd-soc-davinci-mcasp-objs:= davinci-mcasp.o
 snd-soc-davinci-vcif-objs:= davinci-vcif.o
diff --git a/sound/soc/davinci/davinci-pcm-copy.h 
b/sound/soc/davinci/davinci-pcm-copy.h
new file mode 100644
index 0000000..c143fb3
--- /dev/null
+++ b/sound/soc/davinci/davinci-pcm-copy.h
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright (C) 2010 Bticino S.p.a
+ * Author: Davide Bonfanti <davide.bonfa...@bticino.it>
+ *
+ * Contributors:
+ *     Raffaele Recalcati <raffaele.recalc...@bticino.it>
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _DAVINCI_PCM_COPY_H
+#define _DAVINCI_PCM_COPY_H
+
+struct davinci_pcm_copy_ops {
+       /* called to perform initial settings - optional */
+       void (*init)(void);
+       /* called to enable codec - mandatory */
+       void (*enable)(void);
+       /* called to push one data into hw_fifo - mandatory */
+       void (*write)(u16);
+       /* called to wait hw_fifo is ready for more data - optional */
+       void (*wait_fifo_ready)(void);
+       /* called to get hw_fifo size - mandatory */
+       int (*get_fifo_size)(void);
+       /* called to get number of samples already into the hw_fifo */
+       int (*get_fifo_status)(void);
+};
+
+struct davinci_pcm_copy_platform_data {
+       /* length required for samples buffer in bytes */
+       int buffer_size;
+       /* minimum time in ps between an interrupt and another */
+       int min_interrupt_interval_ps;
+       /*
+        * margin between next interrupt occurrency and hw_fifo end_of_play
+        * using loaded samples
+        */
+       int interrupt_margin_ps;
+       /* codec operations as explained above */
+       struct davinci_pcm_copy_ops *ops;
+};
+
+#endif /* _DAVINCI_PCM_COPY_H */
diff --git a/sound/soc/davinci/davinci-pcm-copyfromuser.c 
b/sound/soc/davinci/davinci-pcm-copyfromuser.c
new file mode 100644
index 0000000..7494afe
--- /dev/null
+++ b/sound/soc/davinci/davinci-pcm-copyfromuser.c
@@ -0,0 +1,278 @@
+/*
+ *
+ * Copyright (C) 2010 Bticino S.p.a
+ * Author: Davide Bonfanti <davide.bonfa...@bticino.it>
+ *
+ * Contributors:
+ *     Raffaele Recalcati <raffaele.recalc...@bticino.it>
+ *
+ * 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 version 2.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/hrtimer.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <mach/gpio.h>
+
+#include "davinci-pcm.h"
+#include "davinci-pcm-copy.h"
+
+#define DEF_BUF_SIZE   2048
+#define DEF_MIN_INT    500000
+#define DEF_INT_MARGIN 500000
+
+int pointer_sub;
+int hw_fifo_size;
+u16 *local_buffer;
+static struct hrtimer hrtimer;
+struct snd_pcm_substream *substream_loc;
+int ns_for_interrupt = 1500000;
+int min_interrupt_ps;
+int interrupt_margin;
+
+struct davinci_pcm_copy_ops *ops;
+
+static struct snd_pcm_hardware pcm_hardware_playback = {
+       .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER),
+       .formats = (SNDRV_PCM_FMTBIT_S16_LE),
+       .rates = (SNDRV_PCM_RATE_8000),
+       .rate_min = 8000,
+       .rate_max = 8000,
+       .channels_min = 1,
+       .channels_max = 1,
+       .period_bytes_min = 512,
+       .period_bytes_max = 512,
+       .fifo_size = 0,
+};
+
+/*
+ * How this driver works...
+ *
+ * This driver implements a pcm interface without the use of a DMA but with
+ * a copy_from_user.
+ * There's a buffer of {platform_data->buffer_size} bytes in the driver
+ * that is filled with davinci_pcm_copy.
+ * When pcm is running, a TIMER interrupt is activated  in order to fill
+ * HW FIFO.
+ * It happens that the peripheral stop working so there's a trap...
+ */
+
+static snd_pcm_uframes_t
+davinci_pcm_pointer(struct snd_pcm_substream *substream)
+{
+       return pointer_sub;
+}
+
+static int davinci_pcm_open(struct snd_pcm_substream *substream)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct snd_pcm_hardware *ppcm;
+       static ktime_t wakeups_per_second;
+       int ret = 0;
+
+       pointer_sub = 0;
+
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+               ppcm = &pcm_hardware_playback;
+       else
+               return -ENODEV;
+
+       snd_soc_set_runtime_hwparams(substream, ppcm);
+       /* ensure that buffer size is a multiple of period size */
+       ret = snd_pcm_hw_constraint_integer(runtime,
+                                           SNDRV_PCM_HW_PARAM_PERIODS);
+       if (ret < 0)
+               return ret;
+
+       hrtimer_start(&hrtimer, wakeups_per_second, HRTIMER_MODE_REL);
+       ops->enable();
+       return 0;
+}
+
+static int davinci_pcm_close(struct snd_pcm_substream *substream)
+{
+       hrtimer_cancel(&hrtimer);
+       return 0;
+}
+
+static int davinci_pcm_hw_params(struct snd_pcm_substream *substream,
+                                struct snd_pcm_hw_params *hw_params)
+{
+       long rate;
+
+       rate = hw_params->rate_num * hw_params->rate_den;
+       rate = hw_params->msbits * (1000000000 / rate);
+
+       /* let's take some margin of */
+       rate -= interrupt_margin;
+
+       /* assure the interrupt doesn't occupy too many resources */
+       ns_for_interrupt = rate > min_interrupt_ps ? rate : min_interrupt_ps;
+
+       return snd_pcm_lib_malloc_pages(substream,
+                                       params_buffer_bytes(hw_params));
+}
+
+static int davinci_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+       return snd_pcm_lib_free_pages(substream);
+}
+
+static int davinci_pcm_copy(struct snd_pcm_substream *substream, int channel,
+       snd_pcm_uframes_t hwoff, void __user *buf, snd_pcm_uframes_t frames)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+
+       if (copy_from_user(local_buffer + hwoff, buf,
+           frames_to_bytes(runtime, frames))) {
+               printk(KERN_ERR "ERROR COPY_FROM_USER\n");
+               return -EFAULT;
+       }
+       return 0;
+}
+
+static struct snd_pcm_ops davinci_pcm_ops = {
+       .open =         davinci_pcm_open,
+       .close =        davinci_pcm_close,
+       .ioctl =        snd_pcm_lib_ioctl,
+       .hw_params =    davinci_pcm_hw_params,
+       .hw_free =      davinci_pcm_hw_free,
+       .pointer =      davinci_pcm_pointer,
+       .copy =         davinci_pcm_copy,
+};
+
+static u64 davinci_pcm_dmamask = 0xffffffff;
+
+static enum hrtimer_restart dm_vc_irq(struct hrtimer *handle)
+{
+       int fifo, diff, per_size, buf_size;
+       static int last_ptr;
+       gpio_set_value(69, 1);
+
+       if (substream_loc->runtime && substream_loc->runtime->status &&
+           snd_pcm_running(substream_loc)) {
+               fifo = ops->get_fifo_status();
+               if (fifo >= (hw_fifo_size - 1))
+                       ops->enable();
+
+               buf_size = substream_loc->runtime->buffer_size;
+               per_size = substream_loc->runtime->period_size;
+               for (; fifo < hw_fifo_size; fifo++) {
+                       ops->write(local_buffer[pointer_sub++]);
+                       pointer_sub %= buf_size;
+                       if (ops->wait_fifo_ready)
+                               ops->wait_fifo_ready();
+               }
+               if (last_ptr >= pointer_sub)
+                       diff = buf_size + pointer_sub - last_ptr;
+               else
+                       diff = pointer_sub - last_ptr;
+               if (diff >= per_size) {
+                       snd_pcm_period_elapsed(substream_loc);
+                       last_ptr += per_size;
+                       if (last_ptr >= buf_size)
+                               last_ptr -= buf_size;
+               }
+       } else
+               last_ptr = 0;
+       hrtimer_add_expires_ns(&hrtimer, ns_for_interrupt);
+       gpio_set_value(69, 0);
+       return HRTIMER_RESTART;
+}
+
+static int davinci_pcm_new(struct snd_card *card,
+                          struct snd_soc_dai *dai, struct snd_pcm *pcm)
+{
+       struct snd_dma_buffer *buf;
+       struct snd_pcm_substream *substream;
+
+       if (!card->dev->dma_mask)
+               card->dev->dma_mask = &davinci_pcm_dmamask;
+       if (!card->dev->coherent_dma_mask)
+               card->dev->coherent_dma_mask = 0xffffffff;
+
+       gpio_direction_output(69, 0);
+
+       if (dai->playback.channels_min) {
+               substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+               buf = &substream->dma_buffer;
+               buf->dev.type = SNDRV_DMA_TYPE_DEV;
+               buf->dev.dev = pcm->card->dev;
+               buf->private_data = NULL;
+               buf->bytes = pcm_hardware_playback.buffer_bytes_max;
+               substream_loc = substream;
+       }
+       hrtimer_init(&hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+       hrtimer.function = dm_vc_irq;
+
+       return 0;
+}
+
+int davinci_pcm_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *dev = platform_get_drvdata(pdev);
+       struct davinci_pcm_copy_platform_data *data;
+
+       data = dev->codec_data;
+       ops = data->ops;
+
+       if ((!ops) || (!ops->enable) || (!ops->write) ||
+           (!ops->get_fifo_size) || (!ops->get_fifo_status))
+               return -EINVAL;
+       min_interrupt_ps = data->min_interrupt_interval_ps ?
+                          data->min_interrupt_interval_ps : DEF_MIN_INT;
+       interrupt_margin = data->interrupt_margin_ps ?
+                          data->interrupt_margin_ps : DEF_INT_MARGIN;
+       pcm_hardware_playback.buffer_bytes_max = data->buffer_size ?
+                          data->buffer_size : DEF_BUF_SIZE;
+       pcm_hardware_playback.periods_min = data->buffer_size / 512;
+       pcm_hardware_playback.periods_max = data->buffer_size / 512;
+       local_buffer = kmalloc(data->buffer_size, GFP_KERNEL);
+       if (ops->init)
+               ops->init();
+       hw_fifo_size = ops->get_fifo_size();
+
+       return 0;
+}
+
+struct snd_soc_platform davinci_soc_platform_copy = {
+       .name =         "davinci-audio-copy",
+       .pcm_ops =      &davinci_pcm_ops,
+       .pcm_new =      davinci_pcm_new,
+       .probe =        davinci_pcm_probe,
+};
+EXPORT_SYMBOL_GPL(davinci_soc_platform_copy);
+
+static int __init davinci_soc_copy_platform_init(void)
+{
+       return snd_soc_register_platform(&davinci_soc_platform_copy);
+}
+module_init(davinci_soc_copy_platform_init);
+
+static void __exit davinci_soc_copy_platform_exit(void)
+{
+       snd_soc_unregister_platform(&davinci_soc_platform_copy);
+}
+module_exit(davinci_soc_copy_platform_exit);
+
+MODULE_AUTHOR("bticino s.p.a.");
+MODULE_DESCRIPTION("TI DAVINCI PCM copy_from_user module");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h
index 0764944..cb7c2aa 100644
--- a/sound/soc/davinci/davinci-pcm.h
+++ b/sound/soc/davinci/davinci-pcm.h
@@ -29,5 +29,6 @@ struct davinci_pcm_dma_params {
 
 
 extern struct snd_soc_platform davinci_soc_platform;
+extern struct snd_soc_platform davinci_soc_platform_copy;
 
 #endif
-- 
1.7.0.4

_______________________________________________
Davinci-linux-open-source mailing list
Davinci-linux-open-source@linux.davincidsp.com
http://linux.davincidsp.com/mailman/listinfo/davinci-linux-open-source

Reply via email to