/*
 *  Abstract layer for MIDI v1.0 stream
 *  Copyright (c) by Jaroslav Kysela <perex@jcu.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 "driver.h"
#include "info.h"
#include "minors.h"
#include "midi.h"
#include "control.h"
#include "sndpersist.h"
#include <fcntl.h>

snd_rawmidi_t *snd_rawmidi_devices[SND_CARDS * SND_RAWMIDI_DEVICES];

int snd_rawmidi_receive(snd_rawmidi_t * rmidi, char *buffer, int count);
int snd_rawmidi_transmit(snd_rawmidi_t * rmidi, char *buffer, int count);

static inline unsigned short snd_rawmidi_file_flags(snd_ocb_t *ocb)
{
	switch (ocb->iofunc_ocb.ioflag & _IO_FLAG_MASK) {
		case _IO_FLAG_WR:
			return SND_RAWMIDI_LFLG_OUTPUT;
		case _IO_FLAG_RD:
			return SND_RAWMIDI_LFLG_INPUT;
		case _IO_FLAG_RD|_IO_FLAG_WR:
			return SND_RAWMIDI_LFLG_OPEN;
		default:
			return 0;
	}
}

static inline int snd_rawmidi_input_ok(snd_rawmidi_t * rmidi)
{
	int result;

	snd_mutex_lock(rmidi, input);
	result = rmidi->input.u.s.used >= rmidi->input.u.s.used_min;
	snd_mutex_unlock(rmidi, input);
	return result;
}

static inline int snd_rawmidi_output_ok(snd_rawmidi_t * rmidi)
{
	int result, free;

	snd_mutex_lock(rmidi, output);
	free = rmidi->output.u.s.size - rmidi->output.u.s.used;
	result = rmidi->output.u.s.used <= rmidi->output.u.s.used_max &&
	         free >= rmidi->output.u.s.used_room;
	snd_mutex_unlock(rmidi, output);
	return result;
}

int snd_rawmidi_init_buffer(snd_rawmidi_t * rmidi, int direction)
{
	struct snd_stru_rawmidi_direction *pdir;

	pdir = direction == SND_RAWMIDI_LFLG_INPUT ?
					&rmidi->input : &rmidi->output;
	pdir->mode = SND_RAWMIDI_MODE_STREAM;
	pdir->reset = NULL;
	pdir->u.s.size = 4096;
	pdir->u.s.used_max = pdir->u.s.size - 1;
	pdir->u.s.used_room = 1;
	pdir->u.s.used_min = 1;
	pdir->u.s.buffer = NULL;
	if ((pdir->u.s.buffer = snd_malloc(pdir->u.s.size)) == NULL) {
		return -ENOMEM;
	}
	pdir->u.s.head = pdir->u.s.tail = pdir->u.s.used = 0;
	return 0;
}

int snd_rawmidi_done_buffer(snd_rawmidi_t * rmidi, int direction)
{
	struct snd_stru_rawmidi_direction *pdir;

	pdir = direction == SND_RAWMIDI_LFLG_INPUT ?
					&rmidi->input : &rmidi->output;
	if (pdir->u.s.buffer) {
		snd_free(pdir->u.s.buffer, pdir->u.s.size);
		pdir->u.s.buffer = NULL;
	}
	return 0;
}

static void snd_rawmidi_poll_output(unsigned long data)
{
	snd_rawmidi_t *rmidi;

	rmidi = (snd_rawmidi_t *) data;
	rmidi->output.hw.io.write(rmidi);
	snd_mutex_lock(rmidi, output);
	if (rmidi->output.u.s.used > 0) {
		rmidi->output.flags |= SND_RAWMIDI_FLG_TIMER;
		rmidi->output.timer.expires = 1;
		snd_add_timer(&rmidi->output.timer);
	} else {
		snd_del_timer(&rmidi->output.timer);
		rmidi->output.flags &= ~SND_RAWMIDI_FLG_TIMER;
	}
	snd_mutex_unlock(rmidi, output);
}

void snd_rawmidi_trigger_output(snd_rawmidi_t * rmidi, int up)
{
	int flg;

#if 0
	snd_printk("trigger_output - %i\n", up);
#endif
	if (!(rmidi->output.hw.flags & SND_RAWMIDI_HW_POLL)) {
		flg = up ? SND_RAWMIDI_FLG_TRIGGER : 0;
		snd_mutex_lock(rmidi, output);
		if ((rmidi->output.flags & SND_RAWMIDI_FLG_TRIGGER) != flg) {
			if (up) {
				rmidi->output.flags |= SND_RAWMIDI_FLG_TRIGGER;
			} else {
				rmidi->output.flags &= ~SND_RAWMIDI_FLG_TRIGGER;
			}
			snd_mutex_unlock(rmidi, output);
			rmidi->output.hw.trigger(rmidi, up);
			return;
		}
		snd_mutex_unlock(rmidi, output);
	} else {
		snd_mutex_lock(rmidi, output);
		if (up) {
			if (!(rmidi->output.flags & SND_RAWMIDI_FLG_TIMER)) {
				snd_mutex_unlock(rmidi, output);
				rmidi->output.hw.io.write(rmidi);
				snd_mutex_lock(rmidi, output);
				if (rmidi->output.u.s.used > 0) {
					rmidi->output.flags |= SND_RAWMIDI_FLG_TIMER;
					rmidi->output.timer.expires = 1;
					snd_add_timer(&rmidi->output.timer);
				}
			}
		} else {
			if (rmidi->output.flags & SND_RAWMIDI_FLG_TIMER) {
				rmidi->output.flags &= ~SND_RAWMIDI_FLG_TIMER;
				snd_del_timer(&rmidi->output.timer);
			}
		}
		snd_mutex_unlock(rmidi, output);
	}
}

static void snd_rawmidi_poll_input(unsigned long data)
{
	snd_rawmidi_t *rmidi;

	rmidi = (snd_rawmidi_t *) data;
	rmidi->output.hw.io.read(rmidi);
	snd_mutex_lock(rmidi, input);
	rmidi->input.flags |= SND_RAWMIDI_FLG_TIMER;
	rmidi->input.timer.data = (unsigned long) rmidi;
	rmidi->input.timer.expires = 1;
	snd_add_timer(&rmidi->input.timer);
	snd_mutex_unlock(rmidi, input);
}

void snd_rawmidi_trigger_input(snd_rawmidi_t * rmidi, int up)
{
	int flg;

#if 0
	snd_printk("trigger_input - %i\n", up);
#endif
	if (!(rmidi->input.hw.flags & SND_RAWMIDI_HW_POLL)) {
		flg = up ? SND_RAWMIDI_FLG_TRIGGER : 0;
		snd_mutex_lock(rmidi, input);
		if ((rmidi->input.flags & SND_RAWMIDI_FLG_TRIGGER) != flg) {
			if (up) {
				rmidi->input.flags |= SND_RAWMIDI_FLG_TRIGGER;
			} else {
				rmidi->input.flags &= ~SND_RAWMIDI_FLG_TRIGGER;
			}
			snd_mutex_unlock(rmidi, input);
			rmidi->input.hw.trigger(rmidi, up);
			return;
		}
		snd_mutex_unlock(rmidi, input);
	} else {
		snd_mutex_lock(rmidi, input);
		if (up) {
			if (!(rmidi->input.flags & SND_RAWMIDI_FLG_TIMER)) {
				snd_mutex_unlock(rmidi, input);
				rmidi->input.hw.io.read(rmidi);
				snd_mutex_lock(rmidi, input);
				rmidi->input.flags |= SND_RAWMIDI_FLG_TIMER;
				rmidi->input.timer.expires = 1;
				snd_add_timer(&rmidi->input.timer);
			}
		} else {
			if (rmidi->input.flags & SND_RAWMIDI_FLG_TIMER) {
				rmidi->input.flags &= ~SND_RAWMIDI_FLG_TIMER;
				snd_del_timer(&rmidi->input.timer);
			}
		}
		snd_mutex_unlock(rmidi, input);
	}
}

int snd_rawmidi_drain_output(snd_rawmidi_t * rmidi)
{
	snd_rawmidi_trigger_output(rmidi, 0);
	/* interrupts aren't enabled at this moment, so mutexlock isn't needed */
	rmidi->output.u.s.used =
	  rmidi->output.u.s.head =
	  rmidi->output.u.s.tail = 0;
	return 0;
}

int snd_rawmidi_flush_output(snd_rawmidi_t * rmidi)
{
	int err;

	err = 0;
	while (rmidi->output.u.s.used > 0) {
#if 0
		snd_printk("flush - used = %i\n", rmidi->output.u.s.used);
#endif
		snd_mutex_lock(rmidi, output);
		rmidi->output.flags |= SND_RAWMIDI_FLG_FLUSH;
		snd_sleep(rmidi, output, output, 10 * 50);
		rmidi->output.flags &= ~SND_RAWMIDI_FLG_FLUSH;
		snd_mutex_unlock(rmidi, output);
		if (snd_sleep_abort(rmidi, output)) {
			err = -EINTR;
			break;
		}
		if (rmidi->output.u.s.used >= rmidi->output.u.s.size &&
		    snd_timeout(rmidi, output)) {
			err = -EIO;
			break;
		}
	}
	snd_rawmidi_drain_output(rmidi);
	return err;
}

static int snd_rawmidi_flush_input(snd_rawmidi_t * rmidi)
{
	snd_rawmidi_trigger_input(rmidi, 0);
	/* interrupts aren't enabled at this moment, so mutexlock isn't needed */
	rmidi->input.u.s.used =
	  rmidi->input.u.s.head =
	  rmidi->input.u.s.tail = 0;
	return 0;
}

static int snd_rawmidi_open(void *data, snd_ocb_t *ocb)
{
	snd_rawmidi_t *rmidi = (snd_rawmidi_t *)data;
	unsigned short fflags;
	int err;

	fflags = snd_rawmidi_file_flags(ocb);
	if (!rmidi)
		return -ENODEV;
	snd_mutex_lock(rmidi, open);
	if (fflags & SND_RAWMIDI_LFLG_INPUT) {
		if (!(rmidi->info_flags & SND_RAWMIDI_INFO_INPUT)) {
			snd_mutex_unlock(rmidi, open);
			return -ENXIO;
		}
	}
	if (fflags & SND_RAWMIDI_LFLG_OUTPUT) {
		if (!(rmidi->info_flags & SND_RAWMIDI_INFO_OUTPUT)) {
			snd_mutex_unlock(rmidi, open);
			return -ENXIO;
		}
	}
	if (rmidi->flags & fflags) {
		snd_mutex_unlock(rmidi, open);
		return -EBUSY;
	}
	if (fflags & SND_RAWMIDI_LFLG_INPUT) {
		if (snd_rawmidi_init_buffer(rmidi, SND_RAWMIDI_LFLG_INPUT) < 0) {
			snd_mutex_unlock(rmidi, open);
			return -ENOMEM;
		}
		if ((err = rmidi->input.hw.open(rmidi)) < 0) {
			snd_rawmidi_done_buffer(rmidi, SND_RAWMIDI_LFLG_INPUT);
			snd_mutex_unlock(rmidi, open);
			return err;
		}
		rmidi->input.data = snd_rawmidi_receive;
#if 0
		if (minor < SND_MINOR_BEGIN)
			rmidi->input.flags |= SND_RAWMIDI_FLG_OSS;
		else
			rmidi->input.flags &= ~SND_RAWMIDI_FLG_OSS;
#endif
	}
	if (fflags & SND_RAWMIDI_LFLG_OUTPUT) {
		if (snd_rawmidi_init_buffer(rmidi, SND_RAWMIDI_LFLG_OUTPUT) < 0) {
			if (fflags & SND_RAWMIDI_LFLG_INPUT) {
				rmidi->input.hw.close(rmidi);
				snd_rawmidi_done_buffer(rmidi, SND_RAWMIDI_LFLG_INPUT);
				rmidi->input.data = NULL;
			}
			snd_mutex_unlock(rmidi, open);
			return -ENOMEM;
		}
		if ((err = rmidi->output.hw.open(rmidi)) < 0) {
			if (fflags & SND_RAWMIDI_LFLG_INPUT) {
				rmidi->input.hw.close(rmidi);
				snd_rawmidi_done_buffer(rmidi, SND_RAWMIDI_LFLG_INPUT);
				rmidi->input.data = NULL;
			}
			snd_mutex_unlock(rmidi, open);
			return err;
		}
		rmidi->output.data = snd_rawmidi_transmit;
#if 0
		if (minor < SND_MINOR_BEGIN)
			rmidi->output.flags |= SND_RAWMIDI_FLG_OSS;
		else
			rmidi->output.flags &= ~SND_RAWMIDI_FLG_OSS;
#endif
	}
	rmidi->flags |= fflags;
	ocb->private_data = rmidi;
	snd_mutex_unlock(rmidi, open);
	return 0;
}

static int snd_rawmidi_close(snd_ocb_t *ocb)
{
	unsigned short fflags;
	snd_rawmidi_t *rmidi;

	fflags = snd_rawmidi_file_flags(ocb);
	rmidi = (snd_rawmidi_t *) ocb->private_data;
	if (!rmidi)
		return -ENODEV;
	snd_mutex_lock(rmidi, open);
	if (fflags & SND_RAWMIDI_LFLG_INPUT) {
		snd_rawmidi_trigger_input(rmidi, 0);
		rmidi->input.hw.close(rmidi);
		rmidi->input.data = NULL;
		snd_rawmidi_done_buffer(rmidi, SND_RAWMIDI_LFLG_INPUT);
	}
	if (fflags & SND_RAWMIDI_LFLG_OUTPUT) {
		if (!(ocb->iofunc_ocb.ioflag & O_NONBLOCK))
			snd_rawmidi_flush_output(rmidi);
		snd_rawmidi_trigger_output(rmidi, 0);
		rmidi->output.hw.close(rmidi);
		rmidi->output.data = NULL;
		snd_rawmidi_done_buffer(rmidi, SND_RAWMIDI_LFLG_OUTPUT);
	}
	rmidi->flags &= ~fflags;
	snd_mutex_unlock(rmidi, open);
	return 0;
}

static int snd_rawmidi_info(snd_rawmidi_t * rmidi, snd_rawmidi_info_t * _info)
{
	snd_rawmidi_info_t info;

	memset(&info, 0, sizeof(info));
	info.type = rmidi->card->type;
	info.flags = rmidi->info_flags;
	strcpy(info.id, rmidi->id);
	strcpy(info.name, rmidi->name);
	memcpy( _info, &info, sizeof(snd_rawmidi_info_t));
	return sizeof( snd_rawmidi_info_t );
}

static int snd_rawmidi_output_info(snd_rawmidi_t * rmidi,
				   snd_rawmidi_output_info_t * _info)
{
	snd_rawmidi_output_info_t info;

	memset(&info, 0, sizeof(info));
	info.switches = snd_switch_count(&rmidi->output.switches);
	memcpy(_info, &info, sizeof(snd_rawmidi_output_info_t));
	return sizeof( snd_rawmidi_output_info_t );
}

static int snd_rawmidi_input_info(snd_rawmidi_t * rmidi,
				  snd_rawmidi_input_info_t * _info)
{
	snd_rawmidi_input_info_t info;

	memset(&info, 0, sizeof(info));
	info.switches = snd_switch_count(&rmidi->input.switches);
	memcpy(_info, &info, sizeof(snd_rawmidi_input_info_t));
	return sizeof( snd_rawmidi_input_info_t );
}

static int snd_rawmidi_output_params(snd_rawmidi_t * rmidi,
				     snd_rawmidi_output_params_t * _params)
{
	snd_rawmidi_output_params_t params;
	char *newbuf;

	memcpy(&params, _params, sizeof(snd_rawmidi_output_params_t));
	snd_rawmidi_flush_output(rmidi);
	if (params.size < 32 || params.size > 1024L * 1024L)
		return -EINVAL;
	if (params.room < 1 || params.room >= params.size)
		return -EINVAL;
	if (params.max < 1 || params.max >= params.size || params.max < params.room)
		return -EINVAL;
	if ((newbuf = (char *) snd_malloc(params.size)) == NULL)
		return -ENOMEM;
	snd_free(rmidi->output.u.s.buffer, rmidi->output.u.s.size);
	rmidi->output.u.s.buffer = newbuf;
	rmidi->output.u.s.size = params.size;
	rmidi->output.u.s.used_max = params.max;
	rmidi->output.u.s.used_room = params.room;
	return 0;
}

static int snd_rawmidi_input_params(snd_rawmidi_t * rmidi,
				    snd_rawmidi_input_params_t * _params)
{
	snd_rawmidi_input_params_t params;
	char *newbuf;

	memcpy(&params, _params, sizeof(snd_rawmidi_input_params_t));
	snd_rawmidi_flush_input(rmidi);
	if (params.size < 32 || params.size > 1024L * 1024L)
		return -EINVAL;
	if (params.min < 1 || params.min >= params.size)
		return -EINVAL;
	if ((newbuf = (char *) snd_malloc(params.size)) == NULL)
		return -ENOMEM;
	snd_free(rmidi->input.u.s.buffer, rmidi->input.u.s.size);
	rmidi->input.u.s.buffer = newbuf;
	rmidi->input.u.s.size = params.size;
	rmidi->input.u.s.used_min = params.min;
	return 0;
}

static int snd_rawmidi_output_status(snd_rawmidi_t * rmidi,
				     snd_rawmidi_output_status_t * _status)
{
	snd_rawmidi_output_status_t status;

	memset(&status, 0, sizeof(status));
	snd_rawmidi_flush_output(rmidi);
	snd_mutex_lock(rmidi, output);
	status.size = rmidi->output.u.s.size;
	status.count = rmidi->output.u.s.size - rmidi->output.u.s.used;
	status.queue = rmidi->output.u.s.used;
	snd_mutex_unlock(rmidi, output);
	memcpy(_status, &status, sizeof(snd_rawmidi_output_status_t));
	return sizeof( snd_rawmidi_output_status_t );
}

static int snd_rawmidi_input_status(snd_rawmidi_t * rmidi,
				    snd_rawmidi_input_status_t * _status)
{
	snd_rawmidi_input_status_t status;

	memset(&status, 0, sizeof(status));
	snd_rawmidi_flush_input(rmidi);
	snd_mutex_lock(rmidi, input);
	status.size = rmidi->input.u.s.size;
	status.count = rmidi->input.u.s.used;
	status.free = rmidi->input.u.s.size - rmidi->input.u.s.used;
	status.overrun = rmidi->input.u.s.xruns;
	rmidi->input.u.s.xruns = 0;
	snd_mutex_unlock(rmidi, input);
	memcpy(_status, &status, sizeof(snd_rawmidi_input_status_t));
	return sizeof(snd_rawmidi_input_status_t );
}

static int snd_rawmidi_ioctl(snd_ocb_t *ocb, unsigned int cmd, unsigned long arg, int len)
{
	snd_rawmidi_t *rmidi;
	unsigned short fflags;

	rmidi = (snd_rawmidi_t *) ocb->private_data;
	if (!rmidi)
		return -EIO;
	if (((cmd >> 8) & 0xff) != 'W')
		return -EIO;
	fflags = snd_rawmidi_file_flags(ocb);
	switch (cmd) {
	case SND_RAWMIDI_IOCTL_PVERSION:
		return snd_ioctl_out((long *) arg, SND_RAWMIDI_VERSION);
	case SND_RAWMIDI_IOCTL_INFO:
		return snd_rawmidi_info(rmidi, (snd_rawmidi_info_t *) arg);
	case SND_RAWMIDI_IOCTL_OUTPUT_INFO:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		return snd_rawmidi_output_info(rmidi, (snd_rawmidi_output_info_t *) arg);
	case SND_RAWMIDI_IOCTL_INPUT_INFO:
		if (!(fflags & SND_RAWMIDI_LFLG_INPUT))
			return -EIO;
		return snd_rawmidi_input_info(rmidi, (snd_rawmidi_input_info_t *) arg);
	case SND_RAWMIDI_IOCTL_OSWITCHES:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		*(int *)arg = snd_switch_count(&rmidi->output.switches);
		return sizeof(int);
	case SND_RAWMIDI_IOCTL_OSWITCH_READ:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		return snd_switch_read(&rmidi->output.switches, rmidi, (snd_switch_t *) arg);
	case SND_RAWMIDI_IOCTL_OSWITCH_WRITE:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		return snd_switch_write(&rmidi->output.switches, rmidi, (snd_switch_t *) arg);
	case SND_RAWMIDI_IOCTL_ISWITCHES:
		if (!(fflags & SND_RAWMIDI_LFLG_INPUT))
			return -EIO;
		*(int *)arg = snd_switch_count(&rmidi->input.switches);
		return sizeof(int);
	case SND_RAWMIDI_IOCTL_ISWITCH_READ:
		if (!(fflags & SND_RAWMIDI_LFLG_INPUT))
			return -EIO;
		return snd_switch_read(&rmidi->input.switches, rmidi, (snd_switch_t *) arg);
	case SND_RAWMIDI_IOCTL_ISWITCH_WRITE:
		if (!(fflags & SND_RAWMIDI_LFLG_INPUT))
			return -EIO;
		return snd_switch_write(&rmidi->input.switches, rmidi, (snd_switch_t *) arg);
	case SND_RAWMIDI_IOCTL_OUTPUT_PARAMS:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		return snd_rawmidi_output_params(rmidi, (snd_rawmidi_output_params_t *) arg);
	case SND_RAWMIDI_IOCTL_INPUT_PARAMS:
		if (!(fflags & SND_RAWMIDI_LFLG_INPUT))
			return -EIO;
		return snd_rawmidi_input_params(rmidi, (snd_rawmidi_input_params_t *) arg);
	case SND_RAWMIDI_IOCTL_OUTPUT_STATUS:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		return snd_rawmidi_output_status(rmidi, (snd_rawmidi_output_status_t *) arg);
	case SND_RAWMIDI_IOCTL_INPUT_STATUS:
		if (!(fflags & SND_RAWMIDI_LFLG_INPUT))
			return -EIO;
		return snd_rawmidi_input_status(rmidi, (snd_rawmidi_input_status_t *) arg);
	case SND_RAWMIDI_IOCTL_DRAIN_OUTPUT:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		return snd_rawmidi_drain_output(rmidi);
	case SND_RAWMIDI_IOCTL_FLUSH_OUTPUT:
		if (!(fflags & SND_RAWMIDI_LFLG_OUTPUT))
			return -EIO;
		return snd_rawmidi_flush_output(rmidi);
	case SND_RAWMIDI_IOCTL_FLUSH_INPUT:
		if (!(fflags & SND_RAWMIDI_LFLG_INPUT))
			return -EIO;
		return snd_rawmidi_flush_input(rmidi);
#ifdef SNDCFG_DEBUG
	default:
		snd_printk("rawmidi: unknown command = 0x%x\n", cmd);
#endif
	}
	return -EIO;
}

int snd_rawmidi_control_ioctl(snd_card_t * card, snd_control_t * control, unsigned int cmd, unsigned long arg)
{
	unsigned int tmp;
	int idx;
	snd_rawmidi_t *rmidi;

	tmp = card->number << 2;
	switch (cmd) {
	case SND_CTL_IOCTL_HW_INFO:
		{
			struct snd_ctl_hw_info *ptr = (struct snd_ctl_hw_info *) arg;

			ptr->mididevs = 0;
			for (idx = SND_RAWMIDI_DEVICES - 1; idx >= 0; idx--) {
				if (snd_rawmidi_devices[tmp + idx]) {
					ptr->mididevs = idx + 1;
					break;
				}
			}
			return 0;
		}
	case SND_CTL_IOCTL_RAWMIDI_DEVICE:
		{
			int val = snd_ioctl_in((long *) arg);
			if (val < 0 || val >= SND_RAWMIDI_DEVICES)
				return -EINVAL;
			if (!snd_rawmidi_devices[tmp + val])
				return -EINVAL;
			control->rawmidi_device = val;
			return 0;
		}
	case SND_CTL_IOCTL_RAWMIDI_INFO:
	case SND_CTL_IOCTL_RAWMIDI_OUTPUT_INFO:
	case SND_CTL_IOCTL_RAWMIDI_INPUT_INFO:
	case SND_CTL_IOCTL_RAWMIDI_OSWITCHES:
	case SND_CTL_IOCTL_RAWMIDI_OSWITCH_READ:
	case SND_CTL_IOCTL_RAWMIDI_OSWITCH_WRITE:
	case SND_CTL_IOCTL_RAWMIDI_ISWITCHES:
	case SND_CTL_IOCTL_RAWMIDI_ISWITCH_READ:
	case SND_CTL_IOCTL_RAWMIDI_ISWITCH_WRITE:
		rmidi = snd_rawmidi_devices[tmp + control->rawmidi_device];
		if (!rmidi)
			return -ENOENT;
		switch (cmd) {
		case SND_CTL_IOCTL_RAWMIDI_INFO:
			return snd_rawmidi_info(rmidi, (snd_rawmidi_info_t *) arg);
		case SND_CTL_IOCTL_RAWMIDI_OUTPUT_INFO:
			return snd_rawmidi_output_info(rmidi, (snd_rawmidi_output_info_t *) arg);
		case SND_CTL_IOCTL_RAWMIDI_INPUT_INFO:
			return snd_rawmidi_input_info(rmidi, (snd_rawmidi_input_info_t *) arg);
		case SND_CTL_IOCTL_RAWMIDI_OSWITCHES:
			*(int *)arg = snd_switch_count(&rmidi->output.switches);
			return sizeof(int);
		case SND_CTL_IOCTL_RAWMIDI_OSWITCH_READ:
			return snd_switch_read(&rmidi->output.switches, rmidi, (snd_switch_t *) arg);
		case SND_CTL_IOCTL_RAWMIDI_OSWITCH_WRITE:
			return snd_switch_write(&rmidi->output.switches, rmidi, (snd_switch_t *) arg);
		case SND_CTL_IOCTL_RAWMIDI_ISWITCHES:
			*(int *)arg = snd_switch_count(&rmidi->input.switches);
			return sizeof(int);
		case SND_CTL_IOCTL_RAWMIDI_ISWITCH_READ:
			return snd_switch_read(&rmidi->input.switches, rmidi, (snd_switch_t *) arg);
		case SND_CTL_IOCTL_RAWMIDI_ISWITCH_WRITE:
			return snd_switch_write(&rmidi->input.switches, rmidi, (snd_switch_t *) arg);
		}
		break;
	}
	return -EAGAIN;
}

int snd_rawmidi_receive(snd_rawmidi_t * rmidi, char *buffer, int count)
{
	int count1;

#if 0
	snd_printk("rx: count = %i\n", count);
#endif
	if (!rmidi->input.u.s.buffer) {
		snd_printd("snd_rawmidi_receive: input isn't active!!!\n");
		return -EINVAL;
	}
	snd_mutex_lock(rmidi, input);
	rmidi->input.bytes += count;
	count1 = rmidi->input.u.s.size - rmidi->input.u.s.head;
	if (count1 > count)
		count1 = count;
	if (count1 > rmidi->input.u.s.size - rmidi->input.u.s.used) {
		count1 = rmidi->input.u.s.size - rmidi->input.u.s.used;
	}
	memcpy(rmidi->input.u.s.buffer + rmidi->input.u.s.head, buffer, count1);
	rmidi->input.u.s.head += count1;
	rmidi->input.u.s.head %= rmidi->input.u.s.size;
	rmidi->input.u.s.used += count1;
	count -= count1;
	if (count > 0) {
		count1 = count;
		if (count > rmidi->input.u.s.size - rmidi->input.u.s.used) {
			count1 = rmidi->input.u.s.size - rmidi->input.u.s.used;
			rmidi->input.u.s.xruns = count - count1;
		}
		if (count1 > 0) {
			memcpy(rmidi->input.u.s.buffer, buffer + count1, count1);
			rmidi->input.u.s.head = count1;
			rmidi->input.u.s.used += count1;
		}
	}
	snd_mutex_unlock(rmidi, input);
	if ((rmidi->input.flags & SND_RAWMIDI_FLG_SLEEP) && snd_rawmidi_input_ok(rmidi)) {
		snd_wakeup(rmidi, input);
	}
#if 0
	snd_printk("rx: head = %i, tail = %i, used = %i, size = %i\n", rmidi->input.head, rmidi->input.tail, rmidi->input.used, rmidi->input.size);
#endif
	return 0;
}

static long snd_rawmidi_read(snd_context_t *ctp, long count)
{
	long result;
	int count1;
	snd_rawmidi_t *rmidi;

	rmidi = (snd_rawmidi_t *) ctp->ocb->private_data;
	if (!rmidi)
		return -EIO;
	result = 0;
	snd_rawmidi_trigger_input(rmidi, 1);
	while (count > 0) {
		while (!snd_rawmidi_input_ok(rmidi)) {
			if (ctp->nonblock)
				return result;
			snd_mutex_lock(rmidi, input);
			rmidi->input.flags |= SND_RAWMIDI_FLG_SLEEP;
			snd_sleep(rmidi, input, input, 10 * 50);
			rmidi->input.flags &= ~SND_RAWMIDI_FLG_SLEEP;
			snd_mutex_unlock(rmidi, input);
			if (snd_sleep_abort(rmidi, input))
				return result;
			if (!rmidi->input.u.s.used && snd_timeout(rmidi, input))
				return -EIO;
		}
		while (count > 0 && rmidi->input.u.s.used) {
			count1 = rmidi->input.u.s.size - rmidi->input.u.s.tail;
			if (count1 > count)
				count1 = count;
			snd_mutex_lock(rmidi, input);
			if (count1 > rmidi->input.u.s.used)
				count1 = rmidi->input.u.s.used;
			snd_mutex_unlock(rmidi, input);
			snd_copy_to_user(ctp, rmidi->input.u.s.buffer + rmidi->input.u.s.tail, count1);
			snd_mutex_lock(rmidi, input);
			rmidi->input.u.s.tail += count1;
			rmidi->input.u.s.tail %= rmidi->input.u.s.size;
			rmidi->input.u.s.used -= count1;
			snd_mutex_unlock(rmidi, input);
			result += count1;
			count -= count1;
		}
	}
	return result;
}

int snd_rawmidi_transmit(snd_rawmidi_t * rmidi, char *buffer, int count)
{
	int result, count1;

	result = 0;
#if 0
	snd_printk("transmit: count = %i\n", count);
#endif
	if (!rmidi->output.u.s.buffer) {
		snd_printd("snd_rawmidi_transmit: output isn't active!!!\n");
		return 0;
	}
	snd_mutex_lock(rmidi, output);
	count1 = rmidi->output.u.s.size - rmidi->input.u.s.tail;
	if (count1 > count)
		count1 = count;
	if (count1 > rmidi->output.u.s.used)
		count1 = rmidi->output.u.s.used;
	if (!count1) {
		snd_mutex_unlock(rmidi, output);
		snd_rawmidi_trigger_output(rmidi, 0);
		if (rmidi->output.flags & (SND_RAWMIDI_FLG_FLUSH | SND_RAWMIDI_FLG_SLEEP))
			snd_wakeup(rmidi, output);
		return 0;
	}
	memcpy(buffer, rmidi->output.u.s.buffer + rmidi->output.u.s.tail, count1);
	rmidi->output.u.s.tail += count1;
	rmidi->output.u.s.tail %= rmidi->output.u.s.size;
	rmidi->output.u.s.used -= count1;
	count -= count1;
	result += count1;
	if (count > 0) {
		memcpy(buffer + count1, rmidi->output.u.s.buffer, count);
		rmidi->output.u.s.tail = count;
		rmidi->output.u.s.used -= count;
		result += count;
	}
	if (!rmidi->output.u.s.used) {
		snd_mutex_unlock(rmidi, output);
		if (!result)
			snd_rawmidi_trigger_output(rmidi, 0);
		if (rmidi->output.flags & (SND_RAWMIDI_FLG_FLUSH | SND_RAWMIDI_FLG_SLEEP))
			snd_wakeup(rmidi, output);
	} else {
		snd_mutex_unlock(rmidi, output);
		if ((rmidi->output.flags & SND_RAWMIDI_FLG_SLEEP) &&
		    snd_rawmidi_output_ok(rmidi))
			snd_wakeup(rmidi, output);
	}
#if 0
	snd_printk("write1: head = %i, tail = %i, used = %i, size = %i\n", rmidi->output.u.s.head, rmidi->output.u.s.tail, rmidi->output.u.s.used, rmidi->output.u.s.size);
	snd_printk("transmit result = %i\n", result);
#endif
	rmidi->output.bytes += result;
	return result;
}

static long snd_rawmidi_write(snd_context_t *ctp, long count)
{
	long result;
	int count1;
	snd_rawmidi_t *rmidi;

	rmidi = (snd_rawmidi_t *) ctp->ocb->private_data;
	if (!rmidi)
		return -EIO;
	result = 0;
	while (count > 0) {
		while (!snd_rawmidi_output_ok(rmidi)) {
			if (ctp->nonblock)
				return result;
			snd_mutex_lock(rmidi, output);
			rmidi->output.flags |= SND_RAWMIDI_FLG_SLEEP;
			snd_sleep(rmidi, output, output, 10 * 50);
			rmidi->output.flags &= ~SND_RAWMIDI_FLG_SLEEP;
			snd_mutex_unlock(rmidi, output);
			if (snd_sleep_abort(rmidi, output))
				return -EINTR;
			if (rmidi->output.u.s.used >= rmidi->output.u.s.size &&
			    snd_timeout(rmidi, output))
				return -EIO;
		}
		while (count > 0 && rmidi->output.u.s.used < rmidi->output.u.s.size) {
			count1 = rmidi->output.u.s.size - rmidi->output.u.s.head;
			if (count1 > count)
				count1 = count;
			snd_mutex_lock(rmidi, output);
			if (count1 > rmidi->output.u.s.size - rmidi->output.u.s.used)
				count1 = rmidi->output.u.s.size - rmidi->output.u.s.used;
			snd_mutex_unlock(rmidi, output);
			snd_copy_from_user(ctp, rmidi->output.u.s.buffer + rmidi->output.u.s.head, count1);
			snd_mutex_lock(rmidi, output);
			rmidi->output.u.s.head += count1;
			rmidi->output.u.s.head %= rmidi->output.u.s.size;
			rmidi->output.u.s.used += count1;
			snd_mutex_unlock(rmidi, output);
			result += count1;
			count -= count1;
		}
		snd_rawmidi_trigger_output(rmidi, 1);
	}
	return result;
}

static int snd_rawmidi_notify(snd_ocb_t *ocb, int *trig)
{
	snd_rawmidi_t *rmidi;
	unsigned short fflags;

	fflags = snd_rawmidi_file_flags(ocb);
	rmidi = (snd_rawmidi_t *) ocb->private_data;
	if (!rmidi)
		return -EIO;

	if (fflags & SND_RAWMIDI_LFLG_INPUT) {
		snd_rawmidi_trigger_input(rmidi, 1);
		snd_mutex_lock(rmidi, input);
		if (!snd_rawmidi_input_ok(rmidi)) {
			rmidi->input.flags |= SND_RAWMIDI_FLG_IONOTIFY;
		} else {
			rmidi->input.flags &= ~SND_RAWMIDI_FLG_IONOTIFY;
			*trig |= NOTIFY_INPUT;
		}
		snd_mutex_unlock(rmidi, input);
	}
	if (fflags & SND_RAWMIDI_LFLG_OUTPUT) {
		snd_mutex_lock(rmidi, output);
		if (!snd_rawmidi_output_ok(rmidi)) {
			rmidi->output.flags |= SND_RAWMIDI_FLG_IONOTIFY;
		} else {
			rmidi->output.flags &= ~SND_RAWMIDI_FLG_IONOTIFY;
			*trig |= NOTIFY_OUTPUT;
		}
		snd_mutex_unlock(rmidi, output);
	}
	return 0;
}

/*

 */

void snd_rawmidi_command_reset(snd_rawmidi_t * rmidi, int direction)
{
	struct snd_stru_rawmidi_direction *pdir;

	pdir = direction == SND_RAWMIDI_LFLG_INPUT ? &rmidi->input : &rmidi->output;
	pdir->u.p.cused = 0;
	pdir->u.p.cprev = 0;
}

/*

 */

static void snd_rawmidi_proc_info_read(snd_info_buffer_t * buffer, void *private_data)
{
	snd_rawmidi_t *rmidi;

	rmidi = (snd_rawmidi_t *) private_data;
	snd_iprintf(buffer, "%s\n\n", rmidi->name);
	if (rmidi->info_flags & SND_RAWMIDI_INFO_OUTPUT) {
		snd_iprintf(buffer,
			    "Output\n"
			    "  Tx bytes     : %u\n",
			    rmidi->output.bytes);
		if (rmidi->flags & SND_RAWMIDI_LFLG_OUTPUT) {
			snd_iprintf(buffer,
				    "  Mode         : %s\n"
				    "  Buffer size  : %d\n",
				    rmidi->output.flags & SND_RAWMIDI_FLG_OSS ?
				    		"OSS compatible" : "native",
				    rmidi->output.u.s.size);
		}
	}
	if (rmidi->info_flags & SND_RAWMIDI_INFO_INPUT) {
		snd_iprintf(buffer,
			    "Input\n"
			    "  Rx bytes     : %u\n",
			    rmidi->input.bytes);
		if (rmidi->flags & SND_RAWMIDI_LFLG_INPUT) {
			snd_iprintf(buffer,
				    "  Mode         : %s\n"
				    "  Buffer size  : %d\n"
				    "  Overruns     : %d\n",
				    rmidi->input.flags & SND_RAWMIDI_FLG_OSS ?
				    		"OSS compatible" : "native",
				    rmidi->input.u.s.size,
				    rmidi->input.u.s.xruns);
		}
	}
}
/*
 *  Register functions
 */

static snd_io_t snd_rawmidi_reg =
{
	"raw midi",

	snd_rawmidi_read,	/* read */
	snd_rawmidi_write,	/* write */
	snd_rawmidi_open,	/* open */
	snd_rawmidi_close,	/* close */
	snd_rawmidi_notify,	/* notify */
	snd_rawmidi_ioctl,	/* ioctl */
};

snd_rawmidi_t *snd_rawmidi_new_device(snd_card_t * card, char *id)
{
	snd_rawmidi_t *rmidi;

	if (!card)
		return NULL;
	rmidi = (snd_rawmidi_t *) snd_malloc(sizeof(snd_rawmidi_t));
	if (!rmidi)
		return NULL;
	memset(rmidi, 0, sizeof(snd_rawmidi_t));
	rmidi->card = card;
	snd_mutex_prepare(rmidi, input);
	snd_mutex_prepare(rmidi, output);
	snd_mutex_prepare(rmidi, open);
	snd_sleep_prepare(rmidi, input);
	snd_sleep_prepare(rmidi, output);
	snd_switch_prepare(&rmidi->input.switches);
	snd_switch_prepare(&rmidi->output.switches);
	rmidi->input.timer.data = (unsigned long) rmidi;
	rmidi->input.timer.function = snd_rawmidi_poll_input;
	rmidi->output.timer.data = (unsigned long) rmidi;
	rmidi->output.timer.function = snd_rawmidi_poll_output;
	if (id) {
		strncpy(rmidi->id, id, sizeof(rmidi->id) - 1);
	}
	return rmidi;
}

int snd_rawmidi_free(snd_rawmidi_t * rmidi)
{
	if (!rmidi)
		return -EINVAL;
	snd_switch_free(&rmidi->output.switches);
	snd_switch_free(&rmidi->input.switches);
	if (rmidi->output.hw.private_free)
		rmidi->output.hw.private_free(rmidi->output.hw.private_data);
	if (rmidi->input.hw.private_free)
		rmidi->input.hw.private_free(rmidi->input.hw.private_data);
	if (rmidi->private_free)
		rmidi->private_free(rmidi->private_data);
	snd_mutex_destroy(rmidi, input);
	snd_mutex_destroy(rmidi, output);
	snd_mutex_destroy(rmidi, open);
	snd_sleep_destroy(rmidi, input);
	snd_sleep_destroy(rmidi, output);
	snd_free(rmidi, sizeof(snd_rawmidi_t));
	return 0;
}

static void snd_rawmidi_store(snd_rawmidi_t * rmidi)
{
	int size;
	long err = 0;
	unsigned int pos;
	char *buffer;
	snd_kswitch_list_t *loswitch, *liswitch;
	char key[32];
	
	loswitch = &rmidi->output.switches;
	liswitch = &rmidi->input.switches;
	snd_switch_lock(loswitch, 0);
	snd_switch_lock(liswitch, 0);
	size = sizeof(rmidi->id) + sizeof(rmidi->name) +
	       snd_switch_size(loswitch) + snd_switch_size(liswitch);
	buffer = (char *) snd_malloc(size);
	if (!buffer) {
		snd_printk("cannot store rawmidi settings - no enough memory\n" );
		snd_switch_lock(loswitch, 1);
		snd_switch_lock(liswitch, 1);
		return;
	}
	pos = 0;
	memcpy(buffer + pos, rmidi->id, sizeof(rmidi->id));
	pos += sizeof(rmidi->id);
	memcpy(buffer + pos, rmidi->name, sizeof(rmidi->name));
	pos += sizeof(rmidi->name);
//	err = snd_switch_store(loswitch, rmidi, buffer + pos, size - pos);
	snd_switch_lock(loswitch, 1);
	if (err < 0) {
		snd_printd("cannot store rawmidi output switch settings\n");
		snd_switch_lock(liswitch, 1);
		snd_free(buffer, size);
		return;
	}
	pos += err;
//	err = snd_switch_store(liswitch, rmidi, buffer + pos, size - pos);
	snd_switch_lock(liswitch, 1);
	if (err < 0) {
		snd_printd("cannot store rawmidi input switch settings\n");
		snd_free(buffer, size);
		return;
	}
	sprintf(key, "snd:%i/rawmidi:%i", rmidi->card->number, rmidi->device);
//	snd_persist_store(key, buffer, size);
	snd_free(buffer, size);
}

static void snd_rawmidi_restore(snd_rawmidi_t * rmidi)
{
	int size = 0;
	long err = 0;
	unsigned int pos;
	char *buffer;
	char key[32];
	
	sprintf(key, "snd:%i/rawmidi:%i", rmidi->card->number, rmidi->device);
//	size = snd_persist_length(key);
	if (size <= 0)
		return;
	buffer = (char *) snd_malloc(size);
	if (!buffer) {
		snd_printk("cannot restore rawmidi settings - no enough memory\n" );
		return;
	}
//	size = snd_persist_restore(key, buffer, size);
	/* ok. I'm very paranoid in bellow code */
	if (size < (int)(sizeof(rmidi->id) + sizeof(rmidi->name))) {
		snd_free(buffer, size);
		return;
	}
	pos = 0;
	if (strcmp(buffer + pos, rmidi->id)) {
		snd_free(buffer, size);
		return;
	}
	pos += sizeof(rmidi->id);
	if (strcmp(buffer + pos, rmidi->name)) {
		snd_free(buffer, size);
		return;
	}
	pos += sizeof(rmidi->name);
//	err = snd_switch_restore(&rmidi->output.switches, rmidi, buffer + pos, size - pos);
	if (err < 0) {
		snd_printk("cannot restore rawmidi output switch settings\n");
		snd_free(buffer, size);
		return;
	}
	pos += err;
//	err = snd_switch_restore(&rmidi->input.switches, rmidi, buffer + pos, size - pos);
	if (err < 0)
		snd_printk("cannot restore rawmidi input switch settings\n");
	snd_free(buffer, size);
}

int __snd_rawmidi_register(snd_rawmidi_t * rmidi, int rawmidi_device)
{
	int idx;
	unsigned short minor;
	snd_info_entry_t *entry;
	char name[16];

	if (rawmidi_device < 0 || rawmidi_device >= SND_RAWMIDI_DEVICES) {
		return -EINVAL;
	}
	idx = (rmidi->card->number * SND_RAWMIDI_DEVICES) + rawmidi_device;
	if (snd_rawmidi_devices[idx]) {
		return -EBUSY;
	}
	rmidi->device = rawmidi_device;
	snd_rawmidi_devices[idx] = rmidi;
	minor = SND_MINOR_RAWMIDI + idx;
	sprintf(name, "midi%i%i", rmidi->card->number, rawmidi_device);
	if ((rmidi->attr = snd_register_io(rmidi, name, &snd_rawmidi_reg)) < 0) {
		snd_rawmidi_devices[idx] = NULL;
		return -errno;
	}
	sprintf(name, "midi%i", rawmidi_device);
	entry = snd_info_create_entry(rmidi->card, name);
	if (entry) {
		entry->private_data = rmidi;
		entry->t.text.read_size = 1024;
		entry->t.text.read = snd_rawmidi_proc_info_read;
		if (snd_info_register(entry) < 0) {
			snd_info_free_entry(entry);
			entry = NULL;
		}
		rmidi->proc_entry = entry;
	}
	snd_rawmidi_restore(rmidi);
	return 0;
}

int __snd_rawmidi_unregister(snd_rawmidi_t * rmidi)
{
	int idx;

	if (!rmidi)
		return -EINVAL;
	idx = (rmidi->card->number * SND_RAWMIDI_DEVICES) + rmidi->device;
	if (snd_rawmidi_devices[idx] != rmidi) {
		return -EINVAL;
	}
	snd_rawmidi_store(rmidi);
	if (rmidi->proc_entry) {
		snd_info_unregister(rmidi->proc_entry);
		rmidi->proc_entry = NULL;
	}
	snd_info_free_device(rmidi->dev);
	snd_unregister_io(rmidi->attr);
	snd_rawmidi_devices[idx] = NULL;
	return snd_rawmidi_free(rmidi);
}

int snd_rawmidi_switch_add(snd_rawmidi_direction_t * dir, snd_kswitch_t * ksw)
{
	return snd_switch_add(&dir->switches, ksw);
}

int snd_rawmidi_switch_remove(snd_rawmidi_direction_t * dir, snd_kswitch_t * ksw)
{
	return snd_switch_remove(&dir->switches, ksw);
}

snd_kswitch_t *snd_rawmidi_switch_new(snd_rawmidi_direction_t * dir, snd_kswitch_t * ksw, void *private_data)
{
	snd_kswitch_t *sw;
	
	sw = snd_switch_new(ksw);
	if (!sw)
		return NULL;
	if (snd_rawmidi_switch_add(dir, sw) < 0) {
		snd_switch_free_one(sw);
		return NULL;
	}
	sw->private_data = private_data;
	return sw;
}
