summaryrefslogtreecommitdiffstats
path: root/dummy/dummy.c
diff options
context:
space:
mode:
Diffstat (limited to 'dummy/dummy.c')
-rw-r--r--dummy/dummy.c278
1 files changed, 278 insertions, 0 deletions
diff --git a/dummy/dummy.c b/dummy/dummy.c
new file mode 100644
index 0000000..7522345
--- /dev/null
+++ b/dummy/dummy.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ALSA Virtual Soundcard
+ *
+ * dummy_audiopath.c - AVIRT sample Audio Path definition
+ *
+ * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <avirt/core.h>
+
+MODULE_AUTHOR("JOSHANNE <james.oshannessy@fiberdyne.com.au>");
+MODULE_AUTHOR("MFARRUGI <mark.farrugia@fiberdyne.com.au>");
+MODULE_DESCRIPTION("Sample Audio Path Module Interface");
+MODULE_LICENSE("GPL");
+
+#define DUMMY_SAMPLE_RATE 48000
+#define DUMMY_BLOCKSIZE 512
+#define DUMMY_PERIODS_MIN 1
+#define DUMMY_PERIODS_MAX 8
+
+#define get_dummy_ops(substream) \
+ (*(const struct dummy_timer_ops **)(substream)->runtime->private_data)
+
+static struct avirt_coreinfo *coreinfo;
+
+/*******************************************************************************
+ * System Timer Interface
+ *
+ * This is used to call the ALSA PCM callbacks required to notify AVIRT of the
+ * 'periods elapsed', so that buffers can keep getting transmitted here
+ *
+ * (Borrowed from the default ALSA 'dummy' driver (sound/driver/dummy.c))
+ ******************************************************************************/
+struct dummy_timer_ops {
+ int (*create)(struct snd_pcm_substream *);
+ void (*free)(struct snd_pcm_substream *);
+ int (*prepare)(struct snd_pcm_substream *);
+ int (*start)(struct snd_pcm_substream *);
+ int (*stop)(struct snd_pcm_substream *);
+ snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *);
+};
+
+struct dummy_systimer_pcm {
+ /* ops must be the first item */
+ const struct dummy_timer_ops *timer_ops;
+ spinlock_t lock;
+ struct timer_list timer;
+ unsigned long base_time;
+ unsigned int frac_pos; /* fractional sample position (based HZ) */
+ unsigned int frac_period_rest;
+ unsigned int frac_buffer_size; /* buffer_size * HZ */
+ unsigned int frac_period_size; /* period_size * HZ */
+ unsigned int rate;
+ int elapsed;
+ struct snd_pcm_substream *substream;
+};
+
+static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm)
+{
+ mod_timer(&dpcm->timer, jiffies + (dpcm->frac_period_rest + dpcm->rate -
+ 1) / dpcm->rate);
+}
+
+static void dummy_systimer_update(struct dummy_systimer_pcm *dpcm)
+{
+ unsigned long delta;
+
+ delta = jiffies - dpcm->base_time;
+ if (!delta)
+ return;
+ dpcm->base_time += delta;
+ delta *= dpcm->rate;
+ dpcm->frac_pos += delta;
+ while (dpcm->frac_pos >= dpcm->frac_buffer_size)
+ dpcm->frac_pos -= dpcm->frac_buffer_size;
+ while (dpcm->frac_period_rest <= delta) {
+ dpcm->elapsed++;
+ dpcm->frac_period_rest += dpcm->frac_period_size;
+ }
+ dpcm->frac_period_rest -= delta;
+}
+
+static int dummy_systimer_start(struct snd_pcm_substream *substream)
+{
+ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data;
+ spin_lock(&dpcm->lock);
+ dpcm->base_time = jiffies;
+ dummy_systimer_rearm(dpcm);
+ spin_unlock(&dpcm->lock);
+ return 0;
+}
+
+static int dummy_systimer_stop(struct snd_pcm_substream *substream)
+{
+ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data;
+ spin_lock(&dpcm->lock);
+ del_timer(&dpcm->timer);
+ spin_unlock(&dpcm->lock);
+ return 0;
+}
+
+static int dummy_systimer_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct dummy_systimer_pcm *dpcm = runtime->private_data;
+
+ dpcm->frac_pos = 0;
+ dpcm->rate = runtime->rate;
+ dpcm->frac_buffer_size = runtime->buffer_size * HZ;
+ dpcm->frac_period_size = runtime->period_size * HZ;
+ dpcm->frac_period_rest = dpcm->frac_period_size;
+ dpcm->elapsed = 0;
+
+ return 0;
+}
+
+static void dummy_systimer_callback(struct timer_list *t)
+{
+ struct dummy_systimer_pcm *dpcm = from_timer(dpcm, t, timer);
+ unsigned long flags;
+ int elapsed = 0;
+
+ spin_lock_irqsave(&dpcm->lock, flags);
+ dummy_systimer_update(dpcm);
+ dummy_systimer_rearm(dpcm);
+ elapsed = dpcm->elapsed;
+ dpcm->elapsed = 0;
+ spin_unlock_irqrestore(&dpcm->lock, flags);
+ if (elapsed)
+ coreinfo->pcm_buff_complete(dpcm->substream);
+}
+
+static snd_pcm_uframes_t
+ dummy_systimer_pointer(struct snd_pcm_substream *substream)
+{
+ struct dummy_systimer_pcm *dpcm = substream->runtime->private_data;
+ snd_pcm_uframes_t pos;
+
+ spin_lock(&dpcm->lock);
+ dummy_systimer_update(dpcm);
+ pos = dpcm->frac_pos / HZ;
+ spin_unlock(&dpcm->lock);
+ return pos;
+}
+
+static int dummy_systimer_create(struct snd_pcm_substream *substream)
+{
+ struct dummy_systimer_pcm *dpcm;
+
+ dpcm = kzalloc(sizeof(*dpcm), GFP_KERNEL);
+ if (!dpcm)
+ return -ENOMEM;
+ substream->runtime->private_data = dpcm;
+ timer_setup(&dpcm->timer, dummy_systimer_callback, 0);
+ spin_lock_init(&dpcm->lock);
+ dpcm->substream = substream;
+ return 0;
+}
+
+static void dummy_systimer_free(struct snd_pcm_substream *substream)
+{
+ kfree(substream->runtime->private_data);
+}
+
+static const struct dummy_timer_ops dummy_systimer_ops = {
+ .create = dummy_systimer_create,
+ .free = dummy_systimer_free,
+ .prepare = dummy_systimer_prepare,
+ .start = dummy_systimer_start,
+ .stop = dummy_systimer_stop,
+ .pointer = dummy_systimer_pointer,
+};
+
+/*******************************************************************************
+ * Audio Path ALSA PCM Callbacks
+ ******************************************************************************/
+static int dummy_pcm_open(struct snd_pcm_substream *substream)
+{
+ const struct dummy_timer_ops *ops;
+ int err;
+
+ ops = &dummy_systimer_ops;
+ err = ops->create(substream);
+ if (err < 0)
+ return err;
+ get_dummy_ops(substream) = ops;
+
+ return 0;
+}
+
+static int dummy_pcm_close(struct snd_pcm_substream *substream)
+{
+ get_dummy_ops(substream)->free(substream);
+ return 0;
+}
+
+static snd_pcm_uframes_t dummy_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ return get_dummy_ops(substream)->pointer(substream);
+}
+
+static int dummy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ return get_dummy_ops(substream)->start(substream);
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ return get_dummy_ops(substream)->stop(substream);
+ }
+ return -EINVAL;
+}
+
+static int dummy_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ return get_dummy_ops(substream)->prepare(substream);
+}
+
+static struct snd_pcm_ops dummyap_pcm_ops = {
+ .open = dummy_pcm_open,
+ .close = dummy_pcm_close,
+ .prepare = dummy_pcm_prepare,
+ .pointer = dummy_pcm_pointer,
+ .trigger = dummy_pcm_trigger,
+};
+
+/*******************************************************************************
+ * Dummy Audio Path AVIRT registration
+ ******************************************************************************/
+static struct snd_pcm_hardware dummyap_hw = {
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .info = (SNDRV_PCM_INFO_INTERLEAVED // Channel interleaved audio
+ | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = DUMMY_SAMPLE_RATE,
+ .rate_max = DUMMY_SAMPLE_RATE,
+ .periods_min = DUMMY_PERIODS_MIN,
+ .periods_max = DUMMY_PERIODS_MAX,
+};
+
+static struct avirt_audiopath dummyap_module = {
+ .name = "Dummy Audio Path",
+ .version = { 0, 0, 1 },
+ .value = 10,
+ .hw = &dummyap_hw,
+ .pcm_ops = &dummyap_pcm_ops,
+ .blocksize = DUMMY_BLOCKSIZE,
+};
+
+static int __init dummy_init(void)
+{
+ int err = 0;
+
+ pr_info("init()\n");
+
+ err = avirt_register_audiopath(&dummyap_module, &coreinfo);
+ if ((err < 0) || (!coreinfo)) {
+ pr_err("%s: coreinfo is NULL!\n", __func__);
+ return err;
+ }
+
+ return err;
+}
+
+static void __exit dummy_exit(void)
+{
+ pr_info("exit()\n");
+
+ avirt_deregister_audiopath(&dummyap_module);
+}
+
+module_init(dummy_init);
+module_exit(dummy_exit);