/*
 *  The /proc routines for emu8000 (AWE32/64)
 *
 *  Copyright (C) 1999 Steve Ratcliffe
 *
 *   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.
 *
 */
/*
 * This is all new for alsa.
 * At present allows you to read the memory from the emu8000.
 *
 * This is mainly a debugging thing, I'm not sure that you would ever
 * want to do this in normal use, or even write directly to the ram.
 *
 * NOTE: the ram is addressed as words, whereas reading from this interface
 * will use the usual byte offsets.  If you are going from one to the
 * other don't get confused...
 */

#include "../../include/driver.h"
#include "../../include/emu8000.h"
#include "../../include/emu8000_reg.h"
#include "../../include/info.h"
#include "emu8000_port.h"

#define HACK_MIDI_IN

#ifdef HACK_MIDI_IN
int midi_in_client = 64;
int midi_in_port = 0;
int midi_in_queue = 0;
MODULE_PARM(midi_in_client, "i");
MODULE_PARM_DESC(midi_in_client, "client number to be connected");
MODULE_PARM(midi_in_port, "i");
MODULE_PARM_DESC(midi_in_port, "port number to be connected");
MODULE_PARM(midi_in_queue, "i");
MODULE_PARM_DESC(midi_in_queue, "queue number to be connected");
#endif

/*
 * The private data.  Mainly used to tell which memory area we are
 * looking at.
 */
struct proc_private {
	emu8000_t *emu;
	loff_t    begin;
	char      *name;
};

/* Prototypes for static functions */
static long proc_mem_read(void *private_data, void *file_private_data,
	 struct file *file, char *buf, long count);
static long patch_write(void *private_data, void *file_private_data,
	 struct file *file, const char *buf, long count);
static snd_info_entry_t *make_mem_proc_entry(emu8000_t *emu,
	 struct proc_private *pp);

/*
 * Read from the onboard memory.
 * This is arranged into two devices one for the rom and one for the ram.
 */
static long
proc_mem_read(void *private_data, void *file_private_data,
              struct file * file, char *buf, long count)
{
	struct proc_private *pp = (struct proc_private *)private_data;
	emu8000_t *emu;
	loff_t  off;
	unsigned short word;
	int  i;
	long result;
	
	if (pp == NULL)
		return -EIO;

	emu = pp->emu;
	off = pp->begin + file->f_pos;

	/* Don't allow the count to be odd */
	if (count & 1)
		count--;
	result = 0;

	/*
	 * Allocate all the channels for dma, this will cause all notes to be
	 * abruptly terminated...  FIXME(perhaps)
	 */
	for (i = 0; i < emu->max_voices; i++)
		snd_emu8000_dma_chan(emu, i, EMU8000_RAM_READ);

	snd_emu8000_read_wait(emu);

	/*
	 * This is the only way that I could get this to work, by reseting
	 * the address each time a word is read.  You are not supposed to
	 * have to do that, should be enough to keep reading the data
	 * register.  The flag to say that the read was complete does
	 * not appear to work.  YMMV.
	 */
	while (count > 0) {
		EMU8000_SMALR_WRITE(emu, off>>1); /* set read address */
		//snd_emu8000_read_wait(emu); /* needed ??? */
		EMU8000_SMLD_READ(emu);	/* Discard pre-fetch word */

		word = EMU8000_SMLD_READ(emu);
		buf[0] = word>>8;
		buf[1] = word;
		snd_emu8000_read_wait(emu); /* needed ??? */

		off += 2;
		buf += 2;
		count -= 2;
		result += 2;
	}
	file->f_pos += result;
	snd_emu8000_read_wait(emu);
	snd_emu8000_close_dma(emu);

	return result;
}

static long
patch_write(void *private_data, void *file_private_data,
	struct file * file, const char *buf, long count)
{
	struct proc_private *pp = (struct proc_private *)private_data;
	emu8000_t *emu;
	long result;

	emu = pp->emu;
	if (emu == NULL)
		return -EIO;

	result = snd_emu8000_patch(emu, buf, count);

	return result;
}


/* HACK HACK */
#ifdef HACK_MIDI_IN

#include "../../include/midi.h"

static void sub_midi_in_hack(emu8000_t *emu);
static void unsub_midi_in_hack(emu8000_t *emu);

static long
hack(void *private_data, void *file_private_data,
	struct file * file, char *buf, long count)
{
	struct proc_private *pp = (struct proc_private *)private_data;
	emu8000_t *emu;

	emu = pp->emu;

	switch (count) {
	case 2:	/* Subscribe to midi in */
		sub_midi_in_hack(emu);
		break;
	case 3:	/* Switch it off */
		unsub_midi_in_hack(emu);
		/*FALLTHROUGH*/
	case 4:
		snd_emu8000_terminate_all(emu);
		break;
	default:
		/* Whatever.. */
		break;
	}
	return count;
}

static void
sub_midi_in_hack(emu8000_t *emu)
{
	int  err;
	snd_seq_port_subscribe_t sub;

	/* Subscribe to the midi input */
	memset(&sub, 0, sizeof(sub));
	sub.sender.queue = midi_in_queue;
	sub.sender.client = midi_in_client;
	sub.sender.port = midi_in_port;
	sub.sender.channel = 0;

	sub.dest.queue = midi_in_queue;
	sub.dest.client = emu->client;
	sub.dest.port = emu->ports[0];
	sub.dest.channel = 0;

	err = snd_seq_kernel_client_ctl(emu->client,
		SND_SEQ_IOCTL_SUBSCRIBE_PORT, &sub);
	if (err < 0) {
		snd_printk("ioctl failed %d\n", err);
	}
}

static void
unsub_midi_in_hack(emu8000_t *emu)
{
	int  err;
	snd_seq_port_subscribe_t sub;

	/* Subscribe to the midi input */
	memset(&sub, 0, sizeof(sub));
	sub.sender.queue = midi_in_queue;
	sub.sender.client = midi_in_client;
	sub.sender.port = midi_in_port;
	sub.sender.channel = 0;

	sub.dest.queue = midi_in_queue;
	sub.dest.client = emu->client;
	sub.dest.port = emu->ports[0];
	sub.dest.channel = 0;

	err = snd_seq_kernel_client_ctl(emu->client,
		SND_SEQ_IOCTL_UNSUBSCRIBE_PORT, &sub);
	if (err < 0) {
		snd_printk("ioctl failed %d\n", err);
	}
}
#endif  /* HACK_MIDI_IN */


/*
 * Set up the /proc devices under the relevant card directory for this
 * device.
 */
void
snd_emu8000_proc_init(emu8000_t *emu)
{
	struct proc_private *rampp;
	struct proc_private *rompp;
	struct proc_private *patchpp;
	snd_info_entry_t *entry;

	patchpp = snd_kmalloc(sizeof(*rampp), GFP_KERNEL);
	if (patchpp == NULL)
		return;
	rampp = snd_kmalloc(sizeof(*rampp), GFP_KERNEL);
	if (rampp == NULL) {
		snd_kfree(patchpp);
		return;
	}
	rompp = snd_kmalloc(sizeof(*rompp), GFP_KERNEL);
	if (rompp == NULL) {
		snd_kfree(patchpp);
		snd_kfree(rampp);
		return;
	}

	/*
	 * Make a patch access device.  Writing to this will install patches
	 * onto the card.  At present done in a semi-compatible way to OSS to
	 * avoid extensive changes.  May be done completely differently in
	 * the future.
	 */
	patchpp->name = "patch0";	/* XXX */
	patchpp->emu = emu;
	entry = snd_info_create_entry(emu->card, patchpp->name);
	if (!entry) {
		snd_kfree(patchpp);
		snd_kfree(rampp);
		return;
	}

	/* Set up the routines */
	entry->type = SND_INFO_ENTRY_DATA;
	entry->mode |= S_IWUSR|S_IWGRP|S_IWOTH;
	entry->private_data = patchpp;
	entry->t.data.open = NULL;
	entry->t.data.release = NULL;
#ifdef HACK_MIDI_IN
	entry->t.data.read = hack;
#else
	entry->t.data.read = NULL;
#endif
	entry->t.data.write = patch_write;
	entry->t.data.ioctl = NULL;
	entry->t.data.poll = NULL;

	/* Now register it so it will appear */
	if (snd_info_register(entry) < 0) {
		snd_kfree(patchpp);
		snd_info_free_entry(entry);
		return;
	}
	emu->patch_proc_entry = entry;

	/*
	 * Make a device that allows us to view the on card RAM.  Mainly
	 * for debugging purposes.
	 */
	rampp->emu = emu;
	rampp->name = "ram0"; /* XXX ram%d, chip_number */
	rampp->begin  = EMU8000_DRAM_OFFSET << 1;

	entry = make_mem_proc_entry(emu, rampp);
	emu->ram_proc_entry = entry;

	/*
	 * Make a device that allows us to view the on card ROM.  Mainly
	 * for completeness.
	 */
	rompp->emu = emu;
	rompp->name = "rom0"; /* XXX */
	rompp->begin  = 0;

	entry = make_mem_proc_entry(emu, rompp);
	emu->rom_proc_entry = entry;
}

static snd_info_entry_t *
make_mem_proc_entry(emu8000_t *emu, struct proc_private *pp)
{
	snd_info_entry_t *entry;

	entry = snd_info_create_entry(emu->card, pp->name);
	if (!entry)
		return 0;

	/* Set up the routines */
	entry->type = SND_INFO_ENTRY_DATA;
	if (pp->name[1] == 'a')
		entry->mode |= S_IWUSR;
	entry->private_data = pp;
	entry->t.data.open = NULL;
	entry->t.data.release = NULL;
	entry->t.data.read = proc_mem_read;
	entry->t.data.write = NULL;
	entry->t.data.ioctl = NULL;
	entry->t.data.poll = NULL;

	/* Now register it so it will appear */
	if (snd_info_register(entry) < 0) {
		snd_kfree(pp);
		snd_info_free_entry(entry);
		return 0;
	}

	return entry;
}

void
snd_emu8000_proc_free(snd_info_entry_t *entry)
{
	if (!entry)
		return;

	if (entry->private_data)
		snd_kfree(entry->private_data);

	snd_info_unregister(entry);
}

