/*
 * Purpose: Driver for Sound Blaster X-Fi (emu20k)
 *
 */

/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2008.
 *
 * This software is released under the BSD license.
 * See the COPYING file included in the main directory of this source
 * distribution for the license terms and conditions
 */


#include "sbxfi_cfg.h"
#include <oss_pci.h>
#include "sbxfi.h"
#include "20k1reg.h"
#include "hwaccess.h"

#define PCI_VENDOR_CREATIVE	0x1102
#define 	CREATIVE_SBXFI	0x0005

#define TIMER_INTERVAL	5	/* In milliseconds */

#define DEFAULT_PLAY_RATE	96000	/* Default rate of play devices */
#define DEFAULT_REC_RATE	96000	/* Default rate of rec devices */
#define HARDWARE_RATE	96000	/* Internal rate used by the hardware */

static void
set_interval_timer(sbxfi_devc_t *devc, int msecs)
{
  int tic = (HARDWARE_RATE*msecs)/1000;

  HwWrite20K1 (devc, TIMR, tic | TIMR_IE|TIMR_IP);
}

static int
sbxfi_intr(oss_device_t *osdev)
{
  unsigned int status;
  sbxfi_devc_t *devc = osdev->devc;

  status = HwRead20K1 (devc, GIP);

  if (status==0) /* Not for me */
     return 0;

  devc->interrupt_count++;

#if 0
  // Not using loop interrupts.
  if (status & SRC_INT)	/* SRC interrupt(s) pending */
  {
	  unsigned int srcipm, srcip;
	  int i;

  	  srcipm = HwRead20K1 (devc, SRCIPM); /* SRC interrupt pending map register */

	  for (i=0;i<7;i++)
	  if (srcipm & (1<<i))
	  {
		int j;
  	  	srcip = HwRead20K1 (devc, SRCIP(i)); /* SRC interrupt pending register for block(i) */

		for (j=0;j<32;j++)
		if (srcip & (1<<j))
		{
			int chn=i*32+j;
			sbxfi_portc_t *portc;

			portc=devc->src_to_portc[chn];

			if (portc==NULL)
			{
				cmn_err(CE_NOTE, "portc==NULL\n");
				continue;
			}

			oss_audio_outputintr(portc->dev, 0);
		}

  		HwWrite20K1 (devc, SRCIP(i), srcip);	/* Acknowledge SRC interrupts for block(i) */
	  }
  }
#endif

  if (status & IT_INT)
  {
  /*
   * Interval timer interrupt
   */
	sbxfi_portc_t *portc;
	int i;

	for (i=0;i<devc->nr_outdevs;i++)
	{
		portc=&devc->play_portc[i];
		if (portc->running)
	   	   oss_audio_outputintr(portc->dev, 0);
	}

	for (i=0;i<devc->nr_indevs;i++)
	{
		portc=&devc->rec_portc[i];
		if (portc->running)
	   	   oss_audio_inputintr(portc->dev, 0);
	}

	set_interval_timer(devc, TIMER_INTERVAL); /* Rearm interval timer */
  }

  HwWrite20K1 (devc, GIP, status & FI_INT);	/* Acknowledge interrupts */
  return 1;
}

 /*ARGSUSED*/ static int
sbxfi_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static mixer_driver_t sbxfi_mixer_driver = {
  sbxfi_mixer_ioctl
};

static int
sbxfi_set_rate (int dev, int arg)
{
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
#if 0
  // TODO: Implement support for other rates

  if (arg == 0)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return portc->rate;
    }

#else
  if (portc->direction == PCM_ENABLE_OUTPUT)
     arg=DEFAULT_PLAY_RATE;
  else
     arg=DEFAULT_REC_RATE;
#endif
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return portc->rate = arg;
}

static short
sbxfi_set_channels (int dev, short arg)
{
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;

  if (arg == 0)
    return portc->channels;

  if (arg<2)
     arg=2;
  else
     if (arg > MAX_PLAY_CHANNELS)
	arg = MAX_PLAY_CHANNELS;
  arg &= ~1; /* Even number of channels */

  return portc->channels = arg;
}

static unsigned int
sbxfi_set_format (int dev, unsigned int arg)
{
  sbxfi_portc_t *portc = audio_engines[dev]->portc;

  if (arg == 0)
    return portc->fmt;

  return portc->fmt = SUPPORTED_FORMAT;
}

static int
sbxfi_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  //sbxfi_portc_t *portc = audio_engines[dev]->portc;
  //sbxfi_devc_t *devc = audio_engines[dev]->devc;

  return -EINVAL;
}

static void sbxfi_trigger (int dev, int state);

static void
sbxfi_reset (int dev)
{
  sbxfi_trigger (dev, 0);
}

 /*ARGSUSED*/ static int
sbxfi_open_input (int dev, int mode, int open_flags)
{
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  adev_p adev = audio_engines[dev];
  oss_native_word flags;

  if (mode & OPEN_WRITE)
    {
      cmn_err (CE_CONT, "Playback is not possible with %s\n", adev->devnode);
      return -ENOTSUP;
    }

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return -EBUSY;
    }
  portc->open_mode = mode;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

 /*ARGSUSED*/ static int
sbxfi_open_output (int dev, int mode, int open_flags)
{
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;
  adev_p adev = audio_engines[dev];

  if (mode == OPEN_READ)
    {
      cmn_err (CE_CONT, "Recording is not possible with %s\n", adev->devnode);
      return -ENOTSUP;
    }

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return -EBUSY;
    }

  portc->open_mode = mode;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
sbxfi_close (int dev, int mode)
{
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  portc->open_mode = 0;

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

 /*ARGSUSED*/ static void
sbxfi_output_block (int dev, oss_native_word buf, int count, int fragsize,
		    int intrflag)
{
}

 /*ARGSUSED*/ static void
sbxfi_start_input (int dev, oss_native_word buf, int count, int fragsize,
		   int intrflag)
{
}

static void
sbxfi_trigger (int dev, int state)
{
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->state_bits == state)	/* No change */
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return;
    }
  portc->state_bits = state;

  if (portc->direction == PCM_ENABLE_OUTPUT)
  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
  		SetupAndStartPlaySRC (devc, portc);
		portc->running=1;
	}
      else
	{
  		StopPlaySRC (devc, portc);
		portc->running=0;
	}
    }

  if (portc->direction == PCM_ENABLE_INPUT)
  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
  		SetupAndStartRecordSRC (devc, portc);
		portc->running=1;
	}
      else
	{
  		StopRecordSRC (devc, portc);
		portc->running=0;
	}
    }

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

 /*ARGSUSED*/ static int
sbxfi_prepare_for_input (int dev, int bsize, int bcount)
{
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_in;
  oss_native_word flags;
  int i;

  if (audio_engines[dev]->flags & ADEV_NOINPUT)
    return -EACCES;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  // Fill record buffer to the page table entries.
  // This need to sync up with setting SRCs start addx
  for (i=0;i<(bsize*bcount)/4096;i++)
     devc->pdwPageTable[portc->pgtable_index+i] = dmap->dmabuf_phys + (i*4096);

  InitADC (devc, portc->dwDAChan[0], FALSE);
 
  // Program input mapper
  SetupRecordInputMapper (devc, portc);
  
  // Program I2S
  SetupRecordFormat (devc);
  SetupRecordMixer (devc, portc);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

 /*ARGSUSED*/ static int
sbxfi_prepare_for_output (int dev, int bsize, int bcount)
{
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  dmap_p dmap = audio_engines[dev]->dmap_out;
  oss_native_word flags;
  int i;

  if (audio_engines[dev]->flags & ADEV_NOOUTPUT)
    return -EACCES;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  // Fill play buffer to the page table entries.
  // This need to sync up with setting SRCs start addx
  for (i=0;i<(bsize*bcount)/4096;i++)
     devc->pdwPageTable[portc->pgtable_index+i] = dmap->dmabuf_phys + (i*4096);

  InitDAC (devc, portc);
  
  // Program I2S
  SetupPlayFormat (devc, portc);
  SetupPlayMixer (devc, portc);
 
  // Program input mapper
  SetupPlayInputMapper (devc, portc);

  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

/*ARGSUSED*/
static int
sbxfi_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  sbxfi_devc_t *devc = audio_engines[dev]->devc;
  sbxfi_portc_t *portc = audio_engines[dev]->portc;
  int pos;
  
  pos = HwRead20K1 (devc, SRCCA(portc->SrcChan)) & 0x03ffffff;

  if (pos>=128)
     pos -= 128; /* The pointer is always 128 bytes ahead */

  pos -= portc->pgtable_index*4096;

  return pos;
}

static audiodrv_t sbxfi_output_driver = {
  sbxfi_open_output,
  sbxfi_close,
  sbxfi_output_block,
  sbxfi_start_input,
  sbxfi_ioctl,
  sbxfi_prepare_for_input,
  sbxfi_prepare_for_output,
  sbxfi_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  sbxfi_trigger,
  sbxfi_set_rate,
  sbxfi_set_format,
  sbxfi_set_channels,
  NULL,
  NULL,
  NULL,				/* check input */
  NULL,				/* sbxfi_check_output */
  NULL,				/* sbxfi_alloc_buffer */
  NULL,				/* sbxfi_free_buffer */
  NULL,
  NULL,
  sbxfi_get_buffer_pointer
};

static audiodrv_t sbxfi_input_driver = {
  sbxfi_open_input,
  sbxfi_close,
  sbxfi_output_block,
  sbxfi_start_input,
  sbxfi_ioctl,
  sbxfi_prepare_for_input,
  sbxfi_prepare_for_output,
  sbxfi_reset,
  NULL,
  NULL,
  NULL,
  NULL,
  sbxfi_trigger,
  sbxfi_set_rate,
  sbxfi_set_format,
  sbxfi_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* sbxfi_alloc_buffer */
  NULL,				/* sbxfi_free_buffer */
  NULL,
  NULL,
  sbxfi_get_buffer_pointer
};

static void
init_play_device (sbxfi_devc_t * devc,
		  char *name, int dev_flags)
{
  int opts, dev, formats;
  char tmp[80];
  sbxfi_portc_t *portc = NULL;
  int i;
  adev_p adev;

  sprintf (tmp, "%s %s", devc->name, name);

  if (devc->nr_outdevs > MAX_OUTPUTDEVS)
    {
      cmn_err (CE_CONT, "Too many audio devices\n");
      return;
    }

  opts = ADEV_AUTOMODE | ADEV_NOINPUT;

  formats = SUPPORTED_FORMAT;

  if ((dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
				   devc->osdev,
				   devc->osdev,
				   tmp,
				   &sbxfi_output_driver,
				   sizeof (audiodrv_t),
				   opts, formats, devc, -1)) < 0)
    {
      return;
    }

  if (devc->first_dev == -1)
    {
      devc->first_dev = dev;
    }
  adev = audio_engines[dev];

  portc = &devc->play_portc[devc->nr_outdevs];

  adev->portc = portc;
  adev->devc = devc;
  adev->mixer_dev = devc->mixer_dev;
  adev->rate_source = devc->first_dev;
  adev->min_rate = 48000;
  adev->max_rate = 92600;
  adev->min_block=4096;
  //adev->vmix_flags |= VMIX_NOMAINVOL;

  portc->dev = dev;
  portc->open_mode = 0;
  portc->fmt = SUPPORTED_FORMAT;
  portc->dev_flags = dev_flags;
  portc->state_bits = 0;
  portc->direction = PCM_ENABLE_OUTPUT;

  portc->rate = DEFAULT_PLAY_RATE;

  // use the following SRC channels for Play
  portc->SrcChan = devc->next_src;
  devc->next_src += MAX_PLAY_CHANNELS;
  devc->src_to_portc[portc->SrcChan]=portc;

  portc->dwDAChan[0] = I2SA_L;
  portc->dwDAChan[1] = I2SA_R;
#if MAX_PLAY_CHANNELS>2
  portc->dwDAChan[2] = I2SB_L;
  portc->dwDAChan[3] = I2SB_R;
  portc->dwDAChan[4] = I2SC_L;
  portc->dwDAChan[5] = I2SC_R;
#endif

  portc->vol_left=portc->vol_right=MIXER_VOLSTEPS;

  adev->min_channels = 2;
  adev->max_channels = MAX_PLAY_CHANNELS;

  portc->pgtable_index = devc->next_pg;
  devc->next_pg += 128/4;	// Up to 128k for buffer

  devc->nr_outdevs++;
}

static void
init_rec_device (sbxfi_devc_t * devc,
		  char *name, int dev_flags)
{
  int opts, dev, formats;
  char tmp[80];
  sbxfi_portc_t *portc = NULL;
  int i;
  adev_p adev;

  sprintf (tmp, "%s %s", devc->name, name);

  if (devc->nr_indevs > MAX_INPUTDEVS)
    {
      cmn_err (CE_CONT, "Too many audio devices\n");
      return;
    }

  opts = ADEV_AUTOMODE | ADEV_NOOUTPUT;

  formats = SUPPORTED_FORMAT;

  if ((dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
				   devc->osdev,
				   devc->osdev,
				   tmp,
				   &sbxfi_input_driver,
				   sizeof (audiodrv_t),
				   opts, formats, devc, -1)) < 0)
    {
      return;
    }

  if (devc->first_dev == -1)
    {
      devc->first_dev = dev;
    }
  adev = audio_engines[dev];

  portc = &devc->rec_portc[devc->nr_indevs];

  adev->portc = portc;
  adev->devc = devc;
  adev->mixer_dev = devc->mixer_dev;
  adev->rate_source = devc->first_dev;
  adev->min_rate = 48000;
  adev->max_rate = 96000;
  adev->min_block=4096;

  portc->dev = dev;
  portc->open_mode = 0;
  portc->fmt = SUPPORTED_FORMAT;
  portc->dev_flags = dev_flags;
  portc->state_bits = 0;
  portc->direction = PCM_ENABLE_INPUT;

  portc->rate = DEFAULT_REC_RATE;

  // use the following SRC channels for record
  portc->SrcChan = devc->next_src;
  devc->next_src += 2;
  devc->src_to_portc[portc->SrcChan]=portc;

  portc->dwDAChan[0] = ADC_SRC_LINEIN;

  portc->vol_left=portc->vol_right=MIXER_VOLSTEPS;

  adev->min_channels = 2;
  adev->max_channels = 2;

  portc->pgtable_index = devc->next_pg;
  devc->next_pg += 128/4;	// Up to 128k for buffer

  devc->nr_indevs++;
}

static int
sbxfi_set_playvol (int dev, int ctrl, unsigned int cmd, int value)
{
  sbxfi_devc_t *devc = mixer_devs[dev]->devc;
  sbxfi_portc_t *portc;
  int left, right;

  if (ctrl<0 || ctrl >= devc->nr_outdevs)
     return -ENXIO;
  portc = &devc->play_portc[ctrl];

  if (cmd == SNDCTL_MIX_READ)
     {
	     return portc->vol_left | (portc->vol_right << 16);
     }

  if (cmd == SNDCTL_MIX_WRITE)
     {
	     left = value & 0xffff;
	     right = (value>>16) & 0xffff;

	     if (left > MIXER_VOLSTEPS)
		left=MIXER_VOLSTEPS;
	     if (right > MIXER_VOLSTEPS)
		right=MIXER_VOLSTEPS;

	     portc->vol_left=left;
	     portc->vol_right=right;
	     if (portc->running)
		SetupPlayMixer(devc, portc);

	     return portc->vol_left | (portc->vol_right << 16);
     }

  return -EINVAL;
}

static int
sbxfi_set_recvol (int dev, int ctrl, unsigned int cmd, int value)
{
  sbxfi_devc_t *devc = mixer_devs[dev]->devc;
  sbxfi_portc_t *portc;
  int left, right;

  if (ctrl<0 || ctrl >= devc->nr_indevs)
     return -ENXIO;
  portc = &devc->rec_portc[ctrl];

  if (cmd == SNDCTL_MIX_READ)
     {
	     return portc->vol_left | (portc->vol_right << 16);
     }

  if (cmd == SNDCTL_MIX_WRITE)
     {
	     left = value & 0xffff;
	     right = (value>>16) & 0xffff;

	     if (left > MIXER_VOLSTEPS)
		left=MIXER_VOLSTEPS;
	     if (right > MIXER_VOLSTEPS)
		right=MIXER_VOLSTEPS;

	     portc->vol_left=left;
	     portc->vol_right=right;
	     if (portc->running)
		SetupRecordMixer(devc, portc);

	     return portc->vol_left | (portc->vol_right << 16);
     }

  return -EINVAL;
}

static int
sbxfi_set_recsrc (int dev, int ctrl, unsigned int cmd, int value)
{
  sbxfi_devc_t *devc = mixer_devs[dev]->devc;
  sbxfi_portc_t *portc;

  if (ctrl<0 || ctrl >= devc->nr_indevs)
     return -ENXIO;
  portc = &devc->rec_portc[ctrl];

  if (cmd == SNDCTL_MIX_READ)
     {
	     return portc->dwDAChan[0];
     }

  if (cmd == SNDCTL_MIX_WRITE)
     {
	     if (value<0 || value>ADC_SRC_NONE)
	        return portc->dwDAChan[0];

	     return portc->dwDAChan[0]=value;
     }

  return -EINVAL;
}

static int
sbxfi_mix_init (int dev)
{
      int root=0, ctl;

      if ((ctl = mixer_ext_create_control (dev, root,
					   0, sbxfi_set_playvol,
					   MIXT_STEREOSLIDER16,
					   "play", MIXER_VOLSTEPS,
					   MIXF_READABLE |
					   MIXF_WRITEABLE | MIXF_CENTIBEL)) <
	  0)
	return ctl;

      if ((ctl = mixer_ext_create_control (dev, root,
					   0, sbxfi_set_recvol,
					   MIXT_STEREOSLIDER16,
					   "rec", MIXER_VOLSTEPS,
					   MIXF_READABLE |
					   MIXF_WRITEABLE | MIXF_CENTIBEL)) <
	  0)
	return ctl;

      if ((ctl = mixer_ext_create_control (dev, root,
					   0, sbxfi_set_recsrc,
					   MIXT_ENUM,
					   "recsrc", 5,
					   MIXF_READABLE |
					   MIXF_WRITEABLE | MIXF_CENTIBEL)) <
	  0)
	return ctl;

      mixer_ext_set_strings (dev, ctl, "mic line video aux none", 0);

      return 0;
}

int
sbxfi_attach (oss_device_t * osdev)
{
  unsigned short pci_command, vendor, device, revision;
  unsigned short subvendor, subdevice;

  sbxfi_devc_t *devc;
  sbxfi_portc_t *portc;

  if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL)
    {
      cmn_err (CE_WARN, "Out of memory\n");
      return 0;
    }

  memset (devc, 0, sizeof (*devc));

  portc = &devc->play_portc[0];

  devc->osdev = osdev;
  osdev->devc = devc;
  devc->name = "Sound Blaster X-Fi";

  pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
  pci_read_config_word (osdev, PCI_DEVICE_ID, &device);

  DDB (cmn_err
       (CE_CONT, "sbxfi_attach(Vendor %x, device %x)\n", vendor, device));

  if (vendor != PCI_VENDOR_CREATIVE || device != CREATIVE_SBXFI)
    {
      cmn_err (CE_WARN, "Hardware not recognized (vendor=%x, dev=%x)\n",
	       vendor, device);
      return 0;
    }
  MUTEX_INIT (osdev, devc->mutex, MH_DRV);
  MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1);

  pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &subvendor);
  pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &subdevice);
  pci_read_config_word (osdev, PCI_REVISION_ID, &revision);

  devc->wVendorID = vendor;
  devc->wDeviceID = device;
  devc->wSubsystemVendorID = subvendor;
  devc->wSubsystemID = subdevice;
  devc->wChipRevision = revision;

  switch (subdevice)
    {
    case 0x0021:		/* SB0460 */
    case 0x0023:
    case 0x0024:
    case 0x0025:
    case 0x0026:
    case 0x0027:
    case 0x0028:
    case 0x002a:
    case 0x002b:
    case 0x002c:
    case 0x002d:
    case 0x002e:
    case 0x0032:
    case 0x0033:
      /*
       * Original X-Fi hardware revision (SB046x/067x/076x)
       */
      devc->name = "Sound Blaster X-Fi (SB046x/067x/076x)";
      devc->hw_family = HW_ORIG;
      break;

    case 0x0029:
    case 0x0031:
      devc->name = "Sound Blaster X-Fi (SB073x)";
      devc->hw_family = HW_073x;
      break;

    case 0x0022:
    case 0x002f:
      devc->name = "Sound Blaster X-Fi (SB055x)";
      devc->hw_family = HW_055x;
      break;

    default:
      if (subdevice >= 0x6000 && subdevice <= 0x6fff)	/* "Vista compatible" HW */
	{
	  devc->name = "Sound Blaster X-Fi (UAA)";
	  devc->hw_family = HW_UAA;
	}
    }

  oss_register_device (osdev, devc->name);

  pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  if ((osdev->hw_info = PMALLOC (osdev, 200)) != NULL)
    {
      sprintf (osdev->hw_info, "PCI device %04x:%04x, subdevice %04x:%04x\n",
	       vendor, device, subvendor, subdevice);
    }

  devc->interrupt_count=0;
  if (oss_register_interrupts (devc->osdev, 0, sbxfi_intr, NULL) < 0)
    {
      cmn_err (CE_WARN, "Unable to install interrupt handler\n");
      return 0;
    }

  // Detect and Configure X-Fi PCI config space.
  // Obtain the resource configuration from PCI config space.
  if (!DetectAndConfigureHardware (devc))
    {
      cmn_err (CE_WARN, "Cannot configure X-Fi hardware...\n");
      return 0;
    }

  if (IsVistaCompatibleHardware (devc))
    {
      // Switch to audio core to X-Fi core.
      SwitchToXFiCore (devc);
    }

  // Initialize hardware.  This include setup the PLL etc.
  if (InitHardware (devc) != CTSTATUS_SUCCESS)
    {
      cmn_err (CE_WARN, "Init Hardware failed...\n");
      return 0;
    }

  devc->dwPageTableSize = 1024; /* For up to 4M of memory */
  devc->pdwPageTable = CONTIG_MALLOC (devc->osdev,
				      devc->dwPageTableSize,
				      MEMLIMIT_32BITS, &devc->dwPTBPhysAddx);

  HwWrite20K1 (devc, PTPALX, devc->dwPTBPhysAddx);
  HwWrite20K1 (devc, PTPAHX, 0);

  HwWrite20K1 (devc, TRNCTL, 0x13);
  HwWrite20K1 (devc, TRNIS, 0x200c01);

  HwWrite20K1 (devc, GIE, FI_INT);	/* Enable "forced" interrupts */
  HwWrite20K1 (devc, GIP, FI_INT);	/* Trigger forced interrupt */

  oss_udelay(1000);
  if (devc->interrupt_count==0)
     cmn_err(CE_WARN, "Interrupts don't seem to be working.\n");

  set_interval_timer(devc, TIMER_INTERVAL);

/*
 * Disable FI and enable selected global interrupts
 * (SRC, Interval Timer).
 */
  HwWrite20K1 (devc, GIE, SRC_INT | IT_INT);

  if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
					    devc->osdev,
					    devc->osdev,
					    devc->name,
					    &sbxfi_mixer_driver,
					    sizeof (mixer_driver_t),
					    devc)) >= 0)
    {
      mixer_devs[devc->mixer_dev]->devc = devc;
      mixer_devs[devc->mixer_dev]->hw_devc = devc;
      mixer_devs[devc->mixer_dev]->priority = 1;	/* Possible default mixer candidate */
      mixer_ext_set_init_fn (devc->mixer_dev, sbxfi_mix_init, 10);
    }

  devc->first_dev=-1; /* Not assigned */
  init_play_device (devc, "output", 0);
  init_rec_device (devc, "input", 0);
#if 0
  // Initialize ADC
  InitADC (devc, ADC_SRC_LINEIN, FALSE);
#endif

  return 1;
}

int
sbxfi_detach (oss_device_t * osdev)
{
  sbxfi_devc_t *devc = (sbxfi_devc_t *) osdev->devc;
  sbxfi_portc_t *portc = &devc->play_portc[0];

  if (oss_disable_device (osdev) < 0)
    return 0;

  HwWrite20K1 (devc, GIE, 0);	/* Disable global interrupts */
  oss_unregister_interrupts (devc->osdev);

  HwWrite20K1 (devc, PTPALX, 0);
  if (devc->pdwPageTable != NULL)
    {
      CONTIG_FREE (devc->osdev, devc->pdwPageTable, devc->dwPageTableSize);
      devc->pdwPageTable = NULL;
    }
  oss_unregister_device (osdev);
  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);
  return 1;
}
