diff options
Diffstat (limited to 'kernel/drv/oss_fmedia')
-rw-r--r-- | kernel/drv/oss_fmedia/.devices | 2 | ||||
-rw-r--r-- | kernel/drv/oss_fmedia/.name | 1 | ||||
-rw-r--r-- | kernel/drv/oss_fmedia/.params | 6 | ||||
-rw-r--r-- | kernel/drv/oss_fmedia/oss_fmedia.c | 1076 | ||||
-rw-r--r-- | kernel/drv/oss_fmedia/oss_fmedia.man | 23 |
5 files changed, 1108 insertions, 0 deletions
diff --git a/kernel/drv/oss_fmedia/.devices b/kernel/drv/oss_fmedia/.devices new file mode 100644 index 0000000..bccf58d --- /dev/null +++ b/kernel/drv/oss_fmedia/.devices @@ -0,0 +1,2 @@ +oss_fmedia pci1319,801 ForteMedia FM 801 +oss_fmedia pcs1489,7008 Genius Sound Maker Live diff --git a/kernel/drv/oss_fmedia/.name b/kernel/drv/oss_fmedia/.name new file mode 100644 index 0000000..5fdf20b --- /dev/null +++ b/kernel/drv/oss_fmedia/.name @@ -0,0 +1 @@ +ForteMedia FM 801 diff --git a/kernel/drv/oss_fmedia/.params b/kernel/drv/oss_fmedia/.params new file mode 100644 index 0000000..d756cde --- /dev/null +++ b/kernel/drv/oss_fmedia/.params @@ -0,0 +1,6 @@ +int fmedia_mpu_irq=0; +/* + * FM801 MPU IRQ + * Values: 5,7,9,10,11 Default: 0 + */ + diff --git a/kernel/drv/oss_fmedia/oss_fmedia.c b/kernel/drv/oss_fmedia/oss_fmedia.c new file mode 100644 index 0000000..fb5634a --- /dev/null +++ b/kernel/drv/oss_fmedia/oss_fmedia.c @@ -0,0 +1,1076 @@ +/* + * Purpose: Driver for FM801 FM801 PCI audio controller. + */ +/* + * + * 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; +} diff --git a/kernel/drv/oss_fmedia/oss_fmedia.man b/kernel/drv/oss_fmedia/oss_fmedia.man new file mode 100644 index 0000000..58ed235 --- /dev/null +++ b/kernel/drv/oss_fmedia/oss_fmedia.man @@ -0,0 +1,23 @@ +NAME +oss_fmedia - Forte Media FM801 driver. + +DESCRIPTION +Open Sound System driver for Forte Media FM801/FM801-AU audio controllers. + +FM801 device characteristics: + o 8/16 bit playback/record + o mono/stereo/4ch/5.1ch playback + o mono/sterero recording + o 8KHz to 48Khz sample rate. + +OPTIONS +o fmedia_mpu_irq=<xx> +Set the IRQ for the UART401 MPU. Refer to device conf file (see below) for +valid IRQs. + +FILES +CONFIGFILEPATH/oss_fmedia.conf Device configuration file + +AUTHOR +4Front Technologies + |