/*
 *  Routines for driver control
 *  Copyright (c) by Jaroslav Kysela <perex@suse.cz>
 *
 *
 *   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; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "../include/driver.h"
#include "../include/minors.h"
#include "../include/info.h"
#include "../include/control.h"

struct snd_stru_control_ioctl {
	snd_control_ioctl_t fioctl;
	struct snd_stru_control_ioctl *next;
};

static struct snd_stru_control_ioctl *snd_first_ioctl = NULL;
static int snd_first_ioctl_use = 0;

static DECLARE_MUTEX(register_mutex);

static int snd_control_open(unsigned short minor, int cardnum, int device,
			    struct file *file)
{
	snd_card_t *card;
	snd_control_t *control, *control1;

	if (!(card = snd_cards[cardnum]))
		return -ENODEV;
	control = snd_magic_kcalloc(snd_control_t, 0, GFP_KERNEL);
	if (control == NULL)
		return -ENOMEM;
	MOD_INC_USE_COUNT;
	card->use_inc(card);
	init_waitqueue_head(&control->change_sleep);
	spin_lock_init(&control->read_lock);
	control->card = card;
	file->private_data = control;
	down(&card->control);
	if (card->fcontrol) {
		for (control1 = card->fcontrol;
		     control1->next;
		     control1 = control1->next);
		control1->next = control;
	} else {
		card->fcontrol = control;
	}
	up(&card->control);
	return 0;
}

static int snd_control_release(unsigned short minor, int cardnum, int device,
			       struct file *file)
{
	snd_card_t *card;
	snd_control_t *control, *control1;
	snd_kctl_read_t *cread;

	if (file->private_data) {
		control = snd_magic_cast(snd_control_t, file->private_data, -ENXIO);
		file->private_data = NULL;
		card = control->card;
		down(&card->control);
		if (card->fcontrol == control) {
			card->fcontrol = control->next;
		} else {
			for (control1 = card->fcontrol;
			     control1->next != control;
			     control1 = control1->next);
			control1->next = control->next;
		}
		up(&card->control);
		while (control->first_item) {
			cread = control->first_item;
			control->first_item = cread->next;
			snd_kfree(cread);
		}
		snd_magic_kfree(control);
		MOD_DEC_USE_COUNT;
		card->use_dec(card);
	}
	return 0;
}

int snd_control_busy(snd_control_t * control)
{
	if (control->rebuild || control->first_item)
		return 1;
	return 0;
}

static snd_kctl_read_t *snd_control_alloc_read(snd_control_t * control, int atomic)
{
	snd_kctl_read_t *item;
	
	item = (snd_kctl_read_t *)snd_kcalloc(sizeof(*item), GFP_KERNEL | (atomic ? GFP_ATOMIC : 0));
	return item;
}

static void snd_control_empty_read_queue(snd_control_t * control, int rebuild, int atomic)
{
	unsigned long flags;
	snd_kctl_read_t *read1, *read2;
	
	spin_lock_irqsave(&control->read_lock, flags);
	read1 = control->first_item;
	control->first_item = control->last_item = NULL;
	control->rebuild = rebuild ? 1 : 0;
	spin_unlock_irqrestore(&control->read_lock, flags);
	while (read1) {
		read2 = read1->next;
		snd_kfree(read1);
		read1 = read2;
	}
}

static void snd_control_notify_change(snd_control_t * control, snd_ctl_read_t * cread, int all, int atomic)
{
	unsigned long flags;
	snd_control_t *control1;
	snd_card_t *card;
	snd_kctl_read_t *nread;
	
	if (control == NULL || cread == NULL)
		return;
	card = control->card;
	/* TODO: lock card->fcontrol list */
	for (control1 = card->fcontrol; control1; control1 = control1->next) {
		if (all || control1 != control) {
			if (!control1->read_active) {
				control1->rebuild = 1;
				continue;
			}
			nread = snd_control_alloc_read(control1, atomic);
			if (nread == NULL) {
				snd_control_empty_read_queue(control1, 1, atomic);
			} else {
				memcpy(&nread->data, cread, sizeof(nread->data));
				spin_lock_irqsave(&control1->read_lock, flags);
				if (control1->first_item == NULL) {
					control1->first_item = control1->last_item = nread;
				} else {
					control1->last_item->next = nread;
				}
				spin_unlock_irqrestore(&control1->read_lock, flags);
				wake_up(&control1->change_sleep);
			}
		}
	}
}

void snd_control_notify_structure_change(snd_control_t * control, snd_ctl_read_t * read)
{
	snd_control_notify_change(control, read, 1, 0);
}

void snd_control_notify_value_change(snd_control_t * control, snd_ctl_read_t * read, int atomic)
{
	snd_control_notify_change(control, read, 0, atomic);
}

void snd_control_notify_switch_change(snd_card_t * card, int cmd,
				      int iface, int device, int channel,
				      char *name)
{
	snd_ctl_read_t r;
	
	if (card == NULL || name == NULL)
		return;
	memset(&r, 0, sizeof(r));
	r.cmd = cmd;
	r.data.sw.iface = iface;
	r.data.sw.device = device;
	r.data.sw.channel = channel;
	strncpy(r.data.sw.switem.name, name, sizeof(r.data.sw.switem.name));
	snd_control_notify_structure_change(card->fcontrol, &r);
}

void snd_control_notify_switch_value_change(snd_control_t * control,
					    int iface, int device,
					    int channel, char *name,
					    int atomic)
{
	snd_ctl_read_t r;
	
	memset(&r, 0, sizeof(r));
	r.cmd = SND_CTL_READ_SWITCH_VALUE;
	r.data.sw.iface = iface;
	r.data.sw.device = device;
	r.data.sw.channel = channel;
	strncpy(r.data.sw.switem.name, name, sizeof(r.data.sw.switem.name));
	snd_control_notify_value_change(control, &r, atomic);
}

static int snd_control_hw_info(snd_card_t * card, snd_control_t * control,
			       unsigned int cmd, unsigned long arg)
{
	struct snd_stru_control_ioctl *p;
	struct snd_ctl_hw_info info;

	memset(&info, 0, sizeof(info));
	down(&card->control);
	info.type = card->type;
	strncpy(info.id, card->id, sizeof(info.id) - 1);
	strncpy(info.abbreviation, card->abbreviation, sizeof(info.abbreviation) - 1);
	strncpy(info.name, card->shortname, sizeof(info.name) - 1);
	strncpy(info.longname, card->longname, sizeof(info.longname) - 1);
	for (p = snd_first_ioctl; p; p = p->next)
		p->fioctl(card, control, cmd, (unsigned long) &info);
	up(&card->control);
	if (copy_to_user((void *) arg, &info, sizeof(struct snd_ctl_hw_info)))
		return -EFAULT;
	return 0;
}

static int snd_control_ioctl(struct file *file,
			     unsigned int cmd,
			     unsigned long arg)
{
	snd_control_t *control;
	snd_card_t *card;
	struct snd_stru_control_ioctl *p;
	int err;

	control = snd_magic_cast(snd_control_t, file->private_data, -ENXIO);
	card = control->card;
	snd_debug_check(card == NULL, -ENXIO);
	switch (cmd) {
	case SND_CTL_IOCTL_PVERSION:
		return put_user(SND_CTL_VERSION, (int *)arg) ? -EFAULT : 0;
	case SND_CTL_IOCTL_HW_INFO:
		return snd_control_hw_info(card, control, cmd, arg);
	case SND_CTL_IOCTL_SWITCH_LIST:
		return snd_switch_list(control->card, (snd_switch_list_t *) arg);
	case SND_CTL_IOCTL_SWITCH_READ:
		return snd_switch_read(control->card, (snd_switch_t *) arg);
	case SND_CTL_IOCTL_SWITCH_WRITE:
		return snd_switch_write(control, (snd_switch_t *) arg);
	}
	down(&card->control);
	for (p = snd_first_ioctl; p; p = p->next) {
		err = p->fioctl(card, control, cmd, arg);
		if (err != -ENOIOCTLCMD) {
			up(&card->control);
			return err;
		}
	}
	up(&card->control);
	return -EINVAL;
}

static long snd_control_read(struct file *file, char *buffer, long count)
{
	unsigned long flags;
	snd_control_t *control;
	snd_kctl_read_t *cread;
	snd_ctl_read_t rebuild;
	long result;

	control = snd_magic_cast(snd_control_t, file->private_data, -ENXIO);
	snd_debug_check(control == NULL || control->card == NULL, -ENXIO);
	spin_lock_irqsave(&control->read_lock, flags);
	control->read_active = 1;
	spin_unlock_irqrestore(&control->read_lock, flags);
	result = 0;
	while (count >= sizeof(snd_ctl_read_t)) {
		spin_lock_irqsave(&control->read_lock, flags);
		if (control->rebuild) {
			control->rebuild = 0;
			spin_unlock_irqrestore(&control->read_lock, flags);
			memset(&rebuild, 0, sizeof(rebuild));
			rebuild.cmd = SND_CTL_READ_REBUILD;
			if (copy_to_user(buffer, &rebuild, sizeof(rebuild)))
				return -EFAULT;
			goto __move;
		} else {
			cread = control->first_item;
			if (cread) {
				control->first_item = cread->next;
				if (control->first_item == NULL)
					control->last_item = NULL;
			}
		}
		spin_unlock_irqrestore(&control->read_lock, flags);
		if (cread) {
			if (copy_to_user(buffer, &cread->data, sizeof(snd_ctl_read_t))) {
				snd_kfree(cread);
				return -EFAULT;
			}
			snd_kfree(cread);
		      __move:
			buffer += sizeof(snd_ctl_read_t);
			count -= sizeof(snd_ctl_read_t);
			result += sizeof(snd_ctl_read_t);
		} else {
			break;
		}
	}
	return result;
}

static unsigned int snd_control_poll(struct file *file, poll_table * wait)
{
	unsigned long flags;
	unsigned int mask;
	snd_control_t *control;

	control = snd_magic_cast(snd_control_t, file->private_data, 0);

	spin_lock_irqsave(&control->read_lock, flags);
	control->read_active = 1;
	spin_unlock_irqrestore(&control->read_lock, flags);

	poll_wait(file, &control->change_sleep, wait);

	mask = 0;
	if (control->first_item)
		mask |= POLLIN | POLLRDNORM;

	return mask;
}

int snd_control_register_ioctl(snd_control_ioctl_t fcn)
{
	struct snd_stru_control_ioctl *p, *pn;

	pn = (struct snd_stru_control_ioctl *)
			snd_kcalloc(sizeof(struct snd_stru_control_ioctl), GFP_KERNEL);
	if (pn == NULL)
		return -ENOMEM;
	pn->fioctl = fcn;
	down(&register_mutex);
	if (snd_first_ioctl == NULL) {
		snd_first_ioctl = pn;
	} else {
		for (p = snd_first_ioctl; p->next; p = p->next);
		p->next = pn;
	}
	up(&register_mutex);
	return 0;
}

int snd_control_unregister_ioctl(snd_control_ioctl_t fcn)
{
	struct snd_stru_control_ioctl *p, *pn, *po;

	down(&register_mutex);
	if (snd_first_ioctl == NULL) {
		up(&register_mutex);
		return -EINVAL;
	}
	p = snd_first_ioctl;
	if (p->fioctl == fcn) {
		snd_first_ioctl = p->next;
		up(&register_mutex);
		snd_kfree(p);
		return 0;
	}
	for (; p; p = pn) {
		pn = p->next;
		if (pn && pn->fioctl == fcn) {
			po = pn;
			p->next = pn = pn->next;
			up(&register_mutex);
			snd_kfree(po);
			return 0;
		}
	}
	up(&register_mutex);
	return -EINVAL;
}

int snd_control_switch_add(snd_card_t * card, snd_kswitch_t * ksw)
{
	int err = snd_switch_add(&card->switches, ksw);
	if (err >= 0)
		snd_control_notify_switch_change(card,
						 SND_CTL_READ_SWITCH_ADD,
						 SND_CTL_IFACE_CONTROL,
						 0, 0, ksw->name);
	return err;
}

int snd_control_switch_remove(snd_card_t * card, snd_kswitch_t * ksw)
{
	int err = snd_switch_remove(&card->switches, ksw);
	if (err >= 0)
		snd_control_notify_switch_change(card,
						 SND_CTL_READ_SWITCH_REMOVE,
						 SND_CTL_IFACE_CONTROL,
						 0, 0, ksw->name);
	return err;
}

snd_kswitch_t *snd_control_switch_new(snd_card_t * card, snd_kswitch_t * ksw, void *private_data)
{
	snd_kswitch_t *sw;
	
	sw = snd_switch_new(ksw);
	if (sw == NULL)
		return NULL;
	if (snd_control_switch_add(card, sw) < 0) {
		snd_switch_free_one(sw);
		return NULL;
	}
	sw->private_data = private_data;
	return sw;
}

int snd_control_switch_change(snd_card_t * card, snd_kswitch_t * ksw)
{
	snd_debug_check(card == NULL || ksw == NULL, -ENXIO);
	snd_control_notify_switch_change(card,
					 SND_CTL_READ_SWITCH_CHANGE,
					 SND_CTL_IFACE_CONTROL,
					 0, 0, ksw->name);
	return 0;
}

/*
 *  INIT PART
 */

static snd_minor_t snd_control_reg =
{
	comment:	"control",
	read:		snd_control_read,
	open:		snd_control_open,
	release:	snd_control_release,
	poll:		snd_control_poll,
	ioctl:		snd_control_ioctl,
};

int snd_control_register(snd_card_t *card)
{
	int err, cardnum;
#ifndef CONFIG_DEVFS_FS
	char name[16];
#endif

	snd_debug_check(card == NULL, -ENXIO);
	cardnum = card->number;
	snd_debug_check(cardnum < 0 || cardnum >= SND_CARDS, -ENXIO);
#ifndef CONFIG_DEVFS_FS
	sprintf(name, "controlC%i", cardnum);
	if ((err = snd_register_device(SND_DEVICE_TYPE_CONTROL,
					card, 0, &snd_control_reg, name)) < 0)
		return err;
#else
	/* register only operations, don't create a device */
	/* the device is made by sound.c */
	if ((err = snd_register_device(SND_DEVICE_TYPE_CONTROL,
					card, 0, &snd_control_reg, NULL)) < 0)
		return err;
#endif
	snd_first_ioctl_use++;
	return 0;
}

int snd_control_unregister(snd_card_t *card)
{
	int err, cardnum;

	snd_debug_check(card == NULL, -ENXIO);
	cardnum = card->number;
	snd_debug_check(cardnum < 0 || cardnum >= SND_CARDS, -ENXIO);
	if ((err = snd_unregister_device(SND_DEVICE_TYPE_CONTROL, card, 0)) < 0)
		return err;
	snd_first_ioctl_use--;
	return 0;
}
