Google
 

Open Sound System
The Hitchhiker's Guide to OSS 4.1 Internals

Do you have problems with sound/audio application development? Don't panic! Click here for help!

oss_fmedia/oss_fmedia.c

Driver for FM801 FM801 PCI audio controller.

Description




This file is part of Open Sound System.

Copyright (C) 4Front Technologies 1996-2008.

This this source file is released under GPL v2 license (no other versions). See the COPYING file included in the main directory of this source distribution for the license terms and conditions.


#include "oss_fmedia_cfg.h"
#include "ac97.h"
#include "oss_pci.h"
#include "uart401.h"


#define FORTEMEDIA_VENDOR_ID	0x1319
#define FORTEMEDIA_FM801	0x0801

/* Register Definitions */
#define AC97_CMD		0x2A
#define AC97_DATA		0x2C
#define CODEC_CONTROL		0x22
#define PCM_VOL			0x00
#define FM_VOL			0x02
#define I2S_VOL			0x04
#define DIG_REC_SRC		0x06
#define PLAY_CONTROL		0x08
#define PLAY_SIZE		0x0A
#define PLAY_BUF1_ADDR		0x0C
#define PLAY_BUF2_ADDR		0x10
#define REC_CONTROL		0x14
#define REC_SIZE		0x16
#define REC_BUF1_ADDR		0x18
#define REC_BUF2_ADDR		0x1C
#define I2S_MODE		0x24
#define HW_VOL_CONTROL		0x26
#define I2S_CONTROL		0x29
#define MPU_DATA		0x30
#define MPU_COMMAND		0x31
#define GPIO_CONTROL		0x52
#define GENERAL_CONTROL		0x54
#define IRQ_MASK		0x56
#define IRQ_STATUS		0x5B

/* playback and record control register bits */
#define FM801_BUF1_LAST         (1<<1)
#define FM801_BUF2_LAST         (1<<2)
#define FM801_START             (1<<5)
#define FM801_PAUSE             (1<<6)
#define FM801_IMMED_STOP        (1<<7)

/* IRQ status bits */
#define IRQ_PLAY      		(1<<8)
#define IRQ_REC			(1<<9)
#define IRQ_HWVOL		(1<<14)
#define IRQ_MPU           	(1<<15)

#define MAX_PORTC 2

typedef struct fm801_portc
{
  int speed, bits, channels;
  int open_mode;
  int audio_enabled;
  int trigger_bits;
  int audiodev;
} fm801_portc;

typedef struct fm801_devc
{
  oss_device_t *osdev;
  oss_native_word base, mpu_base;
  int mpu_attached, fm_attached;
  int irq, mpu_irq;
  int irq_allocated;
  volatile unsigned char intr_mask;
  int model;
#define MDL_FM801AS		1
#define MDL_FM801AU		2
  char *chip_name;
  oss_mutex_t mutex;
  oss_mutex_t low_mutex;
  /* Audio parameters */
  int audio_initialized;
  int open_mode;
  fm801_portc portc[MAX_PORTC];
  int play_flag, play_count;
  int rec_flag, rec_count;
  /* Mixer parameters */
  ac97_devc ac97devc, ac97devc2;
  int mixer_dev, mixer2_dev;
} fm801_devc;

extern int fmedia_mpu_irq;

static struct
{
  unsigned int rate;
  unsigned int lower;
  unsigned int upper;
  unsigned char freq;
} rate_lookup[] =
{
  {
  5512, (0 + 5512) / 2, (5512 + 8000) / 2, 0x0},
  {
  8000, (5512 + 8000) / 2, (8000 + 9600) / 2, 0x1},
  {
  9600, (8000 + 9600) / 2, (9600 + 11025) / 2, 0x2},
  {
  11025, (9600 + 11025) / 2, (11025 + 16000) / 2, 0x3},
  {
  16000, (11025 + 16000) / 2, (16000 + 19200) / 2, 0x4},
  {
  19200, (16000 + 19200) / 2, (19200 + 22050) / 2, 0x5},
  {
  22050, (19200 + 22050) / 2, (22050 + 32000) / 2, 0x6},
  {
  32000, (22050 + 32000) / 2, (32000 + 38400) / 2, 0x7},
  {
  38400, (32000 + 38400) / 2, (38400 + 44100) / 2, 0x8},
  {
  44100, (38400 + 44100) / 2, (44100 + 48000) / 2, 0x9},
  {
  48000, (48000 + 44100) / 2, (48000 + 96000) / 2, 0xA}
};

static int
ac97_write (void *devc_, int index, int data)
{
  fm801_devc *devc = devc_;
  int idx;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

Wait until the codec interface is ready..

  for (idx = 0; idx < 10000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }
  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "AC97 busy\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }
  /* write data and address */
  OUTW (devc->osdev, data, devc->base + AC97_DATA);
  OUTW (devc->osdev, index | (0 << 10), devc->base + AC97_CMD);

Wait until the write command is completed..

  for (idx = 0; idx < 1000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }
  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "AC97 busy (1)\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

static int
ac97_read (void *devc_, int index)
{
  fm801_devc *devc = devc_;
  int idx;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

Wait until the codec interface is not ready..

  for (idx = 0; idx < 10000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }
  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "AC97 (read) not ready (1)\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }
  /* read command */
  OUTW (devc->osdev, index | (0 << 10) | (1 << 7), devc->base + AC97_CMD);
  for (idx = 0; idx < 10000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }

Wait until the codec interface is not ready..

  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "AC97 (read) not ready(2)\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }
  for (idx = 0; idx < 10000; idx++)
    {
      if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 8))
	break;
      oss_udelay (10);
    }
  if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 8)))
    {
      cmn_err (CE_WARN, "AC97 (read) data not valid (2)\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return INW (devc->osdev, devc->base + AC97_DATA);
}

static int
ac97_write2 (void *devc_, int index, int data)
{
  fm801_devc *devc = devc_;
  int idx;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

Wait until the codec interface is ready..

  for (idx = 0; idx < 10000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }
  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "Secondary AC97 busy\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }
  /* write data and address */
  OUTW (devc->osdev, data, devc->base + AC97_DATA);
  OUTW (devc->osdev, index | (0x1 << 10), devc->base + AC97_CMD);

Wait until the write command is completed..

  for (idx = 0; idx < 1000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }
  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "Secondary AC97 busy (1)\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return OSS_EIO;
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return 0;
}

static int
ac97_read2 (void *devc_, int index)
{
  fm801_devc *devc = devc_;
  int idx;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);

Wait until the codec interface is not ready..

  for (idx = 0; idx < 10000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }
  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "Secondary AC97 (read) not ready (1)\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }
  /* read command */
  OUTW (devc->osdev, index | (0x1 << 10) | (1 << 7), devc->base + AC97_CMD);
  for (idx = 0; idx < 10000; idx++)
    {
      if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9)))
	break;
      oss_udelay (10);
    }

Wait until the codec interface is not ready..

  if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 9))
    {
      DDB (cmn_err (CE_WARN, "Secondary AC97 (read) not ready(2)\n"));
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }
  for (idx = 0; idx < 10000; idx++)
    {
      if (INW (devc->osdev, devc->base + AC97_CMD) & (1 << 8))
	break;
      oss_udelay (10);
    }
  if (!(INW (devc->osdev, devc->base + AC97_CMD) & (1 << 8)))
    {
      cmn_err (CE_WARN, "Secondary AC97 (read) data not valid (2)\n");
      MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
      return 0;
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return INW (devc->osdev, devc->base + AC97_DATA);
}

static int
fm801intr (oss_device_t * osdev)
{
  fm801_devc *devc = (fm801_devc *) osdev->devc;
  unsigned int status;
  int i;
  int serviced = 0;
  status = INB (devc->osdev, devc->base + IRQ_STATUS);
  /* Playback interrupt */
  if (status & 0x01)
    {
      serviced = 1;
      for (i = 0; i < MAX_PORTC; i++)
	{
	  fm801_portc *portc = &devc->portc[i];
	  if (portc->trigger_bits & PCM_ENABLE_OUTPUT)
	    {
	      dmap_t *dmapout = audio_engines[portc->audiodev]->dmap_out;
	      devc->play_count++;
	      if (devc->play_count == dmapout->nfrags)
		devc->play_count = 0;
	      if (devc->play_flag)
		{
		  OUTL (devc->osdev, dmapout->dmabuf_phys +
			devc->play_count * dmapout->fragment_size,
			devc->base + PLAY_BUF1_ADDR);
		}
	      else
		{
		  OUTL (devc->osdev, dmapout->dmabuf_phys +
			devc->play_count * dmapout->fragment_size,
			devc->base + PLAY_BUF2_ADDR);
		}
	      devc->play_flag = !devc->play_flag;
	      oss_audio_outputintr (portc->audiodev, 1);
	    }
	}
      OUTB (devc->osdev, status | 0x01, devc->base + IRQ_STATUS);
    }
  /* Record Interrupt */
  if (status & 0x02)
    {
      serviced = 1;
      for (i = 0; i < MAX_PORTC; i++)
	{
	  fm801_portc *portc = &devc->portc[i];
	  if (portc->trigger_bits & PCM_ENABLE_INPUT)
	    {
	      dmap_t *dmapin = audio_engines[portc->audiodev]->dmap_in;
	      devc->rec_count++;
	      if (devc->rec_count == dmapin->nfrags)
		devc->rec_count = 0;
	      if (devc->rec_flag)
		{
		  OUTL (devc->osdev, dmapin->dmabuf_phys +
			devc->rec_count * dmapin->fragment_size,
			devc->base + REC_BUF1_ADDR);
		}
	      else
		OUTL (devc->osdev, dmapin->dmabuf_phys +
		      devc->rec_count * dmapin->fragment_size,
		      devc->base + REC_BUF2_ADDR);
	      devc->rec_flag = !devc->rec_flag;
	      oss_audio_inputintr (portc->audiodev, 0);
	    }
	}
      OUTB (devc->osdev, status | 0x02, devc->base + IRQ_STATUS);
    }
  /* MIDI Interrupt */
  if (status & 0x80)
    {
      serviced = 1;
      /* uart401intr (INT_HANDLER_CALL (devc->mpu_irq)); */
      OUTB (devc->osdev, status | 0x80, devc->base + IRQ_STATUS);
    }
  return serviced;
}

static int
fm801_audio_set_rate (int dev, int arg)
{
  fm801_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
fm801_audio_set_channels (int dev, short arg)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  if (arg>6)
     arg=6;
  if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6))
    return portc->channels;
  portc->channels = arg;
  return portc->channels;
}

static unsigned int
fm801_audio_set_format (int dev, unsigned int arg)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  if (arg == 0)
    return portc->bits;
  if (!(arg & (AFMT_U8 | AFMT_S16_LE)))
    return portc->bits;
  portc->bits = arg;
  return portc->bits;
}

/*ARGSUSED*/
static int
fm801_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
{
  return OSS_EINVAL;
}

static void fm801_audio_trigger (int dev, int state);

static void
fm801_audio_reset (int dev)
{
  fm801_audio_trigger (dev, 0);
}

static void
fm801_audio_reset_input (int dev)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  fm801_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
}

static void
fm801_audio_reset_output (int dev)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  fm801_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
}

/*ARGSUSED*/
static int
fm801_audio_open (int dev, int mode, int open_flags)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  fm801_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 OSS_EBUSY;
    }
  if (devc->open_mode & mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return OSS_EBUSY;
    }
  devc->open_mode |= mode;
  portc->open_mode = mode;
  portc->audio_enabled &= ~mode;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static void
fm801_audio_close (int dev, int mode)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  fm801_devc *devc = audio_engines[dev]->devc;
  fm801_audio_reset (dev);
  portc->open_mode = 0;
  devc->open_mode &= ~mode;
  portc->audio_enabled &= ~mode;
}

/*ARGSUSED*/
static void
fm801_audio_output_block (int dev, oss_native_word buf, int count,
			  int fragsize, int intrflag)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  portc->audio_enabled |= PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
}

/*ARGSUSED*/
static void
fm801_audio_start_input (int dev, oss_native_word buf, int count,
			 int fragsize, int intrflag)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  portc->audio_enabled |= PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
}

static void
fm801_audio_trigger (int dev, int state)
{
  fm801_portc *portc = audio_engines[dev]->portc;
  fm801_devc *devc = audio_engines[dev]->devc;
  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))
	    {
	      OUTW (devc->osdev,
		    INW (devc->osdev,
			 devc->base +
			 PLAY_CONTROL) | FM801_START | FM801_IMMED_STOP,
		    devc->base + PLAY_CONTROL);
	      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;
	      OUTW (devc->osdev,
		    (INW (devc->osdev, devc->base + PLAY_CONTROL) &
		     (~FM801_START | FM801_IMMED_STOP)) | (FM801_BUF1_LAST |
							   FM801_BUF2_LAST),
		    devc->base + PLAY_CONTROL);
	    }
	}
    }
  if (portc->open_mode & OPEN_READ)
    {
      if (state & PCM_ENABLE_INPUT)
	{
	  if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
	      !(portc->trigger_bits & PCM_ENABLE_INPUT))
	    {
	      OUTW (devc->osdev, INW (devc->osdev, devc->base + REC_CONTROL) |
		    FM801_START | FM801_IMMED_STOP, devc->base + REC_CONTROL);
	      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;
	      OUTW (devc->osdev,
		    (INW (devc->osdev, devc->base + REC_CONTROL) &
		     (~FM801_START | FM801_IMMED_STOP)) | (FM801_BUF1_LAST |
							   FM801_BUF2_LAST),
		    devc->base + REC_CONTROL);
	    }
	}
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
}

static unsigned char
sampling_rate (unsigned int rate)
{
  unsigned char freq = 1;
  int i;
  if (rate > 48000)
    rate = 48000;
  if (rate < 5512)
    rate = 5512;
  for (i = 0; i < sizeof (rate_lookup) / sizeof (rate_lookup[0]); i++)
    {
      if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper)
	{
	  rate = rate_lookup[i].rate;
	  freq = rate_lookup[i].freq;
	  break;
	}
    }
  return (freq);
}

/*ARGSUSED*/
static int
fm801_audio_prepare_for_input (int dev, int bsize, int bcount)
{
  fm801_devc *devc = audio_engines[dev]->devc;
  fm801_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_in;
  unsigned short value;
  unsigned char frequency;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  value = 0x0000;
  if (portc->channels == 2)
    value |= 0x8000;
  if (portc->bits == 16)
    value |= 0x4000;
  frequency = sampling_rate (portc->speed);
  value |= (frequency << 8);
  OUTW (devc->osdev, value, devc->base + REC_CONTROL);
  OUTW (devc->osdev, dmap->fragment_size - 1, devc->base + REC_SIZE);
  OUTL (devc->osdev, dmap->dmabuf_phys, devc->base + REC_BUF1_ADDR);
  OUTL (devc->osdev, dmap->dmabuf_phys + dmap->fragment_size,
	devc->base + REC_BUF2_ADDR);
  devc->rec_flag = 1;
  devc->rec_count = 1;
  portc->audio_enabled &= ~PCM_ENABLE_INPUT;
  portc->trigger_bits &= ~PCM_ENABLE_INPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

/*ARGSUSED*/
static int
fm801_audio_prepare_for_output (int dev, int bsize, int bcount)
{
  fm801_devc *devc = audio_engines[dev]->devc;
  fm801_portc *portc = audio_engines[dev]->portc;
  dmap_t *dmap = audio_engines[dev]->dmap_out;
  unsigned short value;
  unsigned char frequency;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  value = 0x0000;
  if (portc->channels > 1)
    value |= 0x8000;
  if (portc->bits == 16)
    value |= 0x4000;
  frequency = sampling_rate (portc->speed);
  value |= (frequency << 8);
  if (portc->channels == 4)
    value |= (1 << 12);		/* 4channel output */
  if (portc->channels == 6)
    value |= (1 << 13);		/* 6channel output */
  OUTW (devc->osdev, value, devc->base + PLAY_CONTROL);
  OUTW (devc->osdev, dmap->fragment_size - 1, devc->base + PLAY_SIZE);
  OUTL (devc->osdev, dmap->dmabuf_phys, devc->base + PLAY_BUF1_ADDR);
  OUTL (devc->osdev, dmap->dmabuf_phys + dmap->fragment_size,
	devc->base + PLAY_BUF2_ADDR);
  devc->play_flag = 1;
  devc->play_count = 1;
  portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
  portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
  return 0;
}

static int
fm801_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
{
  fm801_devc *devc = audio_engines[dev]->devc;
  int ptr = 0;
  oss_native_word flags;
  MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
  if (direction == PCM_ENABLE_OUTPUT)
    {
      if (devc->play_flag)
	ptr = INL (devc->osdev, devc->base + PLAY_BUF1_ADDR);
      else
	ptr = INL (devc->osdev, devc->base + PLAY_BUF2_ADDR);
    }
  if (direction == PCM_ENABLE_INPUT)
    {
      if (devc->rec_flag)
	ptr = INL (devc->osdev, devc->base + REC_BUF1_ADDR);
      else
	ptr = INL (devc->osdev, devc->base + REC_BUF2_ADDR);
    }
  MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
  return ptr % dmap->bytes_in_use;
}

static audiodrv_t fm801_audio_driver = {
  fm801_audio_open,
  fm801_audio_close,
  fm801_audio_output_block,
  fm801_audio_start_input,
  fm801_audio_ioctl,
  fm801_audio_prepare_for_input,
  fm801_audio_prepare_for_output,
  fm801_audio_reset,
  NULL,
  NULL,
  fm801_audio_reset_input,
  fm801_audio_reset_output,
  fm801_audio_trigger,
  fm801_audio_set_rate,
  fm801_audio_set_format,
  fm801_audio_set_channels,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,				/* fm801_alloc_buffer */
  NULL,				/* fm801_free_buffer */
  NULL,
  NULL,
  fm801_get_buffer_pointer
};

static void
init_audio (fm801_devc * devc)
{
  devc->audio_initialized = 1;
}

static void
uninit_audio (fm801_devc * devc)
{
  unsigned int irqmask;
  devc->audio_initialized = 0;
#ifndef _TRU64_UNIX
  /* interrupt setup - mask MPU, PLAYBACK & CAPTURE */
  irqmask = INW (devc->osdev, devc->base + IRQ_MASK);
  irqmask |= 0x00C3;
  OUTW (devc->osdev, irqmask, devc->base + IRQ_MASK);
  pci_write_config_word (devc->osdev, 0x40, 0x807F);
#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 (fm801_devc * devc)
{
  if (!opl3_detect (devc->base + 0x68, devc->osdev))
    return;
  opl3_init (devc->base + 0x68, devc->osdev);
  devc->fm_attached = 1;
}

static void
attach_mpu (fm801_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 = "FM801 MPU-401";
  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;
    }
  devc->mpu_attached = 1;
  attach_uart401 (&hw_config);
}

static void
unload_mpu (fm801_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 = "FM801 MPU-401";
  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

static int
init_fm801 (fm801_devc * devc)
{
  int my_mixer, my_mixer2;
  int legacy;
  int irqmask;
  int adev;
  int first_dev = 0;
  int i;

  devc->mpu_attached = devc->fm_attached = 0;

  legacy = 0;

#if !defined(__hpux) && !defined(sparc) && !defined(_TRU64)
  /* Enable Legacy FM, MPU and Joystick ports */
  legacy = 0x001E;
  switch (fmedia_mpu_irq)
    {
    case 5:
      legacy |= 0x0000;
      break;
    case 7:
      legacy |= 0x0800;
      break;
    case 9:
      legacy |= 0x1000;
      break;
    case 10:
      legacy |= 0x1800;
      break;
    case 11:
      legacy |= 0x2000;
      break;
    }
#endif

  pci_write_config_word (devc->osdev, 0x40, legacy);

  /* codec cold reset + AC'97 warm reset */
  OUTW (devc->osdev, (1 << 5) | (1 << 6), devc->base + CODEC_CONTROL);
  oss_udelay (10);
  OUTW (devc->osdev, 0, devc->base + CODEC_CONTROL);

  if (devc->model == MDL_FM801AU)
    {
      OUTW (devc->osdev, (1 << 7), devc->base + CODEC_CONTROL);
      oss_udelay (10);
    }

  /* init volume */
  OUTW (devc->osdev, 0x0808, devc->base + PCM_VOL);
  OUTW (devc->osdev, 0x0808, devc->base + FM_VOL);
  OUTW (devc->osdev, 0x0808, devc->base + I2S_VOL);
  /* interrupt setup - unmask MPU, PLAYBACK & CAPTURE */
  irqmask = INW (devc->osdev, devc->base + IRQ_MASK);
  irqmask &= ~0x0083;
  OUTW (devc->osdev, irqmask, devc->base + IRQ_MASK);
  OUTW (devc->osdev, 0x280C, devc->base + GENERAL_CONTROL);
  OUTW (devc->osdev, 0x0, devc->base + I2S_MODE);
#if !defined(__hpux) && !defined(sparc) && !defined(_TRU64_UNIX)
  /* interrupt clear */

TODO: Check this. Unaligned I/O access causes a crash onder non-x86

  OUTW (devc->osdev, IRQ_PLAY | IRQ_REC | IRQ_MPU, devc->base + IRQ_STATUS);
#endif

Enable BusMasterMode and IOSpace Access

#ifdef OBSOLETED_STUFF
  attach_fm (devc);
  attach_mpu (devc);
#endif
  my_mixer = ac97_install (&devc->ac97devc, "FM801 AC97 Mixer",
			   ac97_read, ac97_write, devc, devc->osdev);

  if (my_mixer >= 0)
    {
      devc->mixer_dev = my_mixer;
      if (devc->model == MDL_FM801AU)
	{
	  my_mixer2 = ac97_install (&devc->ac97devc2, "FM801 AC97 Secondary",
				    ac97_read2, ac97_write2, devc,
				    devc->osdev);
	  if (my_mixer2 >= 0)
	    devc->mixer2_dev = my_mixer2;
	}
    }
  else
    return 0;

  for (i = 0; i < MAX_PORTC; i++)
    {
      char tmp_name[100];
      fm801_portc *portc = &devc->portc[i];
      int caps = ADEV_AUTOMODE;
      if (i == 0)
	{
	  strcpy (tmp_name, devc->chip_name);
	  caps |= ADEV_DUPLEX;
	}
      else
	{
	  strcpy (tmp_name, devc->chip_name);
	  caps |= ADEV_DUPLEX | ADEV_SHADOW;
	}
      if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
					devc->osdev,
					devc->osdev,
					tmp_name,
					&fm801_audio_driver,
					sizeof (audiodrv_t),
					caps,
					AFMT_U8 | AFMT_S16_LE, devc, -1)) < 0)
	{
	  adev = -1;
	  return 1;
	}
      else
	{
	  if (i == 0)
	    first_dev = adev;
	  audio_engines[adev]->portc = portc;
	  audio_engines[adev]->rate_source = first_dev;
	  audio_engines[adev]->mixer_dev = my_mixer;
	  audio_engines[adev]->min_rate = 5000;
	  audio_engines[adev]->max_rate = 48000;
	  audio_engines[adev]->caps |= PCM_CAP_FREERATE;
	  audio_engines[adev]->min_channels = 2;
	  audio_engines[adev]->max_channels = 6;
	  portc->open_mode = 0;
	  portc->audiodev = adev;
	  portc->audio_enabled = 0;
#ifdef CONFIG_OSS_VMIX
	  if (i == 0)
	     vmix_attach_audiodev(devc->osdev, adev, -1, 0);
#endif
	}
      init_audio (devc);
    }
  return 1;
}

int
oss_fmedia_attach (oss_device_t * osdev)
{
  unsigned char pci_irq_line, pci_revision;
  unsigned short pci_command, vendor, device;
  unsigned int pci_ioaddr;
  int err;
  fm801_devc *devc;

  DDB (cmn_err (CE_WARN, "Entered FM801 FM801 probe routine\n"));

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

  if (vendor != FORTEMEDIA_VENDOR_ID || device != FORTEMEDIA_FM801)
    return 0;

  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_BASE_ADDRESS_0, &pci_ioaddr);

  DDB (cmn_err (CE_WARN, "FM801 I/O base %04x\n", pci_ioaddr));

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

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

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

  devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);

  /* Remove I/O space marker in bit 0. */
  devc->base &= ~3;
  devc->mpu_irq = fmedia_mpu_irq;
  devc->mpu_base = devc->base + 0x30;
  pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO;
  pci_write_config_word (osdev, PCI_COMMAND, pci_command);

  switch (pci_revision)
    {
    case 0xb1:
      devc->model = MDL_FM801AS;
      devc->chip_name = "ForteMedia FM801-AS";
      break;
    case 0xb2:
      devc->model = MDL_FM801AU;
      devc->chip_name = "ForteMedia FM801-AU";
      break;
    }

  oss_register_device (osdev, devc->chip_name);
  if ((err = oss_register_interrupts (osdev, 0, fm801intr, NULL)) < 0)
    {
      cmn_err (CE_WARN, "Error installing interrupt handler: %x\n", err);
      return 0;
    }

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

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

int
oss_fmedia_detach (oss_device_t * osdev)
{
  fm801_devc *devc = (fm801_devc *) osdev->devc;

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

  if (devc->audio_initialized)
    {
      uninit_audio (devc);
    }

  devc->audio_initialized = 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);
  UNMAP_PCI_IOADDR (devc->osdev, 0);
  oss_unregister_device (devc->osdev);
  return 1;
}

Copyright (C) 4Front Technologies, 2007. All rights reserved.

Back to index OSS web site


Copyright (C) 4Front Technologies, 2007. All rights reserved.
Back to index OSS web site