summaryrefslogtreecommitdiffstats
path: root/alsa.c
diff options
context:
space:
mode:
authorJames O'Shannessy <james.oshannessy@fiberdyne.com.au>2018-08-27 15:08:14 +1000
committerMark Farrugia <mark.farrugia@fiberdyne.com.au>2018-10-26 17:27:24 +1100
commitbc8c3a602bceaba0e6d34a1ba8b776b56b00d766 (patch)
treeae8cec69c910144611e06f272033cc8c2aee7032 /alsa.c
parent416c9b0f9b816a6b2eb5c544f21567ad286dd4be (diff)
Public push of AVIRT.
Follow readme for building in/out of tree for Ubuntu/AGL/etc. Signed-off-by: James O'Shannessy <james.oshannessy@fiberdyne.com.au>
Diffstat (limited to 'alsa.c')
-rwxr-xr-xalsa.c255
1 files changed, 255 insertions, 0 deletions
diff --git a/alsa.c b/alsa.c
new file mode 100755
index 0000000..0b08744
--- /dev/null
+++ b/alsa.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ALSA Virtual Soundcard
+ *
+ * alsa.c - ALSA PCM driver for virtual ALSA card
+ *
+ * Copyright (C) 2010-2018 Fiberdyne Systems Pty Ltd
+ */
+
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+
+#include "alsa.h"
+#include "core.h"
+
+static struct avirt_alsa_driver *_driver = NULL;
+
+extern struct snd_pcm_ops pcm_ops;
+
+/**
+ * pcm_constructor - Constructs the ALSA PCM middle devices for this driver
+ * @card: The snd_card struct to construct the devices for
+ * @return 0 on success or error code otherwise
+ */
+static int pcm_constructor(struct snd_card *card)
+{
+ struct snd_pcm *pcm;
+ int i;
+
+ // Allocate Playback PCM instances
+ for (i = 0; i < _driver->playback.devices; i++) {
+ CHK_ERR(snd_pcm_new(card,
+ _driver->playback.config[i].devicename, i,
+ 1, 0, &pcm));
+
+ /** Register driver callbacks */
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+ pcm->info_flags = 0;
+ strcpy(pcm->name, _driver->playback.config[i].devicename);
+ }
+
+ // Allocate Capture PCM instances
+ for (i = 0; i < _driver->capture.devices; i++) {
+ CHK_ERR(snd_pcm_new(card, _driver->capture.config[i].devicename,
+ i, 0, 1, &pcm));
+
+ /** Register driver callbacks */
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_ops);
+
+ pcm->info_flags = 0;
+ strcpy(pcm->name, _driver->capture.config[i].devicename);
+ }
+
+ return 0;
+}
+
+/**
+ * alloc_dev_config - Allocates memory for ALSA device configuration
+ * @return: 0 on success or error code otherwise
+ */
+static int alloc_dev_config(struct avirt_alsa_dev_config **devconfig,
+ struct avirt_alsa_dev_config *userconfig,
+ unsigned numdevices)
+{
+ if (numdevices == 0)
+ return 0;
+
+ *devconfig = kzalloc(sizeof(struct avirt_alsa_dev_config) * numdevices,
+ GFP_KERNEL);
+ if (!(*devconfig))
+ return -ENOMEM;
+
+ memcpy(*devconfig, userconfig,
+ sizeof(struct avirt_alsa_dev_config) * numdevices);
+
+ return 0;
+}
+
+/**
+ * alloc_dev_streams - Initializes ALSA device substream buffers
+ * @return: 0 on success or error code otherwise
+ */
+static int alloc_dev_streams(struct avirt_alsa_dev_config *config,
+ struct avirt_alsa_stream **streams,
+ unsigned numdevices)
+{
+ unsigned i;
+
+ if (numdevices == 0)
+ return 0;
+
+ *streams = kzalloc(sizeof(struct avirt_alsa_stream) * numdevices,
+ GFP_KERNEL);
+
+ if (!(*streams))
+ return -EFAULT;
+
+ for (i = 0; i < numdevices; i++)
+ (*streams)[i].hw_frame_idx = 0;
+
+ return 0;
+}
+
+struct avirt_alsa_dev_group *avirt_alsa_get_dev_group(int direction)
+{
+ if (!_driver) {
+ pr_err("[%s] _driver is NULL", __func__);
+ return NULL;
+ }
+
+ switch (direction) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ return &_driver->playback;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ return &_driver->capture;
+ default:
+ pr_err("[%s] Direction must be SNDRV_PCM_STREAM_XXX!",
+ __func__);
+ return NULL;
+ }
+}
+
+/**
+ * avirt_alsa_init - Initializes the ALSA driver
+ * @return: 0 on success or error code otherwise
+ */
+int avirt_alsa_init()
+{
+ // Allocate memory for the driver
+ _driver = kzalloc(sizeof(struct avirt_alsa_driver), GFP_KERNEL);
+ if (!_driver)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/**
+ * avirt_alsa_configure_pcm- Configure the PCM device
+ * @config: Device configuration structure array
+ * @direction: Direction of PCM (SNDRV_PCM_STREAM_XXX)
+ * @numdevices: Number of devices (array length)
+ * @return: 0 on success or error code otherwise
+ */
+int avirt_alsa_configure_pcm(struct avirt_alsa_dev_config *config,
+ int direction, unsigned numdevices)
+{
+ struct avirt_alsa_dev_group *group;
+
+ group = avirt_alsa_get_dev_group(direction);
+ CHK_NULL(group);
+
+ CHK_ERR(alloc_dev_config(&group->config, config, numdevices));
+
+ group->devices = numdevices;
+
+ CHK_ERR(alloc_dev_streams(group->config, &group->streams,
+ group->devices));
+ return 0;
+}
+
+/**
+ * avirt_alsa_register - Registers the ALSA driver
+ * @devptr: Platform driver device
+ * @return: 0 on success or error code otherwise
+ */
+int avirt_alsa_register(struct platform_device *devptr)
+{
+ struct snd_card *card;
+ static struct snd_device_ops device_ops;
+
+ if (!_driver)
+ return -EFAULT;
+
+ // Create the card instance
+ CHK_ERR_V(snd_card_new(&devptr->dev, SNDRV_DEFAULT_IDX1, "avirt",
+ THIS_MODULE, 0, &card),
+ "Failed to create sound card");
+
+ strcpy(card->driver, "avirt-alsa-device");
+ strcpy(card->shortname, "avirt");
+ strcpy(card->longname, "A virtual sound card driver for ALSA");
+ _driver->card = card;
+
+ // Create new sound device
+ CHK_ERR_V((snd_device_new(card, SNDRV_DEV_LOWLEVEL, _driver,
+ &device_ops)),
+ "Failed to create sound device");
+
+ CHK_ERR((pcm_constructor(card)));
+
+ /** Register with the ALSA framework */
+ CHK_ERR_V(snd_card_register(card), "Device registration failed");
+
+ return 0;
+}
+
+/**
+ * avirt_alsa_deregister - Deregisters the ALSA driver
+ * @devptr: Platform driver device
+ * @return: 0 on success or error code otherwise
+ */
+int avirt_alsa_deregister(void)
+{
+ snd_card_free(_driver->card);
+
+ if (_driver->playback.config)
+ kfree(_driver->playback.config);
+ if (_driver->playback.streams)
+ kfree(_driver->playback.streams);
+ if (_driver->capture.config)
+ kfree(_driver->capture.config);
+ if (_driver->capture.streams)
+ kfree(_driver->capture.streams);
+
+ kfree(_driver);
+
+ return 0;
+}
+
+/**
+ * pcm_buff_complete_cb - PCM buffer complete callback
+ * @substreamid: pointer to ALSA PCM substream
+ * @return 0 on success or error code otherwise
+ *
+ * This should be called from a child Audio Path once it has finished processing
+ * the pcm buffer
+ */
+int pcm_buff_complete_cb(struct snd_pcm_substream *substream)
+{
+ int maxframe, deviceid;
+ struct avirt_audiopath *audiopath;
+ struct avirt_alsa_dev_group *group;
+
+ deviceid = substream->pcm->device;
+
+ group = avirt_alsa_get_dev_group(substream->stream);
+ CHK_NULL(group);
+
+ audiopath = avirt_get_current_audiopath();
+ CHK_NULL_V(audiopath, "Cannot find Audio Path!");
+
+ group->streams[deviceid].hw_frame_idx += audiopath->blocksize;
+ maxframe = audiopath->blocksize * audiopath->hw->periods_max;
+
+ // Once the index reaches the DMA buffer boundary, reset it to 0
+ if (group->streams[deviceid].hw_frame_idx >= maxframe)
+ group->streams[deviceid].hw_frame_idx = 0;
+
+ // Notify ALSA middle layer of the elapsed period boundary
+ snd_pcm_period_elapsed(group->streams[deviceid].substream);
+
+ return 0;
+}