/*     
 **********************************************************************
 *     main.c - Creative EMU10K1 audio driver
 *     Copyright 1999, 2000 Creative Labs, Inc. 
 * 
 ********************************************************************** 
 * 
 *     Date                 Author          Summary of changes 
 *     ----                 ------          ------------------ 
 *     October 20, 1999     Bertrand Lee    base code release 
 *     November 2, 1999     Alan Cox        cleaned up stuff
 * 
 ********************************************************************** 
 * 
 *     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. 
 * 
 ********************************************************************** 
 *
 *      Supported devices:
 *      /dev/dsp:     Standard /dev/dsp device, OSS-compatible
 *      /dev/mixer:   Standard /dev/mixer device, OSS-compatible
 *      /dev/sndstat: Standard /dev/sndstat device, mostly OSS-compatible
 *      /dev/midi:    Raw MIDI UART device, mostly OSS-compatible
 *
 *      Revision history:
 *      0.1 beta Initial release
 *      0.2 Lowered initial mixer vol. Improved on stuttering wave playback. Added MIDI UART support.
 *      0.3 Fixed mixer routing bug, added APS, joystick support.
 *      0.4 Added rear-channel, SPDIF support. 
 *
 ********************************************************************** 
 */


/* These are only included once per module */
#ifdef MODULE
#include <linux/module.h>
#else

#endif

#include "hwaccess.h"
#include "mycommon.h"
#include "cardwo.h"

#ifndef PCI_VENDOR_ID_CREATIVE
#define PCI_VENDOR_ID_CREATIVE 0x1102
#endif

#ifndef PCI_DEVICE_ID_CREATIVE_EMU10K1
#define PCI_DEVICE_ID_CREATIVE_EMU10K1 0x0002
#endif

#define EMU10K1_EXTENT	0x20	/* 32 byte I/O space */
#define JOY_EXTENT	0x8	/* 8 byte I/O space */
#define MAX_EMU10K1	5

/* Global var instantiation */

struct sblive_hw *sblive_devs = NULL;

/* Function prototypes */
extern int audio_init(struct sblive_hw *sb_hw, s8 *pname);
extern int audio_exit(struct sblive_hw *sb_hw);
extern int sblive_mixer_init(struct sblive_hw *sb_hw);
extern void sndstat_init(void);
extern int midi_init(struct sblive_hw *sb_hw);
extern int midi_exit(struct sblive_hw *sb_hw);

/* Utility functions */
inline int copy_from_user(void *to, const void *from, unsigned long n)
{
	int i = verify_area(VERIFY_READ, from, n);
	if (i)
		return i;
	memcpy_fromfs(to, from, n);
	return 0;
}

inline int copy_to_user(void *to, const void *from, unsigned long n)
{
	int i = verify_area(VERIFY_WRITE, to, n);
	if (i)
		return i;
	memcpy_tofs(to, from, n);
	return 0;
}


/* Driver initialization routine */
#ifdef MODULE
int init_module(void)
#else
int __init init_emu10k1(void)
#endif
{
	struct sblive_hw *hw_obj;
	u32 hwflags = 0;
	unsigned int index = 0, serial = 0;
	unsigned long ioaddr;
	unsigned char bus, devfn, ucRevID = 0;
	struct sblive_config dsConfig;
	struct sblive_config *config = &dsConfig;
	unsigned long irq;

	DPF("init_module() called\n");

	/* Do not export symbols */
#ifdef MODULE
	register_symtab(NULL);
#endif /* MODULE */

	if (!pcibios_present())
	  return -ENODEV;

	/* PCI BIOS detected */
	for (index = 0; index < MAX_EMU10K1; index++) {
		int nResult;
		nResult = pcibios_find_device(PCI_VENDOR_ID_CREATIVE,
					  PCI_DEVICE_ID_CREATIVE_EMU10K1,
					      index,
					      &bus,
					      &devfn);
		if (nResult != PCIBIOS_SUCCESSFUL)
		  break;

		/* Device found - Query PCI BIOS info */
		DPD("Emu10k1 device %d found!\n", index);

		pcibios_read_config_dword(bus, devfn, PCI_BASE_ADDRESS_0, &ioaddr);
		if (ioaddr == 0 || (ioaddr & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_IO)
		  continue;
		
		ioaddr = ioaddr & PCI_BASE_ADDRESS_IO_MASK;
		DPD("IO Base Address = %#x\n", ioaddr);

		pcibios_read_config_byte(bus, devfn, PCI_INTERRUPT_LINE, &irq);
		if (irq == 0 || irq == 0xff)
		  continue;
		DPD("IRQ Line = %u\n", irq);

		pcibios_read_config_byte(bus, devfn, PCI_REVISION_ID, &ucRevID);
		DPD("Revision ID = %u\n", ucRevID);

		/* Kernel source bug - Should actually be VENDOR_ID */
		pcibios_read_config_dword(bus, devfn, PCI_SUBSYSTEM_ID, &serial);
		DPD("Serial No = %08x\n", serial);

		/* Initialize device struct */
		hw_obj = kmalloc(sizeof(*hw_obj), GFP_KERNEL);
		if (!hw_obj) 
		{
			printk(KERN_WARNING "emu10k1: out of memory\n");
			continue;
		}
#ifdef MEMTEST
		DPD("main.c: kmalloc: [%p]\n", hw_obj);
#endif
		memset(hw_obj, 0, sizeof(*hw_obj)); /* FIXME: use bzero()? */

		printk(KERN_INFO "Creative SBLive! at %#lx on irq %lu\n", ioaddr, irq);

#ifdef MEMTEST
		DPD("main.c: kmalloc: [%p]\n", config);
#endif
		memset(config, 0, sizeof(*config)); /* FIXME: use bzero()? */

		config->vendorid = PCI_VENDOR_ID_CREATIVE;
		config->serialno = serial;
		config->logdevid = PCI_DEVICE_ID_CREATIVE_EMU10K1;
		config->chiprev = ucRevID;

		config->nummemwindows = 0;

		config->numioports = 1;
		config->ioportbase[0] = ioaddr;
		config->ioportlenh[0] = EMU10K1_EXTENT;

		config->numirq = 1;
		config->IRQregs[0] = irq;
		config->irqattr[0] = 0;	/* Doesn't matter */

		config->numdma = 0;

		/* Reserve I/O region */
		if (check_region(ioaddr, EMU10K1_EXTENT)) 
		{
			printk(KERN_ERR "emu10k1: io ports %#lx-%#lx in use\n",
			       ioaddr, ioaddr + EMU10K1_EXTENT - 1);
			goto err_region1;
		}
		request_region(ioaddr, EMU10K1_EXTENT, "emu10k1");


		/* Register devices */
		hw_obj->audio_num = register_sound_dsp(&emu10k1_audio_fops);
		if (hw_obj->audio_num < 0) 
		{
			printk(KERN_ERR "emu10k1: cannot register audio device!\n");
			goto err_dev1;
		}
		
		hw_obj->mixer_num = register_sound_mixer(&emu10k1_mixer_fops);
		if (hw_obj->mixer_num < 0) 
		{
			printk(KERN_ERR "emu10k1: cannot register mixer device!\n");
			goto err_dev2;
		}

		hw_obj->midi_num = register_sound_midi(&emu10k1_midi_fops);
		if (hw_obj->midi_num < 0) 
		{
			printk(KERN_ERR "emu10k1: cannot register MIDI device!\n");
			goto err_dev4;
		}
		

		/* Register-level initialization goes here.... */
		DPF("Initializing hardware....\n");
		/* IRQ reservation happens in halInit */
		if (halInit(hw_obj, config, &hwflags) != CTSTATUS_SUCCESS) {
			printk(KERN_ERR "emu10k1: cannot initialize device!\n");
			goto err_hal_init;
		}
		hw_obj->open_sem = MUTEX;
		hw_obj->open_mode = 0;
		init_waitqueue(&hw_obj->open_wait);

		DPD("Hardware initialized. TRAM allocated: %u bytes\n", (unsigned int) hw_obj->tmemsize);


		audio_init(hw_obj, "SBLive!");
                sblive_mixer_init(hw_obj);

		midi_init(hw_obj);

		/* AOK, update global device list */
		hw_obj->next = sblive_devs;
		sblive_devs = hw_obj;

		hw_obj->prev = NULL;
		if (hw_obj->next)
		  hw_obj->next->prev = hw_obj;
		continue;

		/* Error handlers */
		/* FIXME: These must reflect the device init order... */
err_hal_init:
		halExit(hw_obj);	/* Should this be called? */

		unregister_sound_midi(hw_obj->midi_num);
err_dev4:
		unregister_sound_mixer(hw_obj->mixer_num);
err_dev2:
		unregister_sound_dsp(hw_obj->audio_num);
err_dev1:
		/* free_irq(hw_obj->irq, hw_obj); */
		/*  err_irq: */
		release_region(ioaddr, EMU10K1_EXTENT);
err_region1:
		kfree(hw_obj);
#ifdef MEMTEST
		DPD("main.c: kfree: [%p]\n", hw_obj);
#endif
	}
		
	if (!sblive_devs)
       	  return -ENODEV;

       	sblive_devs->stat_num = register_sound_special(&emu10k1_sndstat_fops, 6);
       	if (sblive_devs->stat_num < 0)
       	  printk(KERN_ERR "emu10k1: cannot register sndstat device!\n");
       	else
       	  sndstat_init();
		
       	DPF("Module successfully initialized!\n");
       	return 0;
}

#ifdef MODULE


/* Driver exit routine */
void cleanup_module(void)
{
	struct sblive_hw *hw_obj;

	DPF("cleanup_module() called\n");

	if (sblive_devs && sblive_devs->stat_num >= 0)
	  unregister_sound_special(6);

	while ((hw_obj = sblive_devs)) 
	{
		sblive_devs = sblive_devs->next;

		audio_exit(hw_obj);
		midi_exit(hw_obj);

		/* IRQ is freed in halExit */
		halExit(hw_obj);
		DPF("halExit completed\n");

		release_region(hw_obj->hwaddr, EMU10K1_EXTENT);
		unregister_sound_dsp(hw_obj->audio_num);
		unregister_sound_mixer(hw_obj->mixer_num);
		unregister_sound_midi(hw_obj->midi_num);
		kfree(hw_obj);
#ifdef MEMTEST
		DPD("main.c: kfree: [%p]\n", hw_obj);
#endif
	}

	DPF("Module unloaded\n");
}

#endif				// MODULE
