diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
commit | 1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch) | |
tree | 4495d23e7b54ab5700e3839081e797c1eafe0db9 /kernel/drv/oss_emu10k1x | |
download | oss4-upstream.tar.gz |
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'kernel/drv/oss_emu10k1x')
-rw-r--r-- | kernel/drv/oss_emu10k1x/.config | 1 | ||||
-rw-r--r-- | kernel/drv/oss_emu10k1x/.devices | 1 | ||||
-rw-r--r-- | kernel/drv/oss_emu10k1x/.name | 1 | ||||
-rw-r--r-- | kernel/drv/oss_emu10k1x/.params | 5 | ||||
-rw-r--r-- | kernel/drv/oss_emu10k1x/oss_emu10k1x.c | 1243 | ||||
-rw-r--r-- | kernel/drv/oss_emu10k1x/oss_emu10k1x.man | 27 |
6 files changed, 1278 insertions, 0 deletions
diff --git a/kernel/drv/oss_emu10k1x/.config b/kernel/drv/oss_emu10k1x/.config new file mode 100644 index 0000000..5280084 --- /dev/null +++ b/kernel/drv/oss_emu10k1x/.config @@ -0,0 +1 @@ +platform=i86pc diff --git a/kernel/drv/oss_emu10k1x/.devices b/kernel/drv/oss_emu10k1x/.devices new file mode 100644 index 0000000..7565a33 --- /dev/null +++ b/kernel/drv/oss_emu10k1x/.devices @@ -0,0 +1 @@ +oss_emu10k1x pci1102,6 Creative Sound Blaster 5.1 (Dell) diff --git a/kernel/drv/oss_emu10k1x/.name b/kernel/drv/oss_emu10k1x/.name new file mode 100644 index 0000000..af96a5f --- /dev/null +++ b/kernel/drv/oss_emu10k1x/.name @@ -0,0 +1 @@ +Creative Sound Blaster 5.1 (Dell) diff --git a/kernel/drv/oss_emu10k1x/.params b/kernel/drv/oss_emu10k1x/.params new file mode 100644 index 0000000..19c1a21 --- /dev/null +++ b/kernel/drv/oss_emu10k1x/.params @@ -0,0 +1,5 @@ +int emu10k1x_spdif_enable=0; +/* + * Enable SPDIF on Combo Jack + * Values: 1=Enable 0=Disable Default: 0 + */ diff --git a/kernel/drv/oss_emu10k1x/oss_emu10k1x.c b/kernel/drv/oss_emu10k1x/oss_emu10k1x.c new file mode 100644 index 0000000..5a66ff7 --- /dev/null +++ b/kernel/drv/oss_emu10k1x/oss_emu10k1x.c @@ -0,0 +1,1243 @@ +/* + * Purpose: Driver for Creative emu10k1x audio controller + * + * This device is usually called as SB Live! 5.1 and it has been used in + * some Dell machines. However it has nothing to do with the original + * SB Live! design. + */ +/* + * + * 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_emu10k1x_cfg.h" +#include "oss_pci.h" +#include "ac97.h" +#include "midi_core.h" +#include "remux.h" + +#define PCI_VENDOR_ID_CREATIVE 0x1102 +#define PCI_DEVICE_ID_CREATIVE_EMU10K1X 0x0006 + +#define USE_DUALBUF + +/* + * Indirect registers + */ + +#define PTBA 0x000 +#define PTBS 0x001 +#define PTCA 0x002 +#define PFBA 0x004 +#define PFBS 0x005 +#define CPFA 0x006 +#define PFEA 0x007 +#define CPCAV 0x008 +#define RFBA 0x010 +#define RFBS 0x011 +#define CRFA 0x012 +#define CRCAV 0x013 +#define CDL 0x020 +#define CDR 0x030 +#define SA 0x040 +#define EA_aux 0x041 +#define SCS0 0x042 +#define SCS1 0x043 +#define SCS2 0x044 +#define SPC 0x045 +#define WMARK 0x046 +#define MUDAT 0x047 +#define MUCMD 0x048 +#define RCD 0x050 + +/* Interrupt bits + */ + +#define INTR_RFF (1<<19) +#define INTR_RFH (1<<16) +#define INTR_PFF (1<<11) +#define INTR_PFH (1<<8) +#define INTR_EAI (1<<29) +#define INTR_PCI 1 +#define INTR_UART_RX 2 +#define INTR_UART_TX 4 +#define INTR_AC97 0x10 +#define INTR_GPIO 0x40 + +#define PLAY_INTR_ENABLE (INTR_PFF|INTR_PFH) +#define RECORD_INTR_ENABLE (INTR_RFF|INTR_RFH) + +#define EMU_BUFSIZE 32*1024 +#define MAX_PORTC 3 + +extern int emu10k1x_spdif_enable; + +typedef struct +{ + int audio_dev; + int port_number; + int open_mode; + int trigger_bits; + int audio_enabled; + int channels; + int fmt; + int speed; + unsigned char *playbuf, *recbuf; + oss_native_word playbuf_phys, recbuf_phys; + + oss_dma_handle_t playbuf_dma_handle, recbuf_dma_handle; + + int play_cfrag, play_chalf, rec_cfrag, rec_chalf; +} +emu10k1x_portc; + +typedef struct +{ + oss_device_t *osdev; + int loaded; + oss_native_word base; + oss_mutex_t mutex; + oss_mutex_t low_mutex; + int irq; + char *card_name; + unsigned int subvendor; + + int mixer_dev; + ac97_devc ac97devc; + +/* + * UART + */ + oss_midi_inputbyte_t midi_input_intr; + int midi_opened, midi_disabled; + volatile unsigned char input_byte; + int midi_dev; + int mpu_attached; + + emu10k1x_portc portc[MAX_PORTC]; + +} +emu10k1x_devc; + + +static void emu10k1xuartintr (emu10k1x_devc * devc); + +static unsigned int +read_reg (emu10k1x_devc * devc, int reg, int chn) +{ + oss_native_word flags; + unsigned int val; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTL (devc->osdev, (reg << 16) | (chn & 0xffff), devc->base + 0x00); /* Pointer */ + val = INL (devc->osdev, devc->base + 0x04); /* Data */ + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + +/* printk("Read reg %03x (ch %d) = %08x\n", reg, chn, val); */ + return val; +} + +static void +write_reg (emu10k1x_devc * devc, int reg, int chn, unsigned int value) +{ + oss_native_word flags; + + /* printk("Write reg %03x (ch %d) = %08x\n", reg, chn, value); */ + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTL (devc->osdev, (reg << 16) | (chn & 0xffff), devc->base + 0x00); /* Pointer */ + OUTL (devc->osdev, value, devc->base + 0x04); /* Data */ + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); +#if 0 + { + char tmp[100]; + int ok = 1; + sprintf (tmp, "@w%d %04x/%s %x", chn, reg, emu_regname (reg, &ok), value); + if (ok) + oss_do_timing (tmp); + } +#endif +} + +static void +recording_intr (emu10k1x_devc * devc, emu10k1x_portc * portc, int status) +{ +#ifdef USE_DUALBUF + unsigned char *frombuf, *tobuf; + dmap_p dmap = audio_engines[portc->audio_dev]->dmap_in; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + /* "Auto sync" the play half counters with the device */ + if (status & INTR_RFH) /* 1st half completed */ + portc->rec_chalf = 0; /* Reuse the first half */ + else + portc->rec_chalf = 1; /* Reuse the second half */ + + tobuf = dmap->dmabuf + (portc->rec_cfrag * dmap->fragment_size); + frombuf = portc->recbuf + (portc->rec_chalf * dmap->fragment_size); + + memcpy (tobuf, frombuf, dmap->fragment_size); + +/* printk("rec %d/%d\n", portc->rec_cfrag, portc->rec_chalf); */ + portc->rec_cfrag = (portc->rec_cfrag + 1) % dmap->nfrags; + portc->rec_chalf = !portc->rec_chalf; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +#endif + oss_audio_inputintr (portc->audio_dev, 0); +} + +static void +playback_intr (emu10k1x_devc * devc, emu10k1x_portc * portc, + unsigned int status) +{ +#ifdef USE_DUALBUF + dmap_p dmap = audio_engines[portc->audio_dev]->dmap_out; + unsigned char *frombuf, *tobuf; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + /* "Auto sync" the play half counters with the device */ + if (status & (INTR_PFH << portc->port_number)) /* 1st half completed */ + { + portc->play_chalf = 0; /* Reuse the first half */ + } + else + { + portc->play_chalf = 1; /* Reuse the second half */ + } + + + frombuf = dmap->dmabuf + (portc->play_cfrag * dmap->fragment_size); + tobuf = portc->playbuf + (portc->play_chalf * dmap->fragment_size); + + memcpy (tobuf, frombuf, dmap->fragment_size); + +/* printk("play %d/%d\n", portc->play_cfrag, portc->play_chalf); */ + portc->play_cfrag = (portc->play_cfrag + 1) % dmap->nfrags; + portc->play_chalf = !portc->play_chalf; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +#endif + oss_audio_outputintr (portc->audio_dev, 0); +} + +static int +emu10k1xintr (oss_device_t * osdev) +{ + int serviced = 0; + unsigned int status; + emu10k1x_devc *devc = (emu10k1x_devc *) osdev->devc; + int portnum; + + status = INL (devc->osdev, devc->base + 0x08); + + if (status & 0x2) /* MIDI RX interrupt */ + { + emu10k1xuartintr (devc); + serviced = 1; + } + + if (status & (INTR_PFF | INTR_PFH | INTR_RFF | INTR_RFH)) + { + for (portnum = 0; portnum < 3; portnum++) + { + emu10k1x_portc *portc = &devc->portc[portnum]; + + if ((portc->trigger_bits & PCM_ENABLE_OUTPUT) && + (status & ((INTR_PFF | INTR_PFH) << portc->port_number))) + playback_intr (devc, portc, status); + if ((portc->trigger_bits & PCM_ENABLE_INPUT) && + (status & (INTR_RFF | INTR_RFH))) + recording_intr (devc, portc, status); + + } + serviced = 1; + OUTL (devc->osdev, status, devc->base + 0x08); /* Acknowledge */ + } + return serviced; +} + +/*ARGSUSED*/ +static int +emu10k1x_set_rate (int dev, int arg) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + + return portc->speed = 48000; +} + +static short +emu10k1x_set_channels (int dev, short arg) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->channels; + + if (portc->open_mode & OPEN_READ) + return portc->channels = 2; + + if (arg != 1 && arg != 2) + return portc->channels = 2; + return portc->channels = arg; +} + +static unsigned int +emu10k1x_set_format (int dev, unsigned int arg) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->fmt; + + if (arg == AFMT_AC3) + if ((portc->open_mode & OPEN_READ) || !emu10k1x_spdif_enable) + arg = AFMT_S16_LE; + + if (arg != AFMT_AC3 && arg != AFMT_S16_LE) + return portc->fmt = AFMT_S16_LE; + + return portc->fmt = arg; +} + +/*ARGSUSED*/ +static int +emu10k1x_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static void emu10k1x_trigger (int dev, int state); + +static void +emu10k1x_reset (int dev) +{ + emu10k1x_trigger (dev, 0); +} + +static void +emu10k1x_reset_input (int dev) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + emu10k1x_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +emu10k1x_reset_output (int dev) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + emu10k1x_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +/*ARGSUSED*/ +static int +emu10k1x_open (int dev, int mode, int open_flags) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + emu10k1x_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; + } + + portc->open_mode = mode; + portc->audio_enabled = ~mode; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static void +emu10k1x_close (int dev, int mode) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + + emu10k1x_trigger (dev, 0); + portc->open_mode = 0; + portc->audio_enabled &= ~mode; +} + +/*ARGSUSED*/ +static void +emu10k1x_output_block (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +/*ARGSUSED*/ +static void +emu10k1x_start_input (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ + emu10k1x_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; +} + + +static void +emu10k1x_trigger (int dev, int state) +{ + emu10k1x_devc *devc = audio_engines[dev]->devc; + emu10k1x_portc *portc = audio_engines[dev]->portc; + int tmp; + 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)) + { + /* Enable play channel and set mono/stereo mode */ + tmp = read_reg (devc, SA, 0); + tmp &= ~(0x10000 << portc->port_number); + if (portc->channels == 1) + tmp |= (0x10000 << portc->port_number); + tmp |= 1 << portc->port_number; + write_reg (devc, SA, 0, tmp); + + 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; + + /* Disable Play channel */ + tmp = read_reg (devc, SA, 0); + tmp &= ~(1 << portc->port_number); + write_reg (devc, SA, 0, tmp); + } + } + } + + if (portc->open_mode & OPEN_READ) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + /* Enable Rec Channel */ + tmp = read_reg (devc, SA, 0); + tmp |= 0x100; + write_reg (devc, SA, 0, tmp); + 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; + + /* disable channel */ + tmp = read_reg (devc, SA, 0); + tmp &= ~0x100; + write_reg (devc, SA, 0, tmp); + } + } + } + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static int +emu10k1x_prepare_for_input (int dev, int bsize, int bcount) +{ + emu10k1x_devc *devc = audio_engines[dev]->devc; + emu10k1x_portc *portc = audio_engines[dev]->portc; + dmap_p dmap = audio_engines[dev]->dmap_in; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); +#ifndef USE_DUALBUF + /* Single buffering mode */ + dmap->nfrags = 2; + dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size; + write_reg (devc, RFBA, 0, dmap->dmabuf_phys); + write_reg (devc, RFBS, 0, (dmap->bytes_in_use - 4) << 16); +#else + write_reg (devc, RFBA, 0, portc->recbuf_phys); + write_reg (devc, RFBS, 0, (dmap->fragment_size * 2) << 16); +#endif + memset (portc->recbuf, 0, EMU_BUFSIZE); + portc->rec_cfrag = portc->rec_chalf = 0; + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +emu10k1x_prepare_for_output (int dev, int bsize, int bcount) +{ + emu10k1x_devc *devc = audio_engines[dev]->devc; + emu10k1x_portc *portc = audio_engines[dev]->portc; + dmap_p dmap = audio_engines[dev]->dmap_out; + unsigned int tmp; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->fmt == AFMT_AC3) + portc->channels = 2; + + write_reg (devc, PTBA, portc->port_number, 0); + write_reg (devc, PTBS, portc->port_number, 0); + write_reg (devc, PTCA, portc->port_number, 0); + + write_reg (devc, CPFA, portc->port_number, 0); + write_reg (devc, PFEA, portc->port_number, 0); + write_reg (devc, CPCAV, portc->port_number, 0); + +#ifndef USE_DUALBUF + /* Single buffering mode */ + dmap->nfrags = 2; + dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size; + write_reg (devc, PFBA, portc->port_number, dmap->dmabuf_phys); + write_reg (devc, PFBS, portc->port_number, (dmap->bytes_in_use - 4) << 16); +#else + /* Dual buffering mode */ + write_reg (devc, PFBA, portc->port_number, portc->playbuf_phys); + write_reg (devc, PFBS, portc->port_number, (dmap->fragment_size * 2) << 16); +#endif + memset (portc->playbuf, 0, EMU_BUFSIZE); + portc->play_cfrag = portc->play_chalf = 0; + + if (portc->fmt == AFMT_AC3) + { + tmp = read_reg (devc, EA_aux, 0); + tmp &= ~(0x03 << (portc->port_number * 2)); + if (portc->port_number == 2) + tmp &= ~0x10000; + write_reg (devc, EA_aux, 0, tmp); + write_reg (devc, SCS0 + portc->port_number, 0, 0x02108506); /* Data */ + } + else + { + tmp = read_reg (devc, EA_aux, 0); + tmp |= (0x03 << (portc->port_number * 2)); + + if (emu10k1x_spdif_enable == 0) + if (portc->port_number == 2) + tmp |= 0x10000; + + write_reg (devc, EA_aux, 0, tmp); + write_reg (devc, SCS0 + portc->port_number, 0, 0x02108504); /* Audio */ + } + + portc->audio_enabled |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static int +emu10k1x_alloc_buffer (int dev, dmap_t * dmap, int direction) +{ + int err; + emu10k1x_devc *devc = audio_engines[dev]->devc; + emu10k1x_portc *portc = audio_engines[dev]->portc; + oss_native_word phaddr; + + if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0) + return err; + + if (direction == OPEN_READ) + { + if (portc->port_number == 0) + { + portc->recbuf = + CONTIG_MALLOC (devc->osdev, EMU_BUFSIZE, MEMLIMIT_32BITS, &phaddr, portc->recbuf_dma_handle); + if (portc->recbuf == NULL) + return OSS_ENOMEM; + portc->recbuf_phys = phaddr; + } + } + else + { + portc->playbuf = + CONTIG_MALLOC (devc->osdev, EMU_BUFSIZE, MEMLIMIT_32BITS, &phaddr, portc->playbuf_dma_handle); + if (portc->playbuf == NULL) + return OSS_ENOMEM; + portc->playbuf_phys = phaddr; + } + return 0; +} + +/*ARGSUSED*/ +static int +emu10k1x_free_buffer (int dev, dmap_t * dmap, int direction) +{ + emu10k1x_devc *devc = audio_engines[dev]->devc; + emu10k1x_portc *portc = audio_engines[dev]->portc; + + oss_free_dmabuf (dev, dmap); + + if (portc->playbuf != NULL) + { + CONTIG_FREE (devc->osdev, portc->playbuf, EMU_BUFSIZE, portc->playbuf_dma_handle); + portc->playbuf = NULL; + } + + if (portc->recbuf != NULL) + { + CONTIG_FREE (devc->osdev, portc->recbuf, EMU_BUFSIZE, portc->recbuf_dma_handle); + portc->recbuf = NULL; + } + + return 0; +} + +#if 0 +static int +emu10k1x_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + unsigned int p = 0; + + emu10k1x_devc *devc = audio_engines[dev]->devc; + emu10k1x_portc *portc = audio_engines[dev]->portc; + + dmap = audio_engines[dev]->dmap_out; + if (direction == PCM_ENABLE_OUTPUT) + p = read_reg (devc, CPFA, portc->port_number); + + if (direction == PCM_ENABLE_INPUT) + p = read_reg (devc, CRFA, portc->port_number); + + p %= (dmap->bytes_in_use - 4); + + return p; +} +#endif + +static audiodrv_t emu10k1x_audio_driver = { + emu10k1x_open, + emu10k1x_close, + emu10k1x_output_block, + emu10k1x_start_input, + emu10k1x_ioctl, + emu10k1x_prepare_for_input, + emu10k1x_prepare_for_output, + emu10k1x_reset, + NULL, + NULL, + emu10k1x_reset_input, + emu10k1x_reset_output, + emu10k1x_trigger, + emu10k1x_set_rate, + emu10k1x_set_format, + emu10k1x_set_channels, + NULL, + NULL, + NULL, + NULL, + emu10k1x_alloc_buffer, + emu10k1x_free_buffer, + NULL, + NULL, + NULL /*emu10k1x_get_buffer_pointer */ +}; + + +#define MUADAT 0x47 +#define MUACMD 0x48 +#define MUASTAT 0x48 + +static __inline__ int +emu10k1xuart_status (emu10k1x_devc * devc) +{ + return read_reg (devc, MUASTAT, 0); +} + +#define input_avail(devc) (!(emu10k1xuart_status(devc)&INPUT_AVAIL)) +#define output_ready(devc) (!(emu10k1xuart_status(devc)&OUTPUT_READY)) +static void +emu10k1xuart_cmd (emu10k1x_devc * devc, unsigned char cmd) +{ + write_reg (devc, MUACMD, 0, cmd); +} + +static __inline__ int +emu10k1xuart_read (emu10k1x_devc * devc) +{ + return read_reg (devc, MUADAT, 0); +} + +static __inline__ void +emu10k1xuart_write (emu10k1x_devc * devc, unsigned char byte) +{ + write_reg (devc, MUADAT, 0, byte); +} + +#define OUTPUT_READY 0x40 +#define INPUT_AVAIL 0x80 +#define MPU_ACK 0xFE +#define MPU_RESET 0xFF +#define UART_MODE_ON 0x3F + +static int reset_emu10k1xuart (emu10k1x_devc * devc); +static void enter_uart_mode (emu10k1x_devc * devc); + +static void +emu10k1xuart_input_loop (emu10k1x_devc * devc) +{ + while (input_avail (devc)) + { + unsigned char c = emu10k1xuart_read (devc); + + if (c == MPU_ACK) + devc->input_byte = c; + else if (devc->midi_opened & OPEN_READ && devc->midi_input_intr) + devc->midi_input_intr (devc->midi_dev, c); + } +} + +static void +emu10k1xuartintr (emu10k1x_devc * devc) +{ + emu10k1xuart_input_loop (devc); +} + +/*ARGSUSED*/ +static int +emu10k1xuart_open (int dev, int mode, oss_midi_inputbyte_t inputbyte, + oss_midi_inputbuf_t inputbuf, + oss_midi_outputintr_t outputintr) +{ + emu10k1x_devc *devc = (emu10k1x_devc *) midi_devs[dev]->devc; + + if (devc->midi_opened) + { + return OSS_EBUSY; + } + + while (input_avail (devc)) + emu10k1xuart_read (devc); + + devc->midi_input_intr = inputbyte; + devc->midi_opened = mode; + enter_uart_mode (devc); + devc->midi_disabled = 0; + + return 0; +} + +/*ARGSUSED*/ +static void +emu10k1xuart_close (int dev, int mode) +{ + emu10k1x_devc *devc = (emu10k1x_devc *) midi_devs[dev]->devc; + + reset_emu10k1xuart (devc); + oss_udelay (10); + enter_uart_mode (devc); + reset_emu10k1xuart (devc); + devc->midi_opened = 0; +} + + +static int +emu10k1xuart_out (int dev, unsigned char midi_byte) +{ + int timeout; + emu10k1x_devc *devc = (emu10k1x_devc *) midi_devs[dev]->devc; + oss_native_word flags; + + /* + * Test for input since pending input seems to block the output. + */ + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (input_avail (devc)) + emu10k1xuart_input_loop (devc); + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + /* + * Sometimes it takes about 130000 loops before the output becomes ready + * (After reset). Normally it takes just about 10 loops. + */ + + for (timeout = 130000; timeout > 0 && !output_ready (devc); timeout--); + + if (!output_ready (devc)) + { + cmn_err (CE_WARN, "UART timeout - Device not responding\n"); + devc->midi_disabled = 1; + reset_emu10k1xuart (devc); + enter_uart_mode (devc); + return 1; + } + + emu10k1xuart_write (devc, midi_byte); + return 1; +} + +/*ARGSUSED*/ +static int +emu10k1xuart_ioctl (int dev, unsigned cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static midi_driver_t emu10k1x_midi_driver = { + emu10k1xuart_open, + emu10k1xuart_close, + emu10k1xuart_ioctl, + emu10k1xuart_out +}; + + +static void +enter_uart_mode (emu10k1x_devc * devc) +{ + int ok, timeout; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--); + + devc->input_byte = 0; + emu10k1xuart_cmd (devc, UART_MODE_ON); + + ok = 0; + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (devc->input_byte == MPU_ACK) + ok = 1; + else if (input_avail (devc)) + if (emu10k1xuart_read (devc) == MPU_ACK) + ok = 1; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + + +void +attach_emu10k1xuart (emu10k1x_devc * devc) +{ + enter_uart_mode (devc); + + devc->midi_dev = + oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "EMU10K1X", "SB P16X UART", + &emu10k1x_midi_driver, sizeof (midi_driver_t), + 0, devc, devc->osdev); + devc->midi_opened = 0; +} + +static int +reset_emu10k1xuart (emu10k1x_devc * devc) +{ + int ok, timeout, n; + + /* + * Send the RESET command. Try again if no success at the first time. + */ + + ok = 0; + + for (n = 0; n < 2 && !ok; n++) + { + for (timeout = 30000; timeout > 0 && !output_ready (devc); timeout--); + + devc->input_byte = 0; + emu10k1xuart_cmd (devc, MPU_RESET); + + /* + * Wait at least 25 msec. This method is not accurate so let's make the + * loop bit longer. Cannot sleep since this is called during boot. + */ + + for (timeout = 50000; timeout > 0 && !ok; timeout--) + if (devc->input_byte == MPU_ACK) /* Interrupt */ + ok = 1; + else if (input_avail (devc)) + if (emu10k1xuart_read (devc) == MPU_ACK) + ok = 1; + + } + + + + if (ok) + emu10k1xuart_input_loop (devc); /* + * Flush input before enabling interrupts + */ + + return ok; +} + + +int +probe_emu10k1xuart (emu10k1x_devc * devc) +{ + int ok = 0; + oss_native_word flags; + + DDB (cmn_err (CE_CONT, "Entered probe_emu10k1xuart\n")); + + devc->midi_input_intr = NULL; + devc->midi_opened = 0; + devc->input_byte = 0; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + ok = reset_emu10k1xuart (devc); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + if (ok) + { + DDB (cmn_err (CE_CONT, "Reset UART401 OK\n")); + } + else + { + DDB (cmn_err + (CE_WARN, "Reset UART401 failed (no hardware present?).\n")); + DDB (cmn_err + (CE_WARN, "mpu401 status %02x\n", emu10k1xuart_status (devc))); + } + + DDB (cmn_err (CE_WARN, "emu10k1xuart detected OK\n")); + return ok; +} + +void +unload_emu10k1xuart (emu10k1x_devc * devc) +{ + reset_emu10k1xuart (devc); +} + + +static void +attach_mpu (emu10k1x_devc * devc) +{ + devc->mpu_attached = 1; + attach_emu10k1xuart (devc); +} + + +static int +emu10k1x_ac97_read (void *devc_, int wAddr) +{ + emu10k1x_devc *devc = devc_; + int dtemp = 0, i; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, wAddr, devc->base + 0x1e); + for (i = 0; i < 10000; i++) + if (INB (devc->osdev, devc->base + 0x1e) & 0x80) + break; + dtemp = INW (devc->osdev, devc->base + 0x1c); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return dtemp & 0xffff; +} + +static int +emu10k1x_ac97_write (void *devc_, int wAddr, int wData) +{ + emu10k1x_devc *devc = devc_; + int i; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, wAddr, devc->base + 0x1e); + for (i = 0; i < 10000; i++) + if (INB (devc->osdev, devc->base + 0x1e) & 0x80) + break; + OUTW (devc->osdev, wData, devc->base + 0x1c); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return 0; +} + +static const int bindings[MAX_PORTC] = { + DSP_BIND_FRONT, + DSP_BIND_SURR, + DSP_BIND_CENTER_LFE +}; + +static void +install_audio_devices (emu10k1x_devc * devc) +{ + int i; + unsigned int tmp; + int firstdev = -1; + char name[64]; + +#if 0 + if (emu10k1x_spdif_enable == 1) + n = 2; +#endif + + /* Enable play interrupts for all 3 channels */ + for (i = 0; i < MAX_PORTC; i++) + { + tmp = INL (devc->osdev, devc->base + 0x0c); + tmp |= PLAY_INTR_ENABLE << i; + OUTL (devc->osdev, tmp, devc->base + 0x0c); + } + + /* Enable record interrupts */ + tmp = INL (devc->osdev, devc->base + 0x0c); + tmp |= RECORD_INTR_ENABLE; + OUTL (devc->osdev, tmp, devc->base + 0x0c); + + for (i = 0; i < MAX_PORTC; i++) + { + int adev, flags; + emu10k1x_portc *portc = &devc->portc[i]; + + flags = ADEV_AUTOMODE | ADEV_FIXEDRATE | ADEV_16BITONLY | ADEV_COLD; + + switch (i) + { + case 0: + sprintf (name, "%s (front)", devc->card_name); + break; + case 1: + sprintf (name, "%s (surround)", devc->card_name); + break; + case 2: + if (emu10k1x_spdif_enable == 1) + sprintf (name, "%s (SPDIF)", devc->card_name); + else + sprintf (name, "%s (center/LFE)", devc->card_name); + break; + } + + if (i == 0) + flags |= ADEV_DUPLEX; + else + flags |= ADEV_NOINPUT; + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + name, + &emu10k1x_audio_driver, + sizeof (audiodrv_t), + flags, AFMT_S16_LE | AFMT_AC3, devc, + -1)) < 0) + { + return; + } + + if (i == 0) + firstdev = adev; + audio_engines[adev]->portc = portc; + audio_engines[adev]->mixer_dev = devc->mixer_dev; + audio_engines[adev]->rate_source = firstdev; + audio_engines[adev]->min_rate = 48000; + audio_engines[adev]->max_rate = 48000; + audio_engines[adev]->caps |= PCM_CAP_FREERATE; + /*audio_engines[adev]->max_block = EMU_BUFSIZE / 2; *//* Never change this */ + audio_engines[adev]->fixed_rate = 48000; + audio_engines[adev]->binding = bindings[i]; + audio_engines[adev]->vmix_flags = VMIX_MULTIFRAG; + portc->audio_dev = adev; + portc->open_mode = 0; + portc->port_number = i; + portc->channels = 2; + portc->fmt = AFMT_S16_LE; +#ifdef CONFIG_OSS_VMIX + if (i == 0) + vmix_attach_audiodev(devc->osdev, adev, -1, 0); +#endif + } + +#ifdef USE_REMUX + if (firstdev >= 0) + { + if (emu10k1x_spdif_enable == 1) + { + sprintf (name, "%s 4.0 output", devc->card_name); + remux_install (name, devc->osdev, firstdev, firstdev + 1, -1, -1); + } + else + { + sprintf (name, "%s 5.1 output", devc->card_name); + remux_install (name, devc->osdev, firstdev, firstdev + 1, + firstdev + 2, -1); + } + } +#endif +} + +static void +select_out3_mode (emu10k1x_devc * devc, int mode) +{ + /* + * Set the out3/spdif combo jack format. + * mode0=analog rear/center, 1=spdif + */ + + if (mode == 0) + { + write_reg (devc, SPC, 0, 0x00000700); + write_reg (devc, EA_aux, 0, 0x0001000f); + } + else + { + write_reg (devc, SPC, 0, 0x00000000); + write_reg (devc, EA_aux, 0, 0x0000070f); + } +} + +int +oss_emu10k1x_attach (oss_device_t * osdev) +{ + unsigned char pci_irq_line, pci_revision; + unsigned short pci_command, vendor, device; + unsigned int pci_ioaddr; + unsigned int subvendor; + int err; + emu10k1x_devc *devc = NULL; + + DDB (cmn_err (CE_WARN, "Entered EMU10K1X probe routine\n")); + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if (vendor != PCI_VENDOR_ID_CREATIVE || + device != PCI_DEVICE_ID_CREATIVE_EMU10K1X) + + return 0; + + pci_read_config_dword (osdev, 0x2c, &subvendor); + 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); + + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; + pci_command &= ~(PCI_COMMAND_SERR | PCI_COMMAND_PARITY); + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + if (pci_ioaddr == 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.\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->card_name = "Sound Blaster Live (P16X)"; + devc->subvendor = subvendor; + + devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr); + devc->base &= ~0x3; + + devc->irq = pci_irq_line; + + MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); + MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1); + + oss_register_device (osdev, devc->card_name); + + if ((err = + oss_register_interrupts (devc->osdev, 0, emu10k1xintr, NULL)) < 0) + { + cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); + return 0; + } + +/* + * Init mixer + */ + devc->mixer_dev = ac97_install (&devc->ac97devc, devc->card_name, + emu10k1x_ac97_read, emu10k1x_ac97_write, + devc, devc->osdev); + if (devc->mixer_dev < 0) + { + cmn_err (CE_WARN, "Mixer install failed - cannot continue\n"); + return 0; + } + + write_reg (devc, SCS0, 0, 0x02108504); + write_reg (devc, SCS1, 0, 0x02108504); + write_reg (devc, SCS2, 0, 0x02108504); + select_out3_mode (devc, emu10k1x_spdif_enable); + OUTL (devc->osdev, 0x00000000, devc->base + 0x18); /* GPIO */ + OUTL (devc->osdev, INTR_PCI | INTR_UART_RX, devc->base + 0x0c); + OUTL (devc->osdev, 0x00000009, devc->base + 0x14); /* Enable audio */ + install_audio_devices (devc); + attach_mpu (devc); + return 1; +} + +static void +unload_mpu (emu10k1x_devc * devc) +{ + if (devc->mpu_attached) + { + unload_emu10k1xuart (devc); + devc->mpu_attached = 0; + } +} + +int +oss_emu10k1x_detach (oss_device_t * osdev) +{ + emu10k1x_devc *devc = (emu10k1x_devc *) osdev->devc; + + if (oss_disable_device (osdev) < 0) + return 0; + + write_reg (devc, SA, 0, 0); + OUTL (devc->osdev, 0x00000000, devc->base + 0x0c); /* Interrupt disable */ + OUTL (devc->osdev, 0x00000001, devc->base + 0x14); + + unload_mpu (devc); + + oss_unregister_interrupts (devc->osdev); + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + UNMAP_PCI_IOADDR (devc->osdev, 0); + + oss_unregister_device (osdev); + return 1; +} diff --git a/kernel/drv/oss_emu10k1x/oss_emu10k1x.man b/kernel/drv/oss_emu10k1x/oss_emu10k1x.man new file mode 100644 index 0000000..f3bb3bc --- /dev/null +++ b/kernel/drv/oss_emu10k1x/oss_emu10k1x.man @@ -0,0 +1,27 @@ +NAME +oss_emu10k1x - Creative Labs P16x (EMU10K1X) driver. + +DESCRIPTION +Open Sound System driver for Creative Labs SBLive 5.1 Dell OEM version +soundcards. The device has a chipset called the EMU10K1X and is not the same +as the SBLive EMU10K1/EMU10K2 audio processors found in the SBLive! and Audigy +soundcards. + +EMU10K1X device characteristics: + o 8/16/24 bit playback/record + o mono/stereo/4/5.1 playback + o 8KHz to 192Khz sample rate. + +OPTIONS +o emu10k1x_spdif_enable=<0|1> +The EMU10K1X has a versa-jack (orange) that can be set as SPDIF output +or the Side-Surround left/right speakers in a 5.1 setup. +When set as SPDIF, you can get play PCM/AC3 audio to a Dolby(R) capable +receiver. + +FILES +CONFIGFILEPATH/oss_emu10k1x.conf Device configuration file + +AUTHOR +4Front Technologies + |