/*     
 **********************************************************************
 * 
 *     sndstat.c - /dev/sndstat interface for emu10k1 driver 
 *     Copyright 1999, 2000 Creative Labs, Inc. 
 *
 *     This file uses some code from soundcard.c, Copyright 1993-1997
 *     Hannu Savolainen
 * 
 ********************************************************************** 
 * 
 *     Date                 Author          Summary of changes 
 *     ----                 ------          ------------------ 
 *     October 20, 1999     Bertrand Lee    base code release 
 * 
 ********************************************************************** 
 * 
 *     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. 
 * 
 ********************************************************************** 
 */ 

#ifdef MODULE
#define __NO_VERSION__
#include <linux/module.h>
#endif

#include <linux/utsname.h>

#include "hwaccess.h"
#include "mycommon.h"
#include "sndstat.h"

static int sound_read(struct inode *inode, struct file *file, char *buf, int count)
{
	int dev = MINOR(inode->i_rdev);

	DPD("sound_read(dev=%d, count=%d)\n", dev, count);

	switch (dev & 0x0f) 
	{
		case SND_DEV_STATUS:
		return sndstat_file_read(file, buf, count, &file->f_pos);

		default: 
		break;
	}
	return -EINVAL;
}


/* 4K page size but our output routines use some slack for overruns */
#define PROC_BLOCK_SIZE (3*1024)

/*
 * basically copied from fs/proc/generic.c:proc_file_read 
 * should be removed sometime in the future together with /dev/sndstat
 * (a symlink /dev/sndstat -> /proc/sound will do as well)
 */
static int sndstat_file_read(struct file * file, char * buf, size_t nbytes, loff_t *ppos)
{
	char  *page;
	int   retval=0;
	int   eof=0;
	int   n, count;
	char  *start;

	if (!(page = (char*) __get_free_page(GFP_KERNEL)))
	  return -ENOMEM;

	while ((nbytes > 0) && !eof) 
	{
		count = min(PROC_BLOCK_SIZE, nbytes);

		start = NULL;
		n = sound_proc_get_info(page, &start, *ppos, count, 0);
		if (n < count)
		  eof = 1;
                        
		if (!start) 
		{
			/* For proc files that are less than 4k */
			start = page + *ppos;
			n -= *ppos;
			if (n <= 0)
			  break;
			if (n > count)
			  n = count;
		}
		
		if (n == 0)
		  break;  /* End of file */
		
		if (n < 0) 
		{
			if (retval == 0)
			  retval = n;
			break;
		}
                
		n -= copy_to_user(buf, start, n);       /* BUG ??? */
		if (n == 0) 
		{
			if (retval == 0)
			  retval = -EFAULT;
			break;
		}
                
		*ppos += n;     /* Move down the file */
		nbytes -= n;
		buf += n;
		retval += n;
	}
	
	free_page((unsigned long) page);
	return retval;
}


static int sound_proc_get_info(char *buffer, char **start, off_t offset, int length, int inout)
{
	int len, count;
	off_t begin = 0;
	struct sblive_hw *lastdev, *dev;

#ifdef MODULE
#define MODULEPROCSTRING "Driver loaded as a module"
#else
#define MODULEPROCSTRING "Driver compiled into kernel"
#endif

	for (lastdev = sblive_devs; lastdev->next; lastdev = lastdev->next);
	
	len = sprintf(buffer, "OSS/Free:" SOUND_VERSION_STRING "\n"
		      "Load type: " MODULEPROCSTRING "\n"
		      "Kernel: %s %s %s %s %s\n"
		      "Config options: %x\n\nInstalled drivers: \n", 
		      system_utsname.sysname, system_utsname.nodename, system_utsname.release, 
		      system_utsname.version, system_utsname.machine, SELECTED_SOUND_OPTIONS);

	len += sprintf(buffer + len, "Type 0: %s\n", "Creative EMU10K1 (0.4)");

	len += sprintf(buffer + len, "\nCard config: \n");
	dev = lastdev;
	while (dev)
	{
		len += sprintf(buffer + len, "(");
		len += sprintf(buffer + len, "%s", "Sound Blaster Live!");
		len += sprintf(buffer + len, " at 0x%lx", dev->hwaddr);
		len += sprintf(buffer + len, " irq %d", dev->hw_irq.irq);
		len += sprintf(buffer + len, ")");
		len += sprintf(buffer + len, "\n");
		dev = dev->prev;
	}

	len += sprintf(buffer + len, "\nAudio devices:\n");
	dev = lastdev;
	count = 0;
	while (dev) 
	{
		len += sprintf(buffer + len, "%d: %s%s\n", count++, "Sound Blaster Live!", " (DUPLEX)");
		dev = dev->prev;
	}

	len += sprintf(buffer + len, "\nSynth devices: \nNot supported by current driver\n");

	len += sprintf(buffer + len, "\nMidi devices: \n");
	dev = lastdev;
	count = 0;

	while (dev) 
	{
		len += sprintf(buffer + len, "%d: %s\n", count++, "Sound Blaster Live!");
		dev = dev->prev;
	}

	len += sprintf(buffer + len, "\nTimers:\n");
	len += sprintf(buffer + len, "0: %s\n", "System Clock");

	len += sprintf(buffer + len, "\nMixers:\n");
	dev = lastdev;
	count = 0;

	while (dev) 
	{
		len += sprintf(buffer + len, "%d: %s\n", count++, "Sound Blaster Live!");
		dev = dev->prev;
	}

	*start = buffer + (offset - begin);
	len -= (offset - begin);
	if (len > length) 
	  len = length;
	return len;
}


static int sound_open(struct inode *inode, struct file *file)
{
	int dev;

	if (is_unloading) 
	{
		DPF("Sound: Driver partially removed. Can't open device\n");
		return -EBUSY;
	}
	dev = MINOR(inode->i_rdev);
	if (!soundcard_configured && dev != SND_DEV_CTL && dev != SND_DEV_STATUS) 
	{
		DPF("SoundCard Error: The sound system has not been configured\n");
		return -ENXIO;
	}
	DPD("sound_open(dev=%d)\n", dev);
	if ((dev >= SND_NDEVS) || (dev < 0)) 
	{
		DPD("Invalid minor device %d\n", dev);
		return -ENXIO;
	}

	switch (dev & 0x0f) 
	{
	case SND_DEV_STATUS:
		break;
	default:
		DPD("Invalid minor device %d\n", dev);
		return -ENXIO;
	}
	in_use++;
	return 0;
}

static void sound_release(struct inode *inode, struct file *file)
{
	int dev = MINOR(inode->i_rdev);

	DPD("sound_release(dev=%d)\n", dev);
	switch (dev & 0x0f) 
	{
	case SND_DEV_STATUS:
		break;
	default:
		DPD("Sound error: Releasing unknown device 0x%02x\n", dev);
	}
	in_use--;
}


void sndstat_init(void)
{
	soundcard_configured = 1;
	sound_started = 1;
}

struct file_operations emu10k1_sndstat_fops = 
{
	NULL,
	sound_read,
	NULL,
	NULL,          /* sound_readdir */
	NULL,
	NULL,
	NULL,
	sound_open,
	sound_release
};
