// SPDX-License-Identifier: GPL-2.0
/*
 * AVIRT - ALSA Virtual Soundcard
 *
 * Copyright (c) 2010-2018 Fiberdyne Systems Pty Ltd
 *
 * core.c - AVIRT core internals
 */

#include <linux/module.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <sound/initval.h>
#include <linux/platform_device.h>

#include "core.h"

MODULE_AUTHOR("James O'Shannessy <james.oshannessy@fiberdyne.com.au>");
MODULE_AUTHOR("Mark Farrugia <mark.farrugia@fiberdyne.com.au>");
MODULE_DESCRIPTION("ALSA virtual, dynamic soundcard");
MODULE_LICENSE("GPL v2");

#define D_LOGNAME "core"

#define D_INFOK(fmt, args...) DINFO(D_LOGNAME, fmt, ##args)
#define D_PRINTK(fmt, args...) DDEBUG(D_LOGNAME, fmt, ##args)
#define D_ERRORK(fmt, args...) DERROR(D_LOGNAME, fmt, ##args)

#define PCM_OPS_SET(pcm_ops_ap, pcm_ops, cb)                                   \
	((pcm_ops_ap->cb) ? ((*pcm_ops)->cb = pcm_ops_ap->cb) :                \
			    ((*pcm_ops)->cb = NULL));

#define SND_AVIRT_DRIVER "snd_avirt"

static struct snd_avirt_core core = {
	.version = { 0, 0, 1 },
	.stream_count = 0,
	.streams_configured = false,
};

static LIST_HEAD(audiopath_list);

/**
 * pcm_private_free - callback function to free private data allocated to pcm
 * @pcm: the PCM object
 */
static void pcm_private_free(struct snd_pcm *pcm)
{
	struct snd_avirt_private_data *avirt_private_data;

	/* Free Audio Path private data */
	avirt_private_data = (struct snd_avirt_private_data *)pcm->private_data;
	if (avirt_private_data) {
		if (avirt_private_data->ap_private_data[0] &&
		    avirt_private_data->ap_private_free)
			avirt_private_data->ap_private_free(pcm);
	}

	kfree(pcm->private_data);
}

static struct snd_pcm *snd_avirt_pcm_create(struct snd_avirt_stream *stream)
{
	struct snd_avirt_private_data *avirt_private_data;
	struct snd_pcm *pcm;
	bool playback = false, capture = false;
	int err;

	if (!stream->direction)
		playback = true;
	else
		capture = true;

	/** Special case: loopback */
	if (!strcmp(stream->map, "ap_loopback")) {
		playback = true;
		capture = true;
	}

	if (stream->internal) {
		err = snd_pcm_new_internal(core.card, stream->name,
					   stream->device, playback, capture,
					   &pcm);
	} else {
		err = snd_pcm_new(core.card, stream->name, stream->device,
				  playback, capture, &pcm);
	}

	if (err < 0) {
		D_ERRORK("Failed to create PCM device for stream: '%s'",
			 stream->name);
		return ERR_PTR(err);
	}

	/** Register driver callbacks */
	if (playback)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
				stream->pcm_ops);
	if (capture)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, stream->pcm_ops);

	pcm->info_flags = 0;
	strncpy(pcm->name, stream->name, MAX_NAME_LEN);

	avirt_private_data = kzalloc(sizeof(*avirt_private_data), GFP_KERNEL);
	pcm->private_data = avirt_private_data;
	// Set the private free function for the private user data
	pcm->private_free = pcm_private_free;

	snd_device_register(core.card, pcm);

	return pcm;
}

void snd_avirt_stream_try_destroy(struct snd_avirt_stream *stream)
{
	unsigned long _flags;
	struct snd_pcm_substream *substream =
		stream->pcm->streams[stream->direction].substream;

	snd_pcm_stream_lock_irqsave(substream, _flags);
	if (substream->runtime) {
		if (snd_pcm_running(substream)) {
			if (0 !=
			    snd_pcm_stop(substream, SNDRV_PCM_STATE_SUSPENDED))
				D_ERRORK("Could not stop PCM '%s'",
					 stream->name);
		}
	}
	snd_pcm_stream_unlock_irqrestore(substream, _flags);

	snd_device_free(core.card, stream->pcm);
	kfree(stream->pcm_ops);
	kfree(stream);

	core.stream_count--;
}

static struct snd_avirt_route *snd_avirt_route_get(const char *uid)
{
	struct list_head *entry;
	struct config_item *item;
	struct snd_avirt_route *route;

	list_for_each (entry, &core.route_group->cg_children) {
		item = container_of(entry, struct config_item, ci_entry);
		route = snd_avirt_route_from_config_item(item);
		if (!strcmp(route->uid, uid))
			return route;
	}

	return NULL;
}

/**
 * int snd_avirt_route_try_complete - Set up remaining parameters for a route.
 *                                    Channels, sink, and source Audio Paths
 *                                    should be set when calling this function.
 * @stream: The route to attempt to finalize parameters for.
 * @return: 0 on success, negative ERRNO on failure
 */
int snd_avirt_route_try_complete(struct snd_avirt_route *route)
{
	return 0;
}

/**
 * int snd_avirt_stream_try_complete - Set up remaining parameters for a stream.
 *                                     Channels and map should be set when
 *                                     calling this function.
 * @stream: The stream to attempt to finalize parameters for.
 * @return: 0 on success, negative ERRNO on failure
 */
int snd_avirt_stream_try_complete(struct snd_avirt_stream *stream)
{
	struct snd_avirt_audiopath *audiopath;
	struct snd_avirt_route *route;
	struct snd_pcm_ops *pcm_ops_ap;

	if (snd_avirt_streams_configured())
		return -EPERM;

	if ((stream->channels == 0) || (!strcmp(stream->map, "none")))
		return -EPERM;

	audiopath = snd_avirt_audiopath_get(stream->map);
	if (!audiopath) {
		D_ERRORK("Cannot find Audio Path uid: '%s'!", stream->map);
	}

	/* Check for any routes that have been created for this stream */
	route = snd_avirt_route_get(stream->name);
	if (route) {
		if (audiopath == route->endpoint_ap[SND_AVIRT_ROUTE_SOURCE])
			route->endpoint_stream[SND_AVIRT_ROUTE_SOURCE] = stream;
		else if (audiopath == route->endpoint_ap[SND_AVIRT_ROUTE_SINK])
			route->endpoint_stream[SND_AVIRT_ROUTE_SINK] = stream;
		else {
			D_INFOK("Cannot set route. Audio Path not compatible");
			return -EPERM;
		}

		stream->route = route;
	}

	/* Set up PCM ops */
	if (!stream->direction)
		pcm_ops_ap = (struct snd_pcm_ops *)audiopath->pcm_playback_ops;
	else
		pcm_ops_ap = (struct snd_pcm_ops *)audiopath->pcm_capture_ops;

	if (!pcm_ops_ap) {
		D_ERRORK("No PCM ops for direction '%s' for Audio Path: %s",
			 (stream->direction) ? "capture" : "playback",
			 stream->map);
		return -EFAULT;
	}

	/* Set PCM ops for the Audio Path*/
	PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, pointer);
	PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, get_time_info);
	PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, fill_silence);
	PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, copy_user);
	PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, copy_kernel);
	PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, mmap);
	PCM_OPS_SET(pcm_ops_ap, &stream->pcm_ops, ack);

	/* If not created, create the PCM device now */
	if (!stream->pcm) {
		stream->pcm = snd_avirt_pcm_create(stream);
		if (IS_ERR_OR_NULL(stream->pcm))
			return -EFAULT;
	}

	return 0;
}

/**
 * snd_avirt_streams_get - Get AVIRT streams for a given Audio Path map
 * @map: The Audio Path UID whose streams to find.
 * @stream_array: To be populated with streams.
 * @return: The number of streams found for the Audio Path.
 */
static int snd_avirt_streams_get(const char *map,
				 struct snd_avirt_stream_array *stream_array)
{
	struct list_head *entry;
	struct config_item *item;
	struct snd_avirt_stream *stream;

	list_for_each (entry, &core.stream_group->cg_children) {
		item = container_of(entry, struct config_item, ci_entry);
		stream = snd_avirt_stream_from_config_item(item);
		if (!strcmp(map, stream->map)) {
			stream_array->streams[stream_array->count++] = stream;
		}
	}

	return stream_array->count;
}

/**
 * snd_avirt_route_endpoint_pos - get route endpoint position for Audio Path
 * @pcm: The PCM whose route to inspect.
 * @ap_uid: The Audio Path UID to get
 * @endpoint: The route position (SND_AVIRT_ROUTE_SOURCE or SND_AVIRT_ROUTE_SINK)
 *            of the Audio Path in it's route (if any).
 * @return: 0 if an Audio Path is found in the route, -1 if there is no route,
 *          or -2 otherwise.
 */
int snd_avirt_route_endpoint_pos(struct snd_pcm *pcm, const char *ap_uid,
				 snd_avirt_route_endpoint *endpoint)
{
	struct snd_avirt_audiopath *endpoint_ap;
	struct snd_avirt_stream *stream;
	int i;

	stream = snd_avirt_stream_find_by_device(pcm->device);
	if (IS_ERR_VALUE(stream) || !stream)
		goto exit_err;

	if (!stream->route)
		return -1;

	for (i = 0; i < 2; i++) {
		endpoint_ap = stream->route->endpoint_ap[i];
		if (endpoint_ap)
			if (!strcmp(endpoint_ap->uid, ap_uid)) {
				*endpoint = i;
				return 0;
			}
	}

exit_err:
	D_ERRORK("Could not find Audio Path '%s' in route '%s'", ap_uid,
		 stream->route->uid);
	return -2;
}

/**
 * snd_avirt_route_endpoint_copy - get endpoint copy function for a given
 *                                 Audio Path's source/sink.
 * @ap: The Audio Path whose endpoint copy function to find.
 * @endpoint: The endpoint (SND_AVIRT_ROUTE_SOURCE or SND_AVIRT_ROUTE_SINK).
 * @return: A snd_pcm_copy_kernel function pointer that can be used to either:
 *          1. Source PCM data into the Audio Path, or,
 *          2. Sink PCM data out of the Audio Path.
 *          If no Audio Path endpoint is routed for 'endpoint', NULL is returned.
 */
snd_pcm_copy_kernel
snd_avirt_route_endpoint_copy(struct snd_pcm_substream *substream,
			      snd_avirt_route_endpoint endpoint)
{
	struct snd_avirt_audiopath *endpoint_ap;
	struct snd_avirt_stream *stream;

	if (endpoint < 0 || endpoint > 1) {
		D_ERRORK("Route endpoint must be 0 or 1");
		return NULL;
	}

	stream = snd_avirt_stream_find_by_device(substream->pcm->device);
	if (IS_ERR_VALUE(stream) || !stream)
		return NULL;

	if (!stream->route)
		return NULL;
	endpoint_ap = stream->route->endpoint_ap[endpoint];
	if (!endpoint_ap)
		return NULL;

	switch (endpoint) {
	case SND_AVIRT_ROUTE_SOURCE:
		return endpoint_ap->pcm_capture_ops->copy_kernel;
	case SND_AVIRT_ROUTE_SINK:
		return endpoint_ap->pcm_playback_ops->copy_kernel;
	}

	return NULL;
}
EXPORT_SYMBOL_GPL(snd_avirt_route_endpoint_copy);

/**
 * snd_avirt_route_endpoint_trigger - Trigger an Audio Path's endpoint
 *                                    (sink/source).
 * @uid: The Audio Path whose endpoint trigger function to call.
 * @endpoint: The endpoint (SND_AVIRT_ROUTE_SOURCE or SND_AVIRT_ROUTE_SINK).
 * @return: 0 on success or -1 on failure.
 */
int snd_avirt_route_endpoint_trigger(struct snd_pcm_substream *substream,
				     snd_avirt_route_endpoint endpoint)
{
	struct snd_avirt_audiopath *endpoint_ap;
	struct snd_avirt_stream *stream;

	if (endpoint < 0 || endpoint > 1) {
		D_ERRORK("Route endpoint must be 0 or 1");
		return -1;
	}

	stream = snd_avirt_stream_find_by_device(substream->pcm->device);
	if (IS_ERR_VALUE(stream) || !stream)
		return -1;

	if (!stream->route)
		return -1;
	endpoint_ap = stream->route->endpoint_ap[endpoint];
	if (!endpoint_ap)
		return -1;

	endpoint_ap->pcm_exttrigger();

	return 0;
}
EXPORT_SYMBOL_GPL(snd_avirt_route_endpoint_trigger);

/**
 * snd_avirt_audiopath_get - get Audio Path by it's UID
 * @uid: The Audio Path UID to get
 * @return: The Audio Path if it exists, NULL otherwise.
 */
struct snd_avirt_audiopath *snd_avirt_audiopath_get(const char *uid)
{
	struct snd_avirt_audiopath_obj *ap_obj;

	list_for_each_entry (ap_obj, &audiopath_list, list) {
		if (!strcmp(ap_obj->path->uid, uid))
			return ap_obj->path;
	}

	return NULL;
}
EXPORT_SYMBOL_GPL(snd_avirt_audiopath_get);

/*
 * snd_avirt_audiopath_set_private_data - set PCM private data for an Audio Path
 * @ap: The Audio Path whose private data to set.
 * @pcm: The PCM where the private data is stored.
 * @ap_private_data: The value to set to the private data.
 * @return: 0 on success, -1 on failure.
 */
int snd_avirt_audiopath_set_private_data(struct snd_avirt_audiopath *ap,
					 struct snd_pcm *pcm,
					 void *ap_private_data)
{
	int err = 0;
	snd_avirt_route_endpoint endpoint = SND_AVIRT_ROUTE_SOURCE;
	struct snd_avirt_stream *stream;
	struct snd_avirt_private_data *avirt_private_data;

	stream = snd_avirt_stream_find_by_device(pcm->device);
	if (IS_ERR_VALUE(stream) || !stream)
		goto exit_err;

	err = snd_avirt_route_endpoint_pos(pcm, ap->uid, &endpoint);
	if (err == -2)
		goto exit_err;

	if (stream->internal && stream->route)
		pcm = stream->route->endpoint_stream[!endpoint]->pcm;

	avirt_private_data = pcm->private_data;
	if (!avirt_private_data)
		goto exit_err;

	avirt_private_data->ap_private_data[endpoint] = ap_private_data;

	return 0;

exit_err:
	D_ERRORK("Error setting private data for ap:%s, stream:%s, endpoint:%d",
		 ap->uid, pcm->name, endpoint);
	return -1;
}
EXPORT_SYMBOL_GPL(snd_avirt_audiopath_set_private_data);

/*
 * snd_avirt_audiopath_get_private_data - get PCM private data for an Audio Path
 * @ap: The Audio Path whose private data to get.
 * @pcm: The PCM where the private data is stored.
 * @return: The value assigned to the private data.
 */
void *snd_avirt_audiopath_get_private_data(struct snd_avirt_audiopath *ap,
					   struct snd_pcm *pcm)
{
	int err = 0;
	snd_avirt_route_endpoint endpoint = SND_AVIRT_ROUTE_SOURCE;
	struct snd_avirt_stream *stream;
	struct snd_avirt_private_data *avirt_private_data;

	stream = snd_avirt_stream_find_by_device(pcm->device);
	if (IS_ERR_VALUE(stream) || !stream)
		return NULL;

	err = snd_avirt_route_endpoint_pos(pcm, ap->uid, &endpoint);
	if (err == -2)
		goto exit_err;

	if (stream->internal && stream->route)
		pcm = stream->route->endpoint_stream[!endpoint]->pcm;

	avirt_private_data = pcm->private_data;
	if (!avirt_private_data)
		goto exit_err;

	return avirt_private_data->ap_private_data[endpoint];

exit_err:
	D_ERRORK("Error getting private data for ap:%s, stream:%s, endpoint:%d",
		 ap->uid, pcm->name, endpoint);
	return NULL;
}
EXPORT_SYMBOL_GPL(snd_avirt_audiopath_get_private_data);

/**
 * snd_avirt_audiopath_register - register Audio Path with AVIRT
 * @audiopath: Audio Path to be registered
 * @return: 0 on success or error code otherwise
 */
int snd_avirt_audiopath_register(struct snd_avirt_audiopath *audiopath)
{
	struct snd_avirt_audiopath_obj *audiopath_obj;
	struct snd_avirt_stream_array stream_array;

	if (!audiopath) {
		D_ERRORK("Audio Path is NULL!");
		return -EINVAL;
	}

	audiopath_obj = snd_avirt_audiopath_create_obj(audiopath->uid);
	if (!audiopath_obj) {
		D_INFOK("Failed to alloc driver object");
		return -ENOMEM;
	}
	audiopath_obj->path = audiopath;

	audiopath->context = audiopath_obj;
	D_INFOK("Registered new Audio Path: %s", audiopath->name);

	list_add_tail(&audiopath_obj->list, &audiopath_list);

	// If we have already configured the streams, configure this AP
	if (core.streams_configured) {
		stream_array.count = 0;
		if (snd_avirt_streams_get(audiopath->uid, &stream_array) > 0)
			audiopath->configure(core.card, &stream_array);
	}

	return 0;
}
EXPORT_SYMBOL_GPL(snd_avirt_audiopath_register);

/**
 * snd_avirt_audiopath_deregister - deregister Audio Path with AVIRT
 * @audiopath: Audio Path to be deregistered
 * @return: 0 on success or error code otherwise
 */
int snd_avirt_audiopath_deregister(struct snd_avirt_audiopath *audiopath)
{
	struct snd_avirt_audiopath_obj *audiopath_obj;

	// Check if audio path is registered
	if (!audiopath) {
		D_ERRORK("Bad Audio Path Driver");
		return -EINVAL;
	}

	audiopath_obj = audiopath->context;
	if (!audiopath_obj) {
		D_INFOK("driver not registered");
		return -EINVAL;
	}

	list_del(&audiopath_obj->list);
	snd_avirt_audiopath_destroy_obj(audiopath_obj);
	D_INFOK("Deregistered Audio Path %s", audiopath->uid);

	return 0;
}
EXPORT_SYMBOL_GPL(snd_avirt_audiopath_deregister);

/**
 * snd_avirt_route_create - Create audio route
 * @uid: The unique ID designated to the audio route.
 * @direction: The PCM direction (SNDRV_PCM_STREAM_PLAYBACK or
 *             SNDRV_PCM_STREAM_CAPTURE)
 * @return: The newly created audio route if successful, or an error pointer
 */
struct snd_avirt_route *snd_avirt_route_create(const char *uid, int direction)
{
	struct snd_avirt_route *route;

	route = kzalloc(sizeof(*route), GFP_KERNEL);
	if (!route)
		return ERR_PTR(-ENOMEM);

	strncpy(route->uid, uid, MAX_NAME_LEN);
	route->channels = 0;
	route->direction = direction;

	return route;
}

/**
 * snd_avirt_stream_create - Create audio stream, including it's ALSA PCM device
 * @name: The name designated to the audio stream
 * @direction: The PCM direction (SNDRV_PCM_STREAM_PLAYBACK or
 *             SNDRV_PCM_STREAM_CAPTURE)
 * @internal: Whether the PCM should be internal or not
 * @return: The newly created audio stream if successful, or an error pointer
 */
struct snd_avirt_stream *snd_avirt_stream_create(const char *name,
						 int direction, bool internal)
{
	struct snd_avirt_stream *stream;

	if ((core.stream_count + 1) > MAX_STREAMS) {
		D_ERRORK("Cannot add stream %s, PCMs are maxxed out!", name);
		return ERR_PTR(-EPERM);
	}

	stream = kzalloc(sizeof(*stream), GFP_KERNEL);
	if (!stream)
		return ERR_PTR(-ENOMEM);

	strncpy(stream->name, name, MAX_NAME_LEN);
	strncpy(stream->map, "none", MAX_NAME_LEN);
	stream->channels = 0;
	stream->direction = direction;
	stream->internal = internal;
	stream->device = core.stream_count++;

	/* Initialize PCM ops table for this stream.
	 * Will be populated once map is known */
	stream->pcm_ops = kzalloc(sizeof(struct snd_pcm_ops), GFP_KERNEL);
	if (!stream->pcm_ops) {
		D_ERRORK("Failed to allocate PCM ops table");
		return ERR_PTR(-EFAULT);
	}
	memcpy(stream->pcm_ops, &pcm_ops_avirt, sizeof(struct snd_pcm_ops));

	if (stream->internal) {
		D_INFOK("name:%s device:%d internal:%d", name, stream->device,
			stream->internal);
	} else {
		D_INFOK("name:%s device:%d", name, stream->device);
	}

	return stream;
}

int snd_avirt_streams_configure(void)
{
	int err = 0, i = 0;
	struct snd_avirt_audiopath_obj *ap_obj;
	struct snd_avirt_stream_array stream_array;

	if (core.streams_configured) {
		D_ERRORK("streams are already configured!");
		return -1;
	}

	list_for_each_entry (ap_obj, &audiopath_list, list) {
		for (i = 0; i < MAX_STREAMS; i++)
			stream_array.streams[i] = NULL;
		stream_array.count = 0;
		if (snd_avirt_streams_get(ap_obj->path->uid, &stream_array) <=
		    0)
			continue;

		if (!ap_obj->path->configure) {
			D_ERRORK("Cannot do 'configure' for AP: %s",
				 ap_obj->path->uid);
			return -EFAULT;
		}

		D_INFOK("Do 'configure' for AP: %s streams:%d",
			ap_obj->path->uid, stream_array.count);
		ap_obj->path->configure(core.card, &stream_array);
	}

	core.streams_configured = true;

	return err;
}

int snd_avirt_streams_unconfigure(void)
{
	int i = 0, err = 0;
	struct snd_avirt_audiopath_obj *ap_obj;
	struct snd_avirt_stream_array stream_array;

	if (!core.streams_configured) {
		D_ERRORK("streams are already unconfigured!");
		return -1;
	}

	list_for_each_entry (ap_obj, &audiopath_list, list) {
		if (!ap_obj->path->unconfigure) {
			D_ERRORK("Cannot do 'unconfigure' for AP: %s",
				 ap_obj->path->uid);
			return -EFAULT;
		}

		D_INFOK("Do 'unconfigure' for AP: %s", ap_obj->path->uid);
		ap_obj->path->unconfigure();
	}

	core.streams_configured = false;

	return 0;
}

bool snd_avirt_streams_configured(void)
{
	return core.streams_configured;
}

struct snd_avirt_stream *snd_avirt_stream_find_by_device(unsigned int device)
{
	struct snd_avirt_stream *stream;
	struct config_item *item;
	struct list_head *entry;

	if (device >= core.stream_count) {
		D_ERRORK("Stream device number is larger than stream count");
		return ERR_PTR(-EINVAL);
	}

	list_for_each (entry, &core.stream_group->cg_children) {
		item = container_of(entry, struct config_item, ci_entry);
		stream = snd_avirt_stream_from_config_item(item);
		if (!stream)
			return ERR_PTR(-EFAULT);
		if (stream->device == device)
			return stream;
	}

	return NULL;
}

static int snd_avirt_core_probe(struct platform_device *devptr)
{
	int err;

	// Create the card instance
	err = snd_card_new(&devptr->dev, SNDRV_DEFAULT_IDX1, "avirt",
			   THIS_MODULE, 0, &core.card);
	if (err < 0) {
		D_ERRORK("Failed to create sound card");
		return err;
	}

	strncpy(core.card->driver, "avirt-alsa-dev", 16);
	strncpy(core.card->shortname, "avirt", 32);
	strncpy(core.card->longname, "A virtual sound card driver for ALSA",
		80);

	return 0;
}

static int snd_avirt_core_remove(struct platform_device *devptr)
{
	snd_card_free(core.card);

	return 0;
}

static struct platform_driver snd_avirt_driver = {
	.probe = snd_avirt_core_probe,
	.remove = snd_avirt_core_remove,
	.driver =
		{
			.name = SND_AVIRT_DRIVER,
		},
};

/**
 * snd_avirt_core_init - Initialize the kernel module
 */
static int __init snd_avirt_core_init(void)
{
	int err;

	D_INFOK("Alsa Virtual Sound Driver avirt-%d.%d.%d", core.version[0],
		core.version[1], core.version[2]);

	err = platform_driver_register(&snd_avirt_driver);
	if (err < 0)
		return err;

	core.plat_dev =
		platform_device_register_simple(SND_AVIRT_DRIVER, 0, NULL, 0);
	if (IS_ERR(core.plat_dev)) {
		err = PTR_ERR(core.plat_dev);
		goto exit_platform_device;
	}

	core.class = class_create(THIS_MODULE, SND_AVIRT_DRIVER);
	if (IS_ERR(core.class)) {
		D_ERRORK("No udev support");
		return PTR_ERR(core.class);
	}

	core.dev = device_create(core.class, NULL, 0, NULL, "core");
	if (IS_ERR(core.dev)) {
		err = PTR_ERR(core.dev);
		goto exit_class;
	}

	err = snd_avirt_sysfs_init(&core);
	if (err < 0)
		goto exit_class_container;

	err = snd_card_register(core.card);
	if (err < 0) {
		D_ERRORK("Sound card registration failed!");
		snd_card_free(core.card);
	}

	err = snd_avirt_configfs_init(&core);
	if (err < 0)
		goto exit_sysfs;

	return 0;

exit_sysfs:
	snd_avirt_sysfs_exit(&core);
exit_class_container:
	device_destroy(core.class, 0);
exit_class:
	class_destroy(core.class);
exit_platform_device:
	platform_device_unregister(core.plat_dev);
	platform_driver_unregister(&snd_avirt_driver);

	return err;
}

/**
 * snd_avirt_core_exit - Destroy the kernel module
 */
static void __exit snd_avirt_core_exit(void)
{
	snd_avirt_configfs_exit(&core);
	snd_avirt_sysfs_exit(&core);
	device_destroy(core.class, 0);
	class_destroy(core.class);
	platform_device_unregister(core.plat_dev);
	platform_driver_unregister(&snd_avirt_driver);
}

module_init(snd_avirt_core_init);
module_exit(snd_avirt_core_exit);