/*
 * Apple Onboard Audio driver core
 *
 * Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
 *
 * GPL v2, can be found in COPYING.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
#include "../aoa.h"
#include "snd-aoa-alsa.h"

MODULE_DESCRIPTION("Apple Onboard Audio Sound Driver");
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
MODULE_LICENSE("GPL");

/* We allow only one fabric. This simplifies things,
 * and more don't really make that much sense */
static struct aoa_fabric *fabric;
static LIST_HEAD(codec_list);

static int attach_codec_to_fabric(struct aoa_codec *c)
{
	int err;

	if (!try_module_get(c->owner))
		return -EBUSY;
	/* found_codec has to be assigned */
	err = -ENOENT;
	if (fabric->found_codec)
		err = fabric->found_codec(c);
	if (err) {
		module_put(c->owner);
		printk(KERN_ERR "snd-aoa: fabric didn't like codec %s\n",
				c->name);
		return err;
	}
	c->fabric = fabric;

	err = 0;
	if (c->init)
		err = c->init(c);
	if (err) {
		printk(KERN_ERR "snd-aoa: codec %s didn't init\n", c->name);
		c->fabric = NULL;
		if (fabric->remove_codec)
			fabric->remove_codec(c);
		module_put(c->owner);
		return err;
	}
	if (fabric->attached_codec)
		fabric->attached_codec(c);
	return 0;
}

int aoa_codec_register(struct aoa_codec *codec)
{
	int err = 0;

	/* if there's a fabric already, we can tell if we
	 * will want to have this codec, so propagate error
	 * through. Otherwise, this will happen later... */
	if (fabric)
		err = attach_codec_to_fabric(codec);
	if (!err)
		list_add(&codec->list, &codec_list);
	return err;
}
EXPORT_SYMBOL_GPL(aoa_codec_register);

void aoa_codec_unregister(struct aoa_codec *codec)
{
	list_del(&codec->list);
	if (codec->fabric && codec->exit)
		codec->exit(codec);
	if (fabric && fabric->remove_codec)
		fabric->remove_codec(codec);
	codec->fabric = NULL;
	module_put(codec->owner);
}
EXPORT_SYMBOL_GPL(aoa_codec_unregister);

int aoa_fabric_register(struct aoa_fabric *new_fabric, struct device *dev)
{
	struct aoa_codec *c;
	int err;

	/* allow querying for presence of fabric
	 * (i.e. do this test first!) */
	if (new_fabric == fabric) {
		err = -EALREADY;
		goto attach;
	}
	if (fabric)
		return -EEXIST;
	if (!new_fabric)
		return -EINVAL;

	err = aoa_alsa_init(new_fabric->name, new_fabric->owner, dev);
	if (err)
		return err;

	fabric = new_fabric;

 attach:
	list_for_each_entry(c, &codec_list, list) {
		if (c->fabric != fabric)
			attach_codec_to_fabric(c);
	}
	return err;
}
EXPORT_SYMBOL_GPL(aoa_fabric_register);

void aoa_fabric_unregister(struct aoa_fabric *old_fabric)
{
	struct aoa_codec *c;

	if (fabric != old_fabric)
		return;

	list_for_each_entry(c, &codec_list, list) {
		if (c->fabric)
			aoa_fabric_unlink_codec(c);
	}

	aoa_alsa_cleanup();

	fabric = NULL;
}
EXPORT_SYMBOL_GPL(aoa_fabric_unregister);

void aoa_fabric_unlink_codec(struct aoa_codec *codec)
{
	if (!codec->fabric) {
		printk(KERN_ERR "snd-aoa: fabric unassigned "
				"in aoa_fabric_unlink_codec\n");
		dump_stack();
		return;
	}
	if (codec->exit)
		codec->exit(codec);
	if (codec->fabric->remove_codec)
		codec->fabric->remove_codec(codec);
	codec->fabric = NULL;
	module_put(codec->owner);
}
EXPORT_SYMBOL_GPL(aoa_fabric_unlink_codec);

static int __init aoa_init(void)
{
	return 0;
}

static void __exit aoa_exit(void)
{
	aoa_alsa_cleanup();
}

module_init(aoa_init);
module_exit(aoa_exit);
