summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_sbxfi/oss_sbxfi.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_sbxfi/oss_sbxfi.c')
-rw-r--r--kernel/drv/oss_sbxfi/oss_sbxfi.c1078
1 files changed, 1078 insertions, 0 deletions
diff --git a/kernel/drv/oss_sbxfi/oss_sbxfi.c b/kernel/drv/oss_sbxfi/oss_sbxfi.c
new file mode 100644
index 0000000..837ab33
--- /dev/null
+++ b/kernel/drv/oss_sbxfi/oss_sbxfi.c
@@ -0,0 +1,1078 @@
+/*
+ * Purpose: Driver for Sound Blaster X-Fi (emu20k)
+ *
+ */
+
+/*
+ *
+ * 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_sbxfi_cfg.h"
+#include <oss_pci.h>
+#include "sbxfi.h"
+#include "20k1reg.h"
+#include "hwaccess.h"
+
+#define PCI_VENDOR_CREATIVE 0x1102
+#define CREATIVE_SBXFI_K1 0x0005
+#define CREATIVE_SBXFI_K2 0x000b
+#define CREATIVE_SBXFI_E 0x0009
+
+#define TIMER_INTERVAL 5 /* In milliseconds */
+
+#define DEFAULT_PLAY_RATE 96000 /* Default rate of play devices */
+#define DEFAULT_REC_RATE 96000 /* Default rate of rec devices */
+#define HARDWARE_RATE 96000 /* Internal rate used by the hardware */
+
+static void
+set_interval_timer(sbxfi_devc_t *devc, int msecs)
+{
+ int tic = (HARDWARE_RATE*msecs)/1000;
+
+ HwWrite20K1 (devc, TIMR, tic | TIMR_IE|TIMR_IP);
+}
+
+static int
+sbxfi_intr(oss_device_t *osdev)
+{
+ unsigned int status;
+ sbxfi_devc_t *devc = osdev->devc;
+
+ status = HwRead20K1 (devc, GIP);
+
+ if (status==0) /* Not for me */
+ return 0;
+
+ devc->interrupt_count++;
+
+#if 0
+ // Not using loop interrupts.
+ if (status & SRC_INT) /* SRC interrupt(s) pending */
+ {
+ unsigned int srcipm, srcip;
+ int i;
+
+ srcipm = HwRead20K1 (devc, SRCIPM); /* SRC interrupt pending map register */
+
+ for (i=0;i<7;i++)
+ if (srcipm & (1<<i))
+ {
+ int j;
+ srcip = HwRead20K1 (devc, SRCIP(i)); /* SRC interrupt pending register for block(i) */
+
+ for (j=0;j<32;j++)
+ if (srcip & (1<<j))
+ {
+ int chn=i*32+j;
+ sbxfi_portc_t *portc;
+
+ portc=devc->src_to_portc[chn];
+
+ if (portc==NULL)
+ {
+ cmn_err(CE_NOTE, "portc==NULL\n");
+ continue;
+ }
+
+ oss_audio_outputintr(portc->dev, 0);
+ }
+
+ HwWrite20K1 (devc, SRCIP(i), srcip); /* Acknowledge SRC interrupts for block(i) */
+ }
+ }
+#endif
+
+ if (status & IT_INT)
+ {
+ /*
+ * Interval timer interrupt
+ */
+ sbxfi_portc_t *portc;
+ int i;
+
+ for (i=0;i<devc->nr_outdevs;i++)
+ {
+ portc=&devc->play_portc[i];
+ if (portc->running)
+ oss_audio_outputintr(portc->dev, 0);
+ }
+
+ for (i=0;i<devc->nr_indevs;i++)
+ {
+ portc=&devc->rec_portc[i];
+ if (portc->running)
+ oss_audio_inputintr(portc->dev, 0);
+ }
+
+ set_interval_timer(devc, TIMER_INTERVAL); /* Rearm interval timer */
+ }
+
+ HwWrite20K1 (devc, GIP, status & FI_INT); /* Acknowledge interrupts */
+ return 1;
+}
+
+ /*ARGSUSED*/ static int
+sbxfi_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
+{
+ return OSS_EINVAL;
+}
+
+static mixer_driver_t sbxfi_mixer_driver = {
+ sbxfi_mixer_ioctl
+};
+
+static int
+sbxfi_set_rate (int dev, int arg)
+{
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+#if 0
+ // TODO: Implement support for other rates
+
+ if (arg == 0)
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return portc->rate;
+ }
+
+#else
+ if (portc->direction == PCM_ENABLE_OUTPUT)
+ arg=DEFAULT_PLAY_RATE;
+ else
+ arg=DEFAULT_REC_RATE;
+#endif
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return portc->rate = arg;
+}
+
+static short
+sbxfi_set_channels (int dev, short arg)
+{
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->channels;
+
+ if (arg<2)
+ arg=2;
+ else
+ if (arg > MAX_PLAY_CHANNELS)
+ arg = MAX_PLAY_CHANNELS;
+ arg &= ~1; /* Even number of channels */
+
+ return portc->channels = arg;
+}
+
+static unsigned int
+sbxfi_set_format (int dev, unsigned int arg)
+{
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->fmt;
+
+ return portc->fmt = SUPPORTED_FORMAT;
+}
+
+static int
+sbxfi_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
+{
+ //sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ //sbxfi_devc_t *devc = audio_engines[dev]->devc;
+
+ return OSS_EINVAL;
+}
+
+static void sbxfi_trigger (int dev, int state);
+
+static void
+sbxfi_reset (int dev)
+{
+ sbxfi_trigger (dev, 0);
+}
+
+ /*ARGSUSED*/ static int
+sbxfi_open_input (int dev, int mode, int open_flags)
+{
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ adev_p adev = audio_engines[dev];
+ oss_native_word flags;
+
+ if (mode & OPEN_WRITE)
+ {
+ cmn_err (CE_CONT, "Playback is not possible with %s\n", adev->devnode);
+ return OSS_ENOTSUP;
+ }
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ if (portc->open_mode)
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return OSS_EBUSY;
+ }
+ portc->open_mode = mode;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+ /*ARGSUSED*/ static int
+sbxfi_open_output (int dev, int mode, int open_flags)
+{
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ oss_native_word flags;
+ adev_p adev = audio_engines[dev];
+
+ if (mode == OPEN_READ)
+ {
+ cmn_err (CE_CONT, "Recording is not possible with %s\n", adev->devnode);
+ return OSS_ENOTSUP;
+ }
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ if (portc->open_mode)
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return OSS_EBUSY;
+ }
+
+ portc->open_mode = mode;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+static void
+sbxfi_close (int dev, int mode)
+{
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ portc->open_mode = 0;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+}
+
+ /*ARGSUSED*/ static void
+sbxfi_output_block (int dev, oss_native_word buf, int count, int fragsize,
+ int intrflag)
+{
+}
+
+ /*ARGSUSED*/ static void
+sbxfi_start_input (int dev, oss_native_word buf, int count, int fragsize,
+ int intrflag)
+{
+}
+
+static void
+sbxfi_trigger (int dev, int state)
+{
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ if (portc->state_bits == state) /* No change */
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return;
+ }
+ portc->state_bits = state;
+
+ if (portc->direction == PCM_ENABLE_OUTPUT)
+ if (portc->open_mode & OPEN_WRITE)
+ {
+ if (state & PCM_ENABLE_OUTPUT)
+ {
+ SetupAndStartPlaySRC (devc, portc);
+ portc->running=1;
+ }
+ else
+ {
+ StopPlaySRC (devc, portc);
+ portc->running=0;
+ }
+ }
+
+ if (portc->direction == PCM_ENABLE_INPUT)
+ if (portc->open_mode & OPEN_READ)
+ {
+ if (state & PCM_ENABLE_INPUT)
+ {
+ SetupAndStartRecordSRC (devc, portc);
+ portc->running=1;
+ }
+ else
+ {
+ StopRecordSRC (devc, portc);
+ portc->running=0;
+ }
+ }
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+}
+
+ /*ARGSUSED*/ static int
+sbxfi_prepare_for_input (int dev, int bsize, int bcount)
+{
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ dmap_p dmap = audio_engines[dev]->dmap_in;
+ oss_native_word flags;
+ int i;
+
+ if (audio_engines[dev]->flags & ADEV_NOINPUT)
+ return OSS_EACCES;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ // Fill record buffer to the page table entries.
+ // This need to sync up with setting SRCs start addx
+ for (i=0;i<(bsize*bcount)/4096;i++)
+ devc->pdwPageTable[portc->pgtable_index+i] = dmap->dmabuf_phys + (i*4096);
+
+ InitADC (devc, portc->dwDAChan[0], FALSE);
+
+ // Program input mapper
+ SetupRecordInputMapper (devc, portc);
+
+ // Program I2S
+ SetupRecordFormat (devc);
+ SetupRecordMixer (devc, portc);
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+
+ return 0;
+}
+
+ /*ARGSUSED*/ static int
+sbxfi_prepare_for_output (int dev, int bsize, int bcount)
+{
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ dmap_p dmap = audio_engines[dev]->dmap_out;
+ oss_native_word flags;
+ int i;
+
+ if (audio_engines[dev]->flags & ADEV_NOOUTPUT)
+ return OSS_EACCES;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ // Fill play buffer to the page table entries.
+ // This need to sync up with setting SRCs start addx
+ for (i=0;i<(bsize*bcount)/4096;i++)
+ devc->pdwPageTable[portc->pgtable_index+i] = dmap->dmabuf_phys + (i*4096);
+
+ InitDAC (devc, portc);
+
+ // Program I2S
+ SetupPlayFormat (devc, portc);
+ SetupPlayMixer (devc, portc);
+
+ // Program input mapper
+ SetupPlayInputMapper (devc, portc);
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+sbxfi_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
+{
+ sbxfi_devc_t *devc = audio_engines[dev]->devc;
+ sbxfi_portc_t *portc = audio_engines[dev]->portc;
+ int pos;
+
+ pos = HwRead20K1 (devc, SRCCA(portc->SrcChan)) & 0x03ffffff;
+
+ if (pos>=128)
+ pos -= 128; /* The pointer is always 128 bytes ahead */
+
+ pos -= portc->pgtable_index*4096;
+
+ return pos;
+}
+
+static audiodrv_t sbxfi_output_driver = {
+ sbxfi_open_output,
+ sbxfi_close,
+ sbxfi_output_block,
+ sbxfi_start_input,
+ sbxfi_ioctl,
+ sbxfi_prepare_for_input,
+ sbxfi_prepare_for_output,
+ sbxfi_reset,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ sbxfi_trigger,
+ sbxfi_set_rate,
+ sbxfi_set_format,
+ sbxfi_set_channels,
+ NULL,
+ NULL,
+ NULL, /* check input */
+ NULL, /* sbxfi_check_output */
+ NULL, /* sbxfi_alloc_buffer */
+ NULL, /* sbxfi_free_buffer */
+ NULL,
+ NULL,
+ sbxfi_get_buffer_pointer
+};
+
+static audiodrv_t sbxfi_input_driver = {
+ sbxfi_open_input,
+ sbxfi_close,
+ sbxfi_output_block,
+ sbxfi_start_input,
+ sbxfi_ioctl,
+ sbxfi_prepare_for_input,
+ sbxfi_prepare_for_output,
+ sbxfi_reset,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ sbxfi_trigger,
+ sbxfi_set_rate,
+ sbxfi_set_format,
+ sbxfi_set_channels,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* sbxfi_alloc_buffer */
+ NULL, /* sbxfi_free_buffer */
+ NULL,
+ NULL,
+ sbxfi_get_buffer_pointer
+};
+
+static int
+init_play_device (sbxfi_devc_t * devc,
+ char *name, int dev_flags)
+{
+ int opts, dev, formats;
+ char tmp[80];
+ sbxfi_portc_t *portc = NULL;
+ adev_p adev;
+
+ sprintf (tmp, "%s %s", devc->name, name);
+
+ if (devc->nr_outdevs > MAX_OUTPUTDEVS)
+ {
+ cmn_err (CE_CONT, "Too many audio devices\n");
+ return -1;
+ }
+
+ opts = ADEV_AUTOMODE | ADEV_NOINPUT;
+
+ formats = SUPPORTED_FORMAT;
+
+ if ((dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
+ devc->osdev,
+ devc->osdev,
+ tmp,
+ &sbxfi_output_driver,
+ sizeof (audiodrv_t),
+ opts, formats, devc, -1)) < 0)
+ {
+ return -1;
+ }
+
+ if (devc->first_dev == -1)
+ {
+ devc->first_dev = dev;
+ }
+ adev = audio_engines[dev];
+
+ portc = &devc->play_portc[devc->nr_outdevs];
+
+ adev->portc = portc;
+ adev->devc = devc;
+ adev->mixer_dev = devc->mixer_dev;
+ adev->rate_source = devc->first_dev;
+ adev->min_rate = 48000;
+ adev->max_rate = 92600;
+ adev->min_block=4096;
+ adev->dmabuf_maxaddr = MEMLIMIT_ISA;
+
+ portc->dev = dev;
+ portc->open_mode = 0;
+ portc->fmt = SUPPORTED_FORMAT;
+ portc->dev_flags = dev_flags;
+ portc->state_bits = 0;
+ portc->direction = PCM_ENABLE_OUTPUT;
+
+ portc->rate = DEFAULT_PLAY_RATE;
+
+ // use the following SRC channels for Play
+ portc->SrcChan = devc->next_src;
+ devc->next_src += MAX_PLAY_CHANNELS;
+ devc->src_to_portc[portc->SrcChan]=portc;
+
+ portc->dwDAChan[0] = I2SA_L;
+ portc->dwDAChan[1] = I2SA_R;
+#if MAX_PLAY_CHANNELS>2
+ portc->dwDAChan[2] = I2SB_L;
+ portc->dwDAChan[3] = I2SB_R;
+ portc->dwDAChan[4] = I2SC_L;
+ portc->dwDAChan[5] = I2SC_R;
+#endif
+
+ portc->vol_left=portc->vol_right=MIXER_VOLSTEPS;
+
+ adev->min_channels = 2;
+ adev->max_channels = MAX_PLAY_CHANNELS;
+
+ portc->pgtable_index = devc->next_pg;
+ devc->next_pg += 128/4; // Up to 128k for buffer
+
+ devc->nr_outdevs++;
+
+ return dev;
+}
+
+static int
+init_rec_device (sbxfi_devc_t * devc,
+ char *name, int dev_flags)
+{
+ int opts, dev, formats;
+ char tmp[80];
+ sbxfi_portc_t *portc = NULL;
+ adev_p adev;
+
+ sprintf (tmp, "%s %s", devc->name, name);
+
+ if (devc->nr_indevs > MAX_INPUTDEVS)
+ {
+ cmn_err (CE_CONT, "Too many audio devices\n");
+ return -1;
+ }
+
+ opts = ADEV_AUTOMODE | ADEV_NOOUTPUT;
+
+ formats = SUPPORTED_FORMAT;
+
+ if ((dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
+ devc->osdev,
+ devc->osdev,
+ tmp,
+ &sbxfi_input_driver,
+ sizeof (audiodrv_t),
+ opts, formats, devc, -1)) < 0)
+ {
+ return -1;
+ }
+
+ if (devc->first_dev == -1)
+ {
+ devc->first_dev = dev;
+ }
+ adev = audio_engines[dev];
+
+ portc = &devc->rec_portc[devc->nr_indevs];
+
+ adev->portc = portc;
+ adev->devc = devc;
+ adev->mixer_dev = devc->mixer_dev;
+ adev->rate_source = devc->first_dev;
+ adev->min_rate = 48000;
+ adev->max_rate = 96000;
+ adev->min_block=4096;
+ adev->dmabuf_maxaddr = MEMLIMIT_ISA;
+
+ portc->dev = dev;
+ portc->open_mode = 0;
+ portc->fmt = SUPPORTED_FORMAT;
+ portc->dev_flags = dev_flags;
+ portc->state_bits = 0;
+ portc->direction = PCM_ENABLE_INPUT;
+
+ portc->rate = DEFAULT_REC_RATE;
+
+ // use the following SRC channels for record
+ portc->SrcChan = devc->next_src;
+ devc->next_src += 2;
+ devc->src_to_portc[portc->SrcChan]=portc;
+
+ portc->dwDAChan[0] = ADC_SRC_LINEIN;
+
+ portc->vol_left=portc->vol_right=MIXER_VOLSTEPS;
+
+ adev->min_channels = 2;
+ adev->max_channels = 2;
+
+ portc->pgtable_index = devc->next_pg;
+ devc->next_pg += 128/4; // Up to 128k for buffer
+
+ devc->nr_indevs++;
+
+ return dev;
+}
+
+static int
+sbxfi_set_playvol (int dev, int ctrl, unsigned int cmd, int value)
+{
+ sbxfi_devc_t *devc = mixer_devs[dev]->devc;
+ sbxfi_portc_t *portc;
+ int left, right;
+
+ if (ctrl<0 || ctrl >= devc->nr_outdevs)
+ return OSS_ENXIO;
+ portc = &devc->play_portc[ctrl];
+
+ if (cmd == SNDCTL_MIX_READ)
+ {
+ return portc->vol_left | (portc->vol_right << 16);
+ }
+
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ left = value & 0xffff;
+ right = (value>>16) & 0xffff;
+
+ if (left > MIXER_VOLSTEPS)
+ left=MIXER_VOLSTEPS;
+ if (right > MIXER_VOLSTEPS)
+ right=MIXER_VOLSTEPS;
+
+ portc->vol_left=left;
+ portc->vol_right=right;
+ if (portc->running)
+ SetupPlayMixer(devc, portc);
+
+ return portc->vol_left | (portc->vol_right << 16);
+ }
+
+ return OSS_EINVAL;
+}
+
+static int
+sbxfi_set_recvol (int dev, int ctrl, unsigned int cmd, int value)
+{
+ sbxfi_devc_t *devc = mixer_devs[dev]->devc;
+ sbxfi_portc_t *portc;
+ int left, right;
+
+ if (ctrl<0 || ctrl >= devc->nr_indevs)
+ return OSS_ENXIO;
+ portc = &devc->rec_portc[ctrl];
+
+ if (cmd == SNDCTL_MIX_READ)
+ {
+ return portc->vol_left | (portc->vol_right << 16);
+ }
+
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ left = value & 0xffff;
+ right = (value>>16) & 0xffff;
+
+ if (left > MIXER_VOLSTEPS)
+ left=MIXER_VOLSTEPS;
+ if (right > MIXER_VOLSTEPS)
+ right=MIXER_VOLSTEPS;
+
+ portc->vol_left=left;
+ portc->vol_right=right;
+ if (portc->running)
+ SetupRecordMixer(devc, portc);
+
+ return portc->vol_left | (portc->vol_right << 16);
+ }
+
+ return OSS_EINVAL;
+}
+
+static int
+sbxfi_set_recsrc (int dev, int ctrl, unsigned int cmd, int value)
+{
+ sbxfi_devc_t *devc = mixer_devs[dev]->devc;
+ sbxfi_portc_t *portc;
+
+ if (ctrl<0 || ctrl >= devc->nr_indevs)
+ return OSS_ENXIO;
+ portc = &devc->rec_portc[ctrl];
+
+ if (cmd == SNDCTL_MIX_READ)
+ {
+ return portc->dwDAChan[0];
+ }
+
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ if (value<0 || value>ADC_SRC_NONE)
+ return portc->dwDAChan[0];
+
+ return portc->dwDAChan[0]=value;
+ }
+
+ return OSS_EINVAL;
+}
+
+static int
+sbxfi_mix_init (int dev)
+{
+ int root=0, ctl;
+
+ if ((ctl = mixer_ext_create_control (dev, root,
+ 0, sbxfi_set_playvol,
+ MIXT_STEREOSLIDER16,
+ "play", MIXER_VOLSTEPS,
+ MIXF_PCMVOL | MIXF_READABLE |
+ MIXF_WRITEABLE | MIXF_CENTIBEL)) <
+ 0)
+ return ctl;
+
+ if ((ctl = mixer_ext_create_control (dev, root,
+ 0, sbxfi_set_recvol,
+ MIXT_STEREOSLIDER16,
+ "rec", MIXER_VOLSTEPS,
+ MIXF_RECVOL | MIXF_READABLE |
+ MIXF_WRITEABLE | MIXF_CENTIBEL)) <
+ 0)
+ return ctl;
+
+ if ((ctl = mixer_ext_create_control (dev, root,
+ 0, sbxfi_set_recsrc,
+ MIXT_ENUM,
+ "recsrc", 5,
+ MIXF_READABLE |
+ MIXF_WRITEABLE | MIXF_CENTIBEL)) <
+ 0)
+ return ctl;
+
+ mixer_ext_set_strings (dev, ctl, "mic line video aux none", 0);
+
+ return 0;
+}
+
+int
+oss_sbxfi_attach (oss_device_t * osdev)
+{
+ unsigned short pci_command, vendor, device, revision;
+ unsigned short subvendor, subdevice;
+ int pdev, rdev;
+ extern int sbxfi_type;
+
+ sbxfi_devc_t *devc;
+ sbxfi_portc_t *portc;
+
+ if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL)
+ {
+ cmn_err (CE_WARN, "Out of memory\n");
+ return 0;
+ }
+
+ memset (devc, 0, sizeof (*devc));
+
+ portc = &devc->play_portc[0];
+
+ devc->osdev = osdev;
+ osdev->devc = devc;
+ devc->name = "Sound Blaster X-Fi";
+
+ pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
+ pci_read_config_word (osdev, PCI_DEVICE_ID, &device);
+
+ DDB (cmn_err
+ (CE_CONT, "oss_sbxfi_attach(Vendor %x, device %x)\n", vendor, device));
+
+ if (vendor != PCI_VENDOR_CREATIVE ||
+ (device != CREATIVE_SBXFI_K1 && device != CREATIVE_SBXFI_K2 &&
+ device != CREATIVE_SBXFI_E))
+ {
+ cmn_err (CE_WARN, "Hardware not recognized (vendor=%x, dev=%x)\n",
+ vendor, device);
+ return 0;
+ }
+ MUTEX_INIT (osdev, devc->mutex, MH_DRV);
+ MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1);
+
+ pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &subvendor);
+ pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &subdevice);
+ pci_read_config_word (osdev, PCI_REVISION_ID, &revision);
+
+ devc->wVendorID = vendor;
+ devc->wDeviceID = device;
+ devc->wSubsystemVendorID = subvendor;
+ devc->wSubsystemID = subdevice;
+ devc->wChipRevision = revision;
+
+ switch (sbxfi_type)
+ {
+ case 1:
+ devc->name = "Sound Blaster X-Fi (SB046x/067x/076x)";
+ devc->hw_family = HW_ORIG;
+ break;
+
+ case 2:
+ devc->name = "Sound Blaster X-Fi (SB073x)";
+ devc->hw_family = HW_073x;
+ break;
+
+ case 3:
+ devc->name = "Sound Blaster X-Fi (SB055x)";
+ devc->hw_family = HW_055x;
+ break;
+
+ case 4:
+ devc->name = "Sound Blaster X-Fi (UAA)";
+ devc->hw_family = HW_UAA;
+ break;
+
+ case 5:
+ devc->name = "Sound Blaster X-Fi (SB076x)";
+ devc->hw_family = HW_0760;
+ break;
+
+ case 6:
+ devc->name = "Sound Blaster X-Fi (SB0880-1)";
+ devc->hw_family = HW_08801;
+ break;
+
+ case 7:
+ devc->name = "Sound Blaster X-Fi (SB0880-2)";
+ devc->hw_family = HW_08802;
+ break;
+
+ case 8:
+ devc->name = "Sound Blaster X-Fi (SB0880-3)";
+ devc->hw_family = HW_08803;
+ break;
+
+ case 0:
+ default:
+ devc->hw_family = 0;
+ break;
+ }
+
+ if (!devc->hw_family && device == CREATIVE_SBXFI_K1) // EMU20K1 models
+ switch (subdevice)
+ {
+ case 0x0021: /* SB0460 */
+ case 0x0023:
+ case 0x0024:
+ case 0x0025:
+ case 0x0026:
+ case 0x0027:
+ case 0x0028:
+ case 0x002a:
+ case 0x002b:
+ case 0x002c:
+ case 0x002d:
+ case 0x002e:
+ case 0x0032:
+ case 0x0033:
+ case 0x0034: /* This is actually Auzentech Prelude (subvendor 415a) */
+ /*
+ * Original X-Fi hardware revision (SB046x/067x/076x)
+ */
+ devc->name = "Sound Blaster X-Fi (SB046x/067x/076x)";
+ devc->hw_family = HW_ORIG;
+ break;
+
+ case 0x0029:
+ case 0x0031:
+ devc->name = "Sound Blaster X-Fi (SB073x)";
+ devc->hw_family = HW_073x;
+ break;
+
+ case 0x0022:
+ case 0x002f:
+ devc->name = "Sound Blaster X-Fi (SB055x)";
+ devc->hw_family = HW_055x;
+ break;
+
+ default:
+ if (subdevice >= 0x6000 && subdevice <= 0x6fff) /* "Vista compatible" HW */
+ {
+ devc->name = "Sound Blaster X-Fi (UAA)";
+ devc->hw_family = HW_UAA;
+ }
+ }
+
+ if (!devc->hw_family && device == CREATIVE_SBXFI_K2) // EMU 20K2 models
+ switch (subdevice)
+ {
+ case PCI_SUBDEVICE_ID_CREATIVE_SB0760:
+ devc->name = "Sound Blaster X-Fi (SB076x)";
+ devc->hw_family = HW_0760;
+ break;
+
+ case PCI_SUBDEVICE_ID_CREATIVE_SB08801:
+ devc->name = "Sound Blaster X-Fi (SB0880-1)";
+ devc->hw_family = HW_08801;
+ break;
+
+ case PCI_SUBDEVICE_ID_CREATIVE_SB08802:
+ devc->name = "Sound Blaster X-Fi (SB0880-2)";
+ devc->hw_family = HW_08802;
+ break;
+
+ case PCI_SUBDEVICE_ID_CREATIVE_SB08803:
+ devc->name = "Sound Blaster X-Fi (SB0880-3)";
+ devc->hw_family = HW_08803;
+ break;
+
+ default:
+ devc->name = "Sound Blaster X-Fi (20K2)";
+ devc->hw_family = HW_UAA; // Just a wild guess
+ }
+
+ if (!devc->hw_family && device == CREATIVE_SBXFI_E) // PCI-e models
+ {
+ devc->name = "Sound Blaster X-Fi (PCI-e)";
+ devc->hw_family = HW_UAA; // Just a wild guess
+ }
+
+
+#if 1
+// Temporary hacking until proper 20K2 support is in place
+ if (devc->hw_family > HW_UAA) devc->hw_family = HW_UAA;
+#endif
+
+ oss_register_device (osdev, devc->name);
+
+ pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
+ pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEMORY;
+ pci_write_config_word (osdev, PCI_COMMAND, pci_command);
+
+ if ((osdev->hw_info = PMALLOC (osdev, 200)) != NULL)
+ {
+ sprintf (osdev->hw_info, "PCI device %04x:%04x, subdevice %04x:%04x\n",
+ vendor, device, subvendor, subdevice);
+ }
+
+ devc->interrupt_count=0;
+ if (oss_register_interrupts (devc->osdev, 0, sbxfi_intr, NULL) < 0)
+ {
+ cmn_err (CE_WARN, "Unable to install interrupt handler\n");
+ return 0;
+ }
+
+ // Detect and Configure X-Fi PCI config space.
+ // Obtain the resource configuration from PCI config space.
+ if (!DetectAndConfigureHardware (devc))
+ {
+ cmn_err (CE_WARN, "Cannot configure X-Fi hardware...\n");
+ return 0;
+ }
+
+ if (IsVistaCompatibleHardware (devc))
+ {
+ // Switch to audio core to X-Fi core.
+ SwitchToXFiCore (devc);
+ }
+
+ // Initialize hardware. This include setup the PLL etc.
+ if (InitHardware (devc) != CTSTATUS_SUCCESS)
+ {
+ cmn_err (CE_WARN, "Init Hardware failed...\n");
+ return 0;
+ }
+
+ devc->dwPageTableSize = 1024; /* For up to 4M of memory */
+ devc->pdwPageTable = CONTIG_MALLOC (devc->osdev,
+ devc->dwPageTableSize,
+ MEMLIMIT_ISA, &devc->dwPTBPhysAddx, devc->pgtable_dma_handle);
+
+ HwWrite20K1 (devc, PTPALX, devc->dwPTBPhysAddx);
+ HwWrite20K1 (devc, PTPAHX, 0);
+
+ HwWrite20K1 (devc, TRNCTL, 0x13);
+ HwWrite20K1 (devc, TRNIS, 0x200c01);
+
+ HwWrite20K1 (devc, GIE, FI_INT); /* Enable "forced" interrupts */
+ HwWrite20K1 (devc, GIP, FI_INT); /* Trigger forced interrupt */
+
+ oss_udelay(1000);
+ if (devc->interrupt_count==0)
+ cmn_err(CE_WARN, "Interrupts don't seem to be working.\n");
+
+ set_interval_timer(devc, TIMER_INTERVAL);
+
+/*
+ * Disable FI and enable selected global interrupts
+ * (SRC, Interval Timer).
+ */
+ HwWrite20K1 (devc, GIE, SRC_INT | IT_INT);
+
+ if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
+ devc->osdev,
+ devc->osdev,
+ devc->name,
+ &sbxfi_mixer_driver,
+ sizeof (mixer_driver_t),
+ devc)) >= 0)
+ {
+ mixer_devs[devc->mixer_dev]->hw_devc = devc;
+ mixer_devs[devc->mixer_dev]->priority = 1; /* Possible default mixer candidate */
+ mixer_ext_set_init_fn (devc->mixer_dev, sbxfi_mix_init, 10);
+ }
+
+ devc->first_dev=-1; /* Not assigned */
+ pdev = init_play_device (devc, "output", 0);
+ rdev = init_rec_device (devc, "input", 0);
+#ifdef CONFIG_OSS_VMIX
+ if (pdev != -1)
+ {
+ vmix_attach_audiodev (devc->osdev, pdev, rdev, 0);
+ }
+#endif
+
+#if 0
+ // Initialize ADC
+ InitADC (devc, ADC_SRC_LINEIN, FALSE);
+#endif
+
+ return 1;
+}
+
+int
+oss_sbxfi_detach (oss_device_t * osdev)
+{
+ sbxfi_devc_t *devc = (sbxfi_devc_t *) osdev->devc;
+
+ if (oss_disable_device (osdev) < 0)
+ return 0;
+
+ HwWrite20K1 (devc, GIE, 0); /* Disable global interrupts */
+ oss_unregister_interrupts (devc->osdev);
+
+ HwWrite20K1 (devc, PTPALX, 0);
+ if (devc->pdwPageTable != NULL)
+ {
+ CONTIG_FREE (devc->osdev, devc->pdwPageTable, devc->dwPageTableSize, devc->pgtable_dma_handle);
+ devc->pdwPageTable = NULL;
+ }
+ oss_unregister_device (osdev);
+ MUTEX_CLEANUP (devc->mutex);
+ MUTEX_CLEANUP (devc->low_mutex);
+ return 1;
+}