/*
 * Purpose: Driver for Yamaha YMF7xx PCI audio controller.
 */
/*
 * 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 "ymf7xx_cfg.h"
#include "ymf7xx.h"
#include "oss_pci.h"
#include "ac97.h"
#include "uart401.h"

#define YAMAHA_VENDOR_ID	0x1073
#define YAMAHA_YMF724_ID	0x0004
#define YAMAHA_YMF724F_ID	0x000d
#define YAMAHA_YMF734_ID	0x0005
#define YAMAHA_YMF740_ID	0x000a
#define YAMAHA_YMF740C_ID	0x000c
#define YAMAHA_YMF744_ID	0x0010
#define YAMAHA_YMF754_ID	0x0012

#define WRITEB(a,d) devc->bRegister[a] = d
#define READB(a) devc->bRegister[a]
#define WRITEW(a,d) devc->wRegister[a>>1] = d
#define READW(a) devc->wRegister[a>>1]
#define WRITEL(a,d) devc->dwRegister[a>>2] = d
#define READL(a) (devc->dwRegister[a>>2])

#ifdef OSS_BIG_ENDIAN
static __inline__ unsigned int
ymf_swap (unsigned int x)
{
  return ((x & 0x000000ff) << 24) |
    ((x & 0x0000ff00) << 8) |
    ((x & 0x00ff0000) >> 8) | ((x & 0xff000000) >> 24);
}

#define LSWAP(x) ymf_swap(x)
#else
#define LSWAP(x) 	x
#endif

#define MAX_PORTC 8

extern int yamaha_mpu_ioaddr;
extern int yamaha_mpu_irq;
extern int yamaha_fm_ioaddr;


typedef struct ymf7xx_portc
{
  int speed, bits, channels;
  int open_mode;
  int trigger_bits;
  int audio_enabled;
  int audiodev;
  int devs_opened;
  int devnum;
  PLAY_BANK *bank1, *bank2, *bank3, *bank4;
  EFFECT_CNTRL_SLOT effectslot;
  REC_CNTRL_SLOT recslot;
  int dacfmt;
}
ymf7xx_portc;

typedef struct ymf7xx_devc
{
  oss_device_t *osdev;
  char *chip_name;
  int deviceid;
  unsigned int base0addr;
  unsigned int *base0virt;
  volatile unsigned int *dwRegister;
  volatile unsigned short *wRegister;
  volatile unsigned char *bRegister;
  int irq;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;

  /* Legacy */
  int mpu_base, mpu_irq;
  int fm_base;
  int fm_attached, mpu_attached;

  /* Mixer parameters */
  ac97_devc ac97devc, ac97devc2;
  int mixer_dev;
  int mixlevels[10];

  /* Audio parameters */
  int audio_initialized;
  ymf7xx_portc portc[MAX_PORTC];

  /* Play Table */
  volatile oss_native_word slt;
  oss_native_word slt_phys;
  volatile unsigned int *tab;
  oss_native_word play_table;
  oss_native_word play_table_virt;
  unsigned char *dmabuf1;

  /* Effect Table */
  volatile unsigned int *effecttab;
  oss_native_word effect_table;
  oss_native_word effect_table_virt, eff_buf_phys;
  unsigned char *dmabuf2, *eff_buf;

  /* Recording Table */
  volatile unsigned int *rectab;
  oss_native_word rec_table;
  oss_native_word rec_table_virt;
  unsigned char *dmabuf3;
  int spdif_in;
}
ymf7xx_devc;

int SetupPlaySlot (int dev, int slot);
int ymf7xx_spdif_control (int dev, int ctrl, unsigned int cmd, int value);

static int
ac97_read (void *devc_, int addr)
{
  ymf7xx_devc *devc = devc_;
  int count;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  WRITEW (AC97_CMD_ADDRESS, addr | 0x8000);

  for (count = 0; count < 1000; count++)
    if ((READW (AC97_STATUS_ADDRESS) >> 15) == 0)
      {
	MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
	return READW (AC97_STATUS_DATA);
      }
  DDB (cmn_err (CE_WARN, "AC97 mixer read timed out\n"));
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return -1;
}

static int
ac97_write (void *devc_, int addr, int data)
{
  ymf7xx_devc *devc = devc_;
  oss_native_word flags;
  int count;

  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  WRITEW (AC97_CMD_ADDRESS, addr);
  WRITEW (AC97_CMD_DATA, data);

  for (count = 0; count < 1000; count++)
    if ((READW (AC97_STATUS_ADDRESS) >> 15) == 0)
      {
	MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
	return 1;
      }
  DDB (cmn_err (CE_WARN, "AC97 mixer write timed out\n"));
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

static void
install_ucode (ymf7xx_devc * devc, int addr, unsigned int *src, int len)
{
  int i;

  for (i = 0; i < len; i++)
    {
      WRITEL (addr, *src);
      addr += 4;
      src++;
    }
}

static int
ymf7xxintr (oss_device_t * osdev)
{
  ymf7xx_devc *devc = (ymf7xx_devc *) osdev->devc;
  ymf7xx_portc *portc;
  dmap_t *dmapin, *dmapout;
  unsigned int status;
  int i, n;
  int currdac = 0;
  int curradc = 0;
  int serviced = 0;


  status = READL (STATUS);

  if ((status & 0x80000000))
    {
      serviced = 1;
      for (i = 0; i < MAX_PORTC; i++)
	{
	  portc = &devc->portc[i];

	  if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    {
	      dmapout = audio_engines[portc->audiodev]->dmap_out;
	      currdac = LSWAP (portc->bank1->PgStart);
	      currdac /= dmapout->fragment_size / portc->dacfmt;

	      if (currdac < 0 || currdac >= dmapout->nfrags)
		currdac = 0;
	      n = 0;
	      while (dmap_get_qhead (dmapout) != currdac
		     && n++ < dmapout->nfrags)
		oss_audio_outputintr (portc->audiodev, 1);
	    }
	  if (portc->trigger_bits & PCM_ENABLE_INPUT)
	    {
	      dmapin = audio_engines[portc->audiodev]->dmap_in;

	      if (devc->spdif_in)
		curradc = LSWAP (portc->recslot.bank1->PgStartAdr);
	      else
		curradc = LSWAP (portc->recslot.bank3->PgStartAdr);

	      curradc /= dmapin->fragment_size;

	      if (curradc < 0 || curradc >= dmapin->nfrags)
		curradc = 0;
	      n = 0;
	      while (dmap_get_qtail (dmapin) != curradc
		     && n++ < dmapin->nfrags)
		oss_audio_inputintr (portc->audiodev, 0);
	    }
	  WRITEL (STATUS, 0x80000000);
	  WRITEL (MODE, READL (MODE) | 0x00000002);
	}
    }

  return serviced;
}

/*
 *****************************************************************************
 */

static int
ymf7xx_audio_set_rate (int dev, int arg)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;

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

  if (arg > 48000)
    arg = 48000;
  if (arg < 5000)
    arg = 5000;
  portc->speed = arg;
  return portc->speed;
}

static short
ymf7xx_audio_set_channels (int dev, short arg)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;

  if ((arg != 1) && (arg != 2))
    return portc->channels;
  portc->channels = arg;

  return portc->channels;
}

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

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

  if (!(arg & (AFMT_U8 | AFMT_S16_LE | AFMT_AC3)))
    return portc->bits;
  portc->bits = arg;

  return portc->bits;
}

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

static void ymf7xx_audio_trigger (int dev, int state);

static void
ymf7xx_audio_reset (int dev)
{
  ymf7xx_audio_trigger (dev, 0);
}

static void
ymf7xx_audio_reset_input (int dev)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  ymf7xx_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
ymf7xx_audio_reset_output (int dev)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  ymf7xx_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
ymf7xx_audio_open (int dev, int mode, int open_flags)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  ymf7xx_devc *devc = audio_engines[dev]->devc;
  oss_native_word flags;

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

  portc->open_mode = mode;
  portc->audio_enabled = ~mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return 0;
}

static void
ymf7xx_audio_close (int dev, int mode)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;

  ymf7xx_audio_reset (dev);
  portc->open_mode = 0;
  portc->audio_enabled &= ~mode;
}

/*ARGSUSED*/
static void
ymf7xx_audio_output_block (int dev, oss_native_word buf, int count,
			   int fragsize, int intrflag)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;

  portc->audio_enabled |= PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
}

/*ARGSUSED*/
static void
ymf7xx_audio_start_input (int dev, oss_native_word buf, int count,
			  int fragsize, int intrflag)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;

  portc->audio_enabled |= PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
}

static void
ymf7xx_audio_trigger (int dev, int state)
{
  ymf7xx_devc *devc = audio_engines[dev]->devc;
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->open_mode & OPEN_WRITE)
    {
      if (state & PCM_ENABLE_OUTPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->devs_opened = SetupPlaySlot (dev, portc->devnum);

	      WRITEL (CONTROL_SELECT, 1);
	      WRITEL (MODE, READL (MODE) | 0x00000003);
	      portc->trigger_bits |= PCM_ENABLE_OUTPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_OUTPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;

	      devc->tab[portc->devs_opened] = 0;
	      if (portc->channels > 1)
		devc->tab[portc->devs_opened - 1] = 0;
	    }
	}
    }

  if ((portc->open_mode & OPEN_READ)
      && !(audio_engines[dev]->flags & ADEV_NOINPUT))
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      /* 0x01 = REC SLOT, 0x02 = ADC SLOT */
	      if (devc->spdif_in)
		WRITEL (MAP_OF_REC, 0x1);
	      else
		WRITEL (MAP_OF_REC, 0x2);
	      WRITEL (CONTROL_SELECT, 0);
	      WRITEL (MODE, READL (MODE) | 0x00000003);
	      portc->trigger_bits |= PCM_ENABLE_INPUT;
	    }
	}
      else
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      (portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      portc->audio_enabled &= ~PCM_ENABLE_INPUT;
	      portc->trigger_bits &= ~PCM_ENABLE_INPUT;

	      /* set the map of  rec 0 */
	      WRITEL (MAP_OF_REC, 0x0);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static unsigned int
SetFormatField (ymf7xx_portc * portc)
{
  unsigned int val;

  val = 0x00000000;
  if (portc->bits == 8)
    val |= 0x80000000;
  if (portc->channels > 1)
    val |= 0x00010000;
  return val;
}

static unsigned int
SetPgDeltaField (ymf7xx_portc * portc)
{
  unsigned int val;

#if 0
  double x;
  x = (double) portc->speed / 48000.0;
  val = (int) (x * (1 << 28)) & 0x7FFFFF00;
#else
  oss_native_word x;
  x = (portc->speed * (1 << 15) + 187) / 375;
  val = (x * (1 << 6)) & 0x7FFFFF00;
#endif
  return val;
}

static unsigned int
SetLpfKField (ymf7xx_portc * portc)
{
  unsigned int i, val = 0, sr = portc->speed;
  int freq[8] = { 100, 2000, 8000, 11025, 16000, 22050, 32000, 48000 };
  int LpfK[8] =
    { 0x0057, 0x06aa, 0x18B2, 0x2093, 0x2b9a, 0x35a1, 0x3eaa, 0x4000 };

  if (sr == 44100)
    {
      val = 0x4646 << 16;
    }
  else
    {
      for (i = 0; i < 8; i++)
	{
	  if (sr <= freq[i])
	    {
	      val = LpfK[i] << 16;
	      break;
	    }
	}
    }
  return val;
}

static unsigned int
SetLpfQField (ymf7xx_portc * portc)
{
  unsigned int i, val = 0, sr = portc->speed;
  int freq[8] = { 100, 2000, 8000, 11025, 16000, 22050, 32000, 48000 };
  int LpfQ[8] =
    { 0x3528, 0x34a7, 0x3202, 0x3177, 0x3139, 0x31c9, 0x33d0, 0x4000 };

  if (sr == 44100)
    {
      val = 0x370A << 16;
    }
  else
    {
      for (i = 0; i < 8; i++)
	{
	  if (sr <= freq[i])
	    {
	      val = LpfQ[i] << 16;
	      break;
	    }
	}
    }
  return val;
}

void
SetupRecSlot (int dev)
{
  ymf7xx_devc *devc = audio_engines[dev]->devc;
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  int banksize;

  banksize = READL (REC_CNTRL_SIZE);
  portc->recslot.base = (devc->rec_table_virt);

  /* banks 1 and 2 are for REC Slot and 3 and 4 are for ADC Slot */
  if (devc->spdif_in)
    {
      portc->recslot.bank1 =
	(REC_BANK *) (portc->recslot.base + (banksize * 0));
      portc->recslot.bank2 =
	(REC_BANK *) (portc->recslot.base + (banksize * 4));

      WRITEB (REC_FORMAT,
	      (unsigned char) ((portc->channels & 0x02) +
			       ((portc->bits & 0x08) >> 3)));
      WRITEW (REC_SAMPLING_RATE,
	      (unsigned short) (48000 * 4096 / portc->speed) - 1);

      portc->recslot.bank1->PgBase = portc->recslot.bank2->PgBase = LSWAP (dmap->dmabuf_phys);	/* use the REC slot */

      portc->recslot.bank1->PgLoopEndAdr = portc->recslot.bank2->PgLoopEndAdr = LSWAP (dmap->bytes_in_use);	/* use the REC slot */
    }
  else
    {
      portc->recslot.bank3 =
	(REC_BANK *) (portc->recslot.base + (banksize * 8));
      portc->recslot.bank4 =
	(REC_BANK *) (portc->recslot.base + (banksize * 12));

      WRITEB (ADC_FORMAT,
	      (unsigned char) ((portc->channels & 0x02) +
			       ((portc->bits & 0x08) >> 3)));
      WRITEW (ADC_SAMPLING_RATE,
	      (unsigned short) (48000 * 4096 / portc->speed) - 1);

      portc->recslot.bank3->PgBase = portc->recslot.bank4->PgBase = LSWAP (dmap->dmabuf_phys);	/* use the ADC slot */

      portc->recslot.bank3->PgLoopEndAdr = portc->recslot.bank4->PgLoopEndAdr = LSWAP (dmap->bytes_in_use);	/* use the ADC slot */
    }

}

/*ARGSUSED*/
static int
ymf7xx_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  ymf7xx_devc *devc = audio_engines[dev]->devc;
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  SetupRecSlot (dev);

  /* set the input level to max and go! */
  WRITEL (NATIVE_REC_INPUT, 0xffffffff);
  WRITEL (NATIVE_ADC_INPUT, 0xffffffff);
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

void
SetupEffectSlot (int dev)
{
  ymf7xx_devc *devc = audio_engines[dev]->devc;
  ymf7xx_portc *portc = audio_engines[dev]->portc;

  int banksize;

  banksize = READL (EFF_CNTRL_SIZE);
  portc->effectslot.base = (devc->effect_table_virt);

  /* slots 1-5 each having 2 banks */
  portc->effectslot.bank1 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 0));
  portc->effectslot.bank2 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 4));
  portc->effectslot.bank3 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 8));
  portc->effectslot.bank4 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 12));
  portc->effectslot.bank5 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 16));
  portc->effectslot.bank6 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 20));
  portc->effectslot.bank7 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 24));
  portc->effectslot.bank8 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 28));
  portc->effectslot.bank9 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 32));
  portc->effectslot.bank10 =
    (EFFECT_BANK *) (portc->effectslot.base + (banksize * 36));

#if 0
/* Dry Left Channel */
  portc->effectslot.bank1->PgBase = portc->effectslot.bank2->PgBase =
    LSWAP (devc->eff_buf_phys + 0 * 8192);
  portc->effectslot.bank1->PgLoopEnd = portc->effectslot.bank2->PgLoopEnd =
    LSWAP (4096);
  portc->effectslot.bank1->PgStart = portc->effectslot.bank2->PgStart = 0;

/* Dry Right Channel */
  portc->effectslot.bank3->PgBase = portc->effectslot.bank4->PgBase =
    LSWAP (devc->eff_buf_phys + 1 * 8192);
  portc->effectslot.bank3->PgLoopEnd = portc->effectslot.bank4->PgLoopEnd =
    LSWAP (4096);
  portc->effectslot.bank3->PgStart = portc->effectslot.bank4->PgStart = 0;

/* Effect 1 */
  portc->effectslot.bank5->PgBase = portc->effectslot.bank6->PgBase =
    LSWAP (devc->eff_buf_phys + 2 * 8192);
  portc->effectslot.bank5->PgLoopEnd = portc->effectslot.bank6->PgLoopEnd =
    LSWAP (4096);
  portc->effectslot.bank5->PgStart = portc->effectslot.bank6->PgStart = 0;

#endif

/* Effect 2 */
  portc->effectslot.bank7->PgBase = portc->effectslot.bank8->PgBase =
    LSWAP (devc->eff_buf_phys + 0 * 8192);
  portc->effectslot.bank7->PgLoopEnd = portc->effectslot.bank8->PgLoopEnd =
    LSWAP (4096);
  portc->effectslot.bank7->PgStart = portc->effectslot.bank8->PgStart = 0;

/* Effect 3 */
  portc->effectslot.bank9->PgBase = portc->effectslot.bank10->PgBase =
    LSWAP (devc->eff_buf_phys + 1 * 8192);
  portc->effectslot.bank9->PgLoopEnd = portc->effectslot.bank10->PgLoopEnd =
    LSWAP (4096);
  portc->effectslot.bank9->PgStart = portc->effectslot.bank10->PgStart = 0;

  WRITEL (AC97_SEC_CONFIG, (READL (AC97_SEC_CONFIG) & ~0x0030) | 0x0010);
  WRITEL (MAP_OF_EFFECTS, 0x18);	/* effect 2,3 */
  WRITEL (MODE, READL (MODE) | (1 << 30));	/* AC3 Setup */
}


void
SetupPlayBank (int dev, int slot, oss_native_word sbase)
{
  ymf7xx_devc *devc = audio_engines[dev]->devc;
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;

  PLAY_BANK *bank1;
  PLAY_BANK *bank2;
  bank1 = (PLAY_BANK *) sbase;
  bank2 =
    (PLAY_BANK *) (sbase + READL (PLAY_CNTRL_SIZE) * sizeof (unsigned int));

  memset ((void *) bank1, 0, sizeof (PLAY_BANK));
  memset ((void *) bank2, 0, sizeof (PLAY_BANK));

  if (slot == 2)
    {
      portc->bank3 = bank1;
      portc->bank4 = bank2;
    }
  else
    {
      portc->bank1 = bank1;
      portc->bank2 = bank2;
    }

  /* setup format field */
  bank1->Format = LSWAP (SetFormatField (portc));
  bank1->EgGain = bank1->EgGainEnd = LSWAP (0x40000000);

  if (portc->channels == 1)
    {
      /* Gain */
      bank1->LchGain = bank1->LchGainEnd = LSWAP (0x40000000);
      bank1->RchGain = bank1->RchGainEnd = LSWAP (0x40000000);
      bank1->Effect2Gain = bank1->Effect2GainEnd = LSWAP (0x40000000);
      bank1->Effect3Gain = bank1->Effect3GainEnd = LSWAP (0x40000000);
    }
  else
    {
      if (slot == 2)
	{
	  bank1->Format = LSWAP (LSWAP (bank1->Format) + 1);
	  bank1->RchGain = bank1->RchGainEnd = LSWAP (0x40000000);
	  bank1->Effect2Gain = bank1->Effect2GainEnd = LSWAP (0x40000000);
	}
      else
	{
	  bank1->LchGain = bank1->LchGainEnd = LSWAP (0x40000000);
	  bank1->Effect3Gain = bank1->Effect3GainEnd = LSWAP (0x40000000);
	}
    }

  bank1->LoopDefault = 0;
  bank1->NumOfFrames = 0;
  bank1->LoopCount = 0;
  bank1->PgStart = 0;
  bank1->PgLoop = 0;

  /* PgBase */
  bank1->PgBase = LSWAP (dmap->dmabuf_phys);

  /* PgLoopEnd */
  bank1->PgLoopEnd = LSWAP (dmap->bytes_in_use / portc->dacfmt);

  /* PgDelta & PgDeltaEnd */
  bank1->PgDelta = bank1->PgDeltaEnd = LSWAP (SetPgDeltaField (portc));

  if (portc->channels == 4)
    bank1->PgDelta = bank1->PgDeltaEnd *= 2;

  /* LpfK & LpfKEnd */
  bank1->LpfK = bank1->LpfKEnd = LSWAP (SetLpfKField (portc));

  /* LpfQ */
  bank1->LpfQ = LSWAP (SetLpfQField (portc));

  memcpy (bank2, bank1, sizeof (PLAY_BANK));
}

int
SetupPlaySlot (int dev, int slot)
{
  int tmp;
  oss_native_word sltbase;
  oss_native_word physbase;

  ymf7xx_devc *devc = audio_engines[dev]->devc;
  ymf7xx_portc *portc = audio_engines[dev]->portc;

  tmp = slot * 2 + 1;
  sltbase = (oss_native_word) devc->slt + (tmp * 0x120);
  physbase = devc->slt_phys + (tmp * 0x120);
  SetupPlayBank (dev, 1, sltbase);
  devc->tab[tmp] = LSWAP (physbase);

  if (portc->channels > 1)
    {
      sltbase += 0x120;
      physbase += 0x120;
      SetupPlayBank (dev, 2, sltbase);
      tmp++;
      devc->tab[tmp] = LSWAP (physbase);
    }
  return tmp;
}

/*ARGSUSED*/
static int
ymf7xx_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  ymf7xx_devc *devc = audio_engines[dev]->devc;
  ymf7xx_portc *portc = audio_engines[dev]->portc;
  oss_native_word flags;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if (portc->bits == AFMT_AC3)
    {
      portc->channels = 2;
      portc->bits = 16;
      ymf7xx_spdif_control (devc->mixer_dev, 1, SNDCTL_MIX_WRITE, 1);	/* enable SPDIF */
      ymf7xx_spdif_control (devc->mixer_dev, 4, SNDCTL_MIX_WRITE, 1);	/* enable AC3 */
    }
  else
    ymf7xx_spdif_control (devc->mixer_dev, 4, SNDCTL_MIX_WRITE, 0);	/* enable AC3 */


  portc->dacfmt = portc->channels * (portc->bits / 8);

  if (portc->dacfmt > 4)
    portc->dacfmt = 4;
  /* portc->devs_opened = SetupPlaySlot (dev, portc->devnum); -> trigger */
  /* set effects slot */
  SetupEffectSlot (dev);

  if (portc->bits != AFMT_AC3)
    WRITEL (NATIVE_DAC, 0xFFFFFFFF);
  else
    WRITEL (NATIVE_DAC, 0x00000000);

  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static int
ymf7xx_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  ymf7xx_portc *portc = audio_engines[dev]->portc;

  unsigned int ptr = 0;

  if (direction == PCM_ENABLE_OUTPUT)
    {
      ptr = LSWAP (portc->bank1->PgStart);
      ptr *= portc->dacfmt;
    }
  if (direction == PCM_ENABLE_INPUT)
    {
      ptr = LSWAP (portc->recslot.bank3->PgStartAdr);
    }
  return ptr;
}

static audiodrv_t ymf7xx_audio_driver = {
  ymf7xx_audio_open,
  ymf7xx_audio_close,
  ymf7xx_audio_output_block,
  ymf7xx_audio_start_input,
  ymf7xx_audio_ioctl,
  ymf7xx_audio_prepare_for_input,
  ymf7xx_audio_prepare_for_output,
  ymf7xx_audio_reset,
  NULL,
  NULL,
  ymf7xx_audio_reset_input,
  ymf7xx_audio_reset_output,
  ymf7xx_audio_trigger,
  ymf7xx_audio_set_rate,
  ymf7xx_audio_set_format,
  ymf7xx_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* ymf7xx_alloc_buffer */
  NULL,				/* ymf7xx_free_buffer */
  NULL,
  NULL,
  ymf7xx_get_buffer_pointer
};

#if 0
static void
init_audio (ymf7xx_devc * devc)
{
  devc->audio_initialized = 1;
}

uninit_audio (ymf7xx_devc * devc)
{
  devc->audio_initialized = 0;
  WRITEL (CONFIG, 0);
  WRITEL (MODE, 0);
}
#endif

#ifdef OBSOLETED_STUFF
/*
 * This device has "ISA style" MIDI and FM subsystems. Such devices don't
 * use PCI config space for the I/O ports and interrupts. Instead the driver
 * needs to allocate proper resources itself. This functionality is no longer
 * possible. For this reason the MIDI and FM parts are not accessible.
 */
static void
attach_fm (ymf7xx_devc * devc)
{
  devc->fm_attached = 0;
  if (!opl3_detect (0x388, devc->osdev))
    {
      cmn_err (CE_WARN, "OPL3 not detected\n");
      return;
    }
  opl3_init (0x388, devc->osdev);
  devc->fm_attached = 1;
}


static void
attach_mpu (ymf7xx_devc * devc)
{
  struct address_info hw_config;

  hw_config.io_base = devc->mpu_base;
  hw_config.irq = devc->mpu_irq;
  hw_config.dma = -1;
  hw_config.dma2 = -1;
  hw_config.always_detect = 0;
  hw_config.name = "Yamaha DS-XG MPU401";
  hw_config.driver_use_1 = 0;
  hw_config.driver_use_2 = 0;
  hw_config.osdev = devc->osdev;
#ifdef CREATE_OSP
  CREATE_OSP (hw_config.osdev);
#endif
  hw_config.card_subtype = 0;

  if (!probe_uart401 (&hw_config))
    {
      cmn_err (CE_WARN, "MPU-401 was not detected\n");
      return;
    }
  DDB (cmn_err (CE_WARN, "MPU-401 detected - Good\n"));
  devc->mpu_attached = 1;
  attach_uart401 (&hw_config);
}

static void
unload_mpu (ymf7xx_devc * devc)
{
  struct address_info hw_config;

  hw_config.io_base = devc->mpu_base;
  hw_config.irq = devc->mpu_irq;
  hw_config.dma = -1;
  hw_config.dma2 = -1;
  hw_config.always_detect = 0;
  hw_config.name = "Yahama DS-XG MPU401";
  hw_config.driver_use_1 = 0;
  hw_config.driver_use_2 = 0;
  hw_config.osdev = devc->osdev;
#ifdef CREATE_OSP
  CREATE_OSP (hw_config.osdev);
#endif
  hw_config.card_subtype = 0;

  devc->mpu_attached = 0;
  unload_uart401 (&hw_config);
}
#endif

int
ymf7xx_spdif_control (int dev, int ctrl, unsigned int cmd, int value)
{
  ymf7xx_devc *devc = mixer_devs[dev]->hw_devc;
/*  int left, right; */

  if (cmd == SNDCTL_MIX_READ)
    {
      value = 0;
      switch (ctrl)
	{
	case 1:		/* S/PDIF Enable/Disable */
	  value = READL (SPDIFOUT_CONTROL) & 0x1;
	  break;

	case 2:		/* S/PDIF Record Enable */
	  value = devc->spdif_in;
	  break;

	case 3:		/* S/PDIF Loopback */
	  value = READL (SPDIFIN_CONTROL) & (1 << 4);
	  break;

	case 4:		/* AC3 Output */
	  value = READL (SPDIFOUT_CONTROL) & (1 << 1);
	  break;
#if 0
	case 5:		/* CopyProtection Bit */
	  value = READL (SPDIFOUT_STATUS) & (1 << 2);
	  break;
	case 6:		/* SPDIF OUTVOL */
	  value = devc->mixlevels[0];
	  break;
	case 7:		/* SPDIF LOOPVOL */
	  value = devc->mixlevels[1];
	  break;
	case 8:		/* SPDIF AC3VOL */
	  value = devc->mixlevels[2];
	  break;
#endif
	}
    }
  if (cmd == SNDCTL_MIX_WRITE)
    {
      switch (ctrl)
	{
	case 1:		/* S/PDIF OUTPUT ENABLE/DISABLE */
	  if (value)
	    WRITEL (SPDIFOUT_CONTROL, READL (SPDIFOUT_CONTROL) | 0x1);
	  else
	    WRITEL (SPDIFOUT_CONTROL, READL (SPDIFOUT_CONTROL) & ~0x1);
	  break;

	case 2:		/* Record S/PDIF IN ENABLE DISABLE */
	  if (value)
	    {
	      WRITEL (SPDIFIN_CONTROL, READL (SPDIFIN_CONTROL) | 0x1);
	      devc->spdif_in = 1;
	    }
	  else
	    {
	      WRITEL (SPDIFIN_CONTROL, READL (SPDIFIN_CONTROL) & ~0x1);
	      devc->spdif_in = 0;
	    }
	  break;

	case 3:		/* S/PDIF Loopback Mode */
	  if (value)
	    WRITEL (SPDIFIN_CONTROL, READL (SPDIFIN_CONTROL) | (1 << 4));
	  else
	    WRITEL (SPDIFIN_CONTROL, READL (SPDIFIN_CONTROL) & ~(1 << 4));
	  break;

	case 4:		/* AC3 Output Mode */
	  if (value)
	    WRITEL (SPDIFOUT_CONTROL, READL (SPDIFOUT_CONTROL) | (1 << 1));
	  else
	    WRITEL (SPDIFOUT_CONTROL, READL (SPDIFOUT_CONTROL) & ~(1 << 1));
	  break;

#if 0
	case 5:		/* Copy Protect Mode */
	  {
	    int ac3_mode;

	    ac3_mode = READL (SPDIFOUT_CONTROL) & (1 << 1);

	    if (value)
	      {
		if (ac3_mode)
		  WRITEL (SPDIFOUT_STATUS,
			  READL (SPDIFOUT_STATUS) | (1 << 1));
		else
		  WRITEL (SPDIFOUT_STATUS, READL (SPDIFOUT_STATUS) & ~0x3E);
	      }
	    else
	      {
		if (ac3_mode)
		  WRITEL (SPDIFOUT_STATUS,
			  READL (SPDIFOUT_STATUS) | (3 << 1));
		else

		  WRITEL (SPDIFOUT_STATUS,
			  READL (SPDIFOUT_STATUS) | (1 << 2));
	      }
	  }
	  break;

	case 6:
	  left = value & 0xff;
	  right = (value >> 8) & 0xff;
	  if (left > 100)
	    left = 100;
	  if (right > 100)
	    right = 100;
	  value = left | (right << 8);
	  left = left * 0xFFFF / 100;
	  right = right * 0xFFFF / 100;
	  WRITEL (SPDIFOUTVOL, left << 16 | right);
	  devc->mixlevels[0] = value;
	  break;
	case 7:
	  left = value & 0xff;
	  right = (value >> 8) & 0xff;
	  if (left > 100)
	    left = 100;
	  if (right > 100)
	    right = 100;
	  value = left | (right << 8);
	  left = left * 0xFFFF / 100;
	  right = right * 0xFFFF / 100;
	  WRITEL (SPDIFLOOPVOL, left << 16 | right);
	  devc->mixlevels[1] = value;
	  break;
	case 8:
	  left = value & 0xff;
	  right = (value >> 8) & 0xff;
	  if (left > 100)
	    left = 100;
	  if (right > 100)
	    right = 100;
	  value = left | (right << 8);
	  left = left * 0xFFFF / 100;
	  right = right * 0xFFFF / 100;
	  WRITEL (AC3_OUTPUT, left << 16 | right);
	  devc->mixlevels[2] = value;
	  break;
#endif
	}
    }
  return value;
}

static int
ymf7xx_mix_init (int dev)
{
  int group, err;

  if ((group = mixer_ext_create_group (dev, 0, "SPDIF")) < 0)
    return group;

  if ((err = mixer_ext_create_control (dev, group, 1, ymf7xx_spdif_control,
				       MIXT_ONOFF, "ENABLE", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 2, ymf7xx_spdif_control,
				       MIXT_ONOFF, "RECORD", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 3, ymf7xx_spdif_control,
				       MIXT_ONOFF, "LOOP", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

#if 0
  if ((err = mixer_ext_create_control (dev, group, 4, ymf7xx_spdif_control,
				       MIXT_ONOFF, "AC3", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  if ((err = mixer_ext_create_control (dev, group, 5, ymf7xx_spdif_control,
				       MIXT_ONOFF, "COPYPROT", 1,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;

  if ((err = mixer_ext_create_control (dev, group, 6, ymf7xx_spdif_control,
				       MIXT_STEREOSLIDER, "OUTVOL", 100,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  if ((err = mixer_ext_create_control (dev, group, 7, ymf7xx_spdif_control,
				       MIXT_STEREOSLIDER, "LOOPVOL", 100,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
  if ((err = mixer_ext_create_control (dev, group, 8, ymf7xx_spdif_control,
				       MIXT_STEREOSLIDER, "AC3VOL", 100,
				       MIXF_READABLE | MIXF_WRITEABLE)) < 0)
    return err;
#endif

  return 0;
}

static int
init_ymf7xx (ymf7xx_devc * devc)
{

  int my_mixer, i;
  int first_dev = 0;

  WRITEL (NATIVE_DAC, 0x00000000);
  WRITEL (MODE, 0x00010000);
  WRITEL (MODE, 0x00000000);
  WRITEL (MAP_OF_REC, 0x00000000);
  WRITEL (MAP_OF_EFFECTS, 0x00000000);
  WRITEL (PLAY_CNTRL_BASE, 0x00000000);
  WRITEL (REC_CNTRL_BASE, 0x00000000);
  WRITEL (EFF_CNTRL_BASE, 0x00000000);
  WRITEL (CONTROL_SELECT, 1);
  WRITEL (GLOBAL_CONTROL, READL (GLOBAL_CONTROL) & ~0x0007);
  WRITEL (ZVOUTVOL, 0xFFFFFFFF);
  WRITEL (ZVLOOPVOL, 0xFFFFFFFF);
  WRITEL (SPDIFOUTVOL, 0xFFFFFFFF);
  WRITEL (SPDIFLOOPVOL, 0x3FFF3FFF);
  devc->spdif_in = 0;

  if (ac97_read (devc, 0x02) == 0xFFFF)
    {
      for (i = 0; i < 100; i++)
	if (ac97_read (devc, 0x02) != 0xFFFF)
	  {
	    break;
	  }
    }

  install_ucode (devc, 0x1000, dsp, dsp_size);
  switch (devc->deviceid)
    {
    case YAMAHA_YMF724F_ID:
    case YAMAHA_YMF740C_ID:
    case YAMAHA_YMF744_ID:
    case YAMAHA_YMF754_ID:
      install_ucode (devc, 0x4000, cntrl1E, cntrl1E_size);
      break;
    default:
      install_ucode (devc, 0x4000, cntrl, cntrl_size);
    }
  WRITEL (CONFIG, 1);

/* add an extra reset to init the mixers */
  ac97_write (devc, 0x02, 0x0000);
  ac97_write (devc, 0x18, 0x0808);

/* Now check to see if the DSP is started or not */
  {
    int fEnd;
    i = 10000;
    do
      {
	if (i-- <= 0)
	  {
	    cmn_err (CE_WARN, "CTR/DSP Init/ TimeOut\n");
	    return 0;
	  }
	fEnd = READL (CONTROL_SELECT) & 0x1;
      }
    while (fEnd == 0x1);
  }

  my_mixer =
    ac97_install (&devc->ac97devc, "Yamaha DS-XG", ac97_read, ac97_write,
		  devc, devc->osdev);
  if (my_mixer >= 0)
    {
      devc->mixer_dev = my_mixer;
      mixer_ext_set_init_fn (my_mixer, ymf7xx_mix_init, 20);
    }
  else
    return 0;

#ifdef OBSOLETED_STUFF
  if (devc->fm_base > 0)
    attach_fm (devc);
  if (devc->mpu_base > 0)
    attach_mpu (devc);
#endif

  if (devc->play_table_virt == 0)
    {
      /* Allocate the Play Table */
      oss_native_word phaddr;
      devc->dmabuf1 =
	CONTIG_MALLOC (devc->osdev, 0x1000, MEMLIMIT_32BITS, &phaddr);
      devc->play_table_virt = (oss_native_word) devc->dmabuf1;
      devc->play_table = phaddr;

      /* Allocate Effect Table */
      devc->dmabuf2 =
	CONTIG_MALLOC (devc->osdev, 1024, MEMLIMIT_32BITS, &phaddr);
      devc->effect_table_virt = (oss_native_word) devc->dmabuf2;
      devc->effect_table = phaddr;

      /* Allocate Effect Scratch Buffer */
      devc->eff_buf =
	CONTIG_MALLOC (devc->osdev, 2 * 8192, MEMLIMIT_32BITS, &phaddr);
      devc->eff_buf_phys = phaddr;

      /* Allocate the Record Table */
      devc->dmabuf3 =
	CONTIG_MALLOC (devc->osdev, 1024, MEMLIMIT_32BITS, &phaddr);
      devc->rec_table_virt = (oss_native_word) devc->dmabuf3;
      devc->rec_table = phaddr;

      /* Setup Play Table */
      devc->tab = (unsigned int *) devc->play_table_virt;
      devc->slt = (oss_native_word) (devc->play_table_virt + 0x100);
      devc->slt_phys = (devc->play_table + 0x100);
      memset ((void *) devc->tab, 0, 0x1000);
      WRITEL (PLAY_CNTRL_BASE, (unsigned int) devc->play_table);
      devc->tab[0] = LSWAP (20);	/* setup 20 slots for 8 playback devices */
      /* Setup Effect Table and init Effect Slots */
      devc->effecttab = (unsigned int *) devc->effect_table_virt;
      memset ((void *) devc->effecttab, 0, 1024);
      WRITEL (EFF_CNTRL_BASE, (unsigned int) devc->effect_table);

      //SetupEffectSlot (dev);

      /* Setup Record Table */
      devc->rectab = (unsigned int *) devc->rec_table_virt;
      memset ((void *) devc->rectab, 0, 1024);
      WRITEL (REC_CNTRL_BASE, (unsigned int) devc->rec_table);
    }

  for (i = 0; i < MAX_PORTC; i++)
    {
      int adev;
      char tmp_name[100];
      ymf7xx_portc *portc = &devc->portc[i];
      int caps = ADEV_AUTOMODE | ADEV_NOVIRTUAL;

      if (i == 0)
	{
	  strcpy (tmp_name, devc->chip_name);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  strcpy (tmp_name, devc->chip_name);
	  caps |= ADEV_NOINPUT;
	  if (i > 1)
	    caps |= ADEV_SHADOW;
	}


      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&ymf7xx_audio_driver,
					sizeof (audiodrv_t),
					caps,
					AFMT_U8 | AFMT_S16_LE | AFMT_AC3,
					NULL, -1)) < 0)
	{
	  adev = -1;
	  return 0;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->devc = devc;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->min_block = 64;
	  audio_engines[adev]->min_rate = 5000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->vmix_flags |= VMIX_MULTIFRAG;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  audio_engines[adev]->min_channels = 2;
	  audio_engines[adev]->max_channels = 4;
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->devnum = i;
	}
    }
  return 1;
}

int
ymf7xx_attach (oss_device_t * osdev)
{


  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned char pci_reset;
  unsigned short pci_legacy;
  ymf7xx_devc *devc;

  DDB (cmn_err (CE_WARN, "Entered DS-XG attach routine\n"));

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

  if (((vendor != YAMAHA_VENDOR_ID)) ||
      (device != YAMAHA_YMF740_ID &&
       device != YAMAHA_YMF744_ID &&
       device != YAMAHA_YMF754_ID &&
       device != YAMAHA_YMF740C_ID &&
       device != YAMAHA_YMF724_ID &&
       device != YAMAHA_YMF734_ID && device != YAMAHA_YMF724F_ID))
    return 0;

  oss_pci_byteswap (osdev, 1);

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

  devc->osdev = osdev;
  osdev->devc = devc;

  pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision);
  pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
  pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line);
  pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &devc->base0addr);

  if (devc->base0addr == 0)
    {
      cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n");
      return 0;
    }

  if (pci_irq_line == 0)
    {
      cmn_err (CE_WARN, "IRQ not assigned by BIOS (%d).\n", pci_irq_line);
      return 0;
    }

  DDB (cmn_err
       (CE_WARN, "rev %x I/O base %04x\n", pci_revision, devc->base0addr));

  devc->deviceid = device;
  switch (device)
    {
    case YAMAHA_YMF724_ID:
      devc->chip_name = "Yamaha YMF724";
      break;

    case YAMAHA_YMF724F_ID:
      devc->chip_name = "Yamaha YMF724F";
      break;

    case YAMAHA_YMF734_ID:
      devc->chip_name = "Yamaha YMF734";
      break;

    case YAMAHA_YMF740_ID:
      devc->chip_name = "Yamaha YMF740";
      break;

    case YAMAHA_YMF740C_ID:
      devc->chip_name = "Yamaha YMF740C";
      break;

    case YAMAHA_YMF744_ID:
      devc->chip_name = "Yamaha YMF744";
      break;

    case YAMAHA_YMF754_ID:
      devc->chip_name = "Yamaha YMF754";
      break;

    default:
      devc->chip_name = "Yamaha DS-XG";
    }

  devc->fm_base = yamaha_fm_ioaddr;
  devc->mpu_base = yamaha_mpu_ioaddr;
  devc->mpu_irq = yamaha_mpu_irq;

  /* reset the device */
  pci_read_config_byte (osdev, 0x48, &pci_reset);

  if (pci_reset & 0x03)
    {
      pci_write_config_byte (osdev, 0x48, pci_reset & 0xFC);
      pci_write_config_byte (osdev, 0x48, pci_reset | 0x03);
      pci_write_config_byte (osdev, 0x48, pci_reset & 0xFC);
    }

/*  Legacy I/O setup - setup MPU and FM io/irq values */
  devc->fm_attached = 0;
  devc->mpu_attached = 0;
  /*pcipci_legacy = 0x0020|0x00C0|0x0004; // 10 bit address decode and Joystick */
  pci_legacy = 0x4;
  if (devc->fm_base)
    pci_legacy |= 0x0002;	/* FM enable */

  if (devc->mpu_base)
    {
      pci_legacy |= 0x0008;	/* MPU I/O enable */

      switch (devc->mpu_irq)
	{
	case 5:
	  pci_legacy |= 0x0010;
	  break;
	case 7:
	  pci_legacy |= 0x0810;
	  break;
	case 9:
	  pci_legacy |= 0x1010;
	  break;
	case 10:
	  pci_legacy |= 0x1810;
	  break;
	case 11:
	  pci_legacy |= 0x2010;
	  break;
	default:
	  devc->mpu_irq = 0;
	}
    }

  pci_write_config_word (osdev, 0x40, pci_legacy);

  pci_legacy = 0x0000;

  switch (devc->fm_base)
    {
    case 0x388:
      pci_legacy |= 0x0000;
      break;
    case 0x398:
      pci_legacy |= 0x0001;
      break;
    case 0x3a0:
      pci_legacy |= 0x0002;
      break;
    case 0x3a8:
      pci_legacy |= 0x0003;
      break;
    default:
      devc->fm_base = 0;
    }

  switch (devc->mpu_base)
    {
    case 0x330:
      pci_legacy |= 0x0000;
      break;
    case 0x300:
      pci_legacy |= 0x0010;
      break;
    case 0x332:
      pci_legacy |= 0x0020;
      break;
    case 0x334:
      pci_legacy |= 0x0020;
      break;
    default:
      devc->mpu_base = 0;
    }
  pci_write_config_word (osdev, 0x42, pci_legacy);

  /* activate the device */
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  /* Map the shared memory area */
  devc->base0virt =
    (unsigned int *) MAP_PCI_MEM (devc->osdev, 0, devc->base0addr, 32 * 1024);
  devc->dwRegister = (unsigned int *) devc->base0virt;
  devc->wRegister = (unsigned short *) devc->base0virt;
  devc->bRegister = (unsigned char *) devc->base0virt;


  MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV);
  MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1);

  oss_register_device (osdev, devc->chip_name);

  if (oss_register_interrupts (devc->osdev, 0, ymf7xxintr, NULL) < 0)
    {
      cmn_err (CE_WARN, "Can't allocate IRQ%d\n", pci_irq_line);
      return 0;
    }

  return init_ymf7xx (devc);	/* Detected */
}


int
ymf7xx_detach (oss_device_t * osdev)
{
  ymf7xx_devc *devc = (ymf7xx_devc *) osdev->devc;

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

  WRITEL (CONFIG, 0);
  WRITEL (MODE, 0);

  if (devc->dmabuf1)
    {
      CONTIG_FREE (devc->osdev, devc->dmabuf1, 0x1000);
      devc->dmabuf1 = 0;
    }

  if (devc->dmabuf2)
    {
      CONTIG_FREE (devc->osdev, devc->dmabuf2, 1024);
      devc->dmabuf2 = 0;
    }

  if (devc->eff_buf)
    {
      CONTIG_FREE (devc->osdev, devc->eff_buf, 16 * 1024);
      devc->eff_buf = 0;
    }

  if (devc->dmabuf3)
    {
      CONTIG_FREE (devc->osdev, devc->dmabuf3, 1024);
      devc->dmabuf3 = 0;
    }

  devc->play_table_virt = 0;
  devc->effect_table_virt = 0;
  devc->rec_table_virt = 0;

#ifdef OBSOLETED_STUFF
  if (devc->mpu_attached)
    {
      unload_mpu (devc);
      devc->mpu_attached = 0;
    }
#endif

  oss_unregister_interrupts (devc->osdev);

  MUTEX_CLEANUP (devc->mutex);
  MUTEX_CLEANUP (devc->low_mutex);

  if (devc->base0addr != 0)
    {
      UNMAP_PCI_MEM (devc->osdev, 0, devc->base0addr, devc->base0virt,
		     32 * 1024);
      devc->base0addr = 0;
    }
  oss_unregister_device (devc->osdev);
  return 1;
}
