summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_audigyls
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_audigyls')
-rw-r--r--kernel/drv/oss_audigyls/.devices1
-rw-r--r--kernel/drv/oss_audigyls/.name1
-rw-r--r--kernel/drv/oss_audigyls/.params6
-rw-r--r--kernel/drv/oss_audigyls/oss_audigyls.c1860
-rw-r--r--kernel/drv/oss_audigyls/oss_audigyls.man45
5 files changed, 1913 insertions, 0 deletions
diff --git a/kernel/drv/oss_audigyls/.devices b/kernel/drv/oss_audigyls/.devices
new file mode 100644
index 0000000..fee016d
--- /dev/null
+++ b/kernel/drv/oss_audigyls/.devices
@@ -0,0 +1 @@
+oss_audigyls pci1102,7 Sound Blaster Audigy LS / Live7.1
diff --git a/kernel/drv/oss_audigyls/.name b/kernel/drv/oss_audigyls/.name
new file mode 100644
index 0000000..c68179e
--- /dev/null
+++ b/kernel/drv/oss_audigyls/.name
@@ -0,0 +1 @@
+Sound Blaster Audigy LS / Live7.1
diff --git a/kernel/drv/oss_audigyls/.params b/kernel/drv/oss_audigyls/.params
new file mode 100644
index 0000000..7714edd
--- /dev/null
+++ b/kernel/drv/oss_audigyls/.params
@@ -0,0 +1,6 @@
+int audigyls_spdif_enable=0;
+/*
+ * Set the Orange Jack to SPDIF or Analog output
+ * Values 1=SPDIF 0=Analog (side-surround) Default=0
+ */
+
diff --git a/kernel/drv/oss_audigyls/oss_audigyls.c b/kernel/drv/oss_audigyls/oss_audigyls.c
new file mode 100644
index 0000000..1639e6c
--- /dev/null
+++ b/kernel/drv/oss_audigyls/oss_audigyls.c
@@ -0,0 +1,1860 @@
+/*
+ * Purpose: Driver for Creative Audigy LS audio controller
+ *
+ * This sound card has been sold under many different names.
+ */
+/*
+ *
+ * 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_audigyls_cfg.h"
+#include "oss_pci.h"
+#include "ac97.h"
+#include "midi_core.h"
+#include "remux.h"
+
+#define DEFAULT_RATE 48000
+
+#undef USE_ITIMER
+
+#define PCI_VENDOR_ID_CREATIVE 0x1102
+#define PCI_DEVICE_ID_CREATIVE_AUDIGYLS 0x0007
+
+/*
+ * 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 SA 0x040
+#define SCS3 0x041
+#define SCS0 0x042
+#define SCS1 0x043
+#define SCS2 0x044
+#define SPC 0x045
+#define WMARK 0x046
+#define SPSC 0x049
+#define RCD 0x050 /* 0x50-0z5f */
+#define P17RECSEL 0x060
+#define P17RECVOLL 0x061
+#define P17RECVOLH 0x062
+
+#define HMIXMAP_SPDIF 0x63
+#define SMIXMAP_SPDIF 0x64
+#define MIXCTL_SPDIF 0x65
+#define MIXVOL_SPDIF 0x66
+#define HMIXMAP_I2S 0x67
+#define SMIXMAP_I2S 0x68
+#define MIXCTL_I2S 0x69
+#define MIXVOL_I2S 0x6a
+
+/* MIDI UART */
+#define MUDATA 0x06c
+#define MUCMDA 0x06d
+#define MUDATB 0x06e
+#define MUCMDB 0x06f
+
+#define SRT 0x070
+#define SRCTL 0x071
+#define AUDCTL 0x072
+#define CHIP_ID 0x074
+#define AINT_ENABLE 0x075
+#define AINT_STATUS 0x076
+#define Wall192 0x077
+#define Wall441 0x078
+#define IT 0x079
+#define SPI 0x07a
+#define I2C_A 0x07b
+#define I2C_0 0x07c
+#define I2C_1 0x07d
+
+/*
+ * Global Interrupt bits
+ */
+
+#define IE 0x0c
+#define INTR_PCI (1<< 0)
+#define INTR_TXA (1<< 1)
+#define INTR_RXA (1<< 2)
+#define INTR_IT1 (1<< 3)
+#define INTR_IT2 (1<< 4)
+#define INTR_SS_ (1<< 5)
+#define INTR_SRT (1<< 6)
+#define INTR_GP (1<< 7)
+#define INTR_AI (1<< 8)
+#define INTR_I2Cdac (1<< 9)
+#define INTR_I2CEE (1<< 10)
+#define INTR_SPI (1<< 11)
+#define INTR_SPF (1<< 12)
+#define INTR_SUO (1<< 13)
+#define INTR_SUI (1<< 14)
+#define INTR_TXB (1<< 16)
+#define INTR_RXB (1<< 17)
+
+/*
+ * Audio interrupt bits
+ */
+
+#define AI_PFH (1<< 0)
+#define AI_PFF (1<< 4)
+#define AI_TFH (1<< 8)
+#define AI_TFF (1<< 12)
+#define AI_RFH (1<< 16)
+#define AI_RFF (1<< 20)
+#define AI_EAI (1<< 24)
+
+
+#define MAX_PORTC 3
+
+extern int audigyls_spdif_enable;
+
+typedef struct
+{
+ int audio_dev;
+ int play_port;
+ int rec_port;
+ int open_mode;
+ int trigger_bits;
+ int audio_enabled;
+ int channels;
+ int bits;
+ int speed;
+#ifdef USE_ITIMER
+ int frag_192khz;
+#endif
+ int port_type;
+ int play_ptr, rec_ptr;
+}
+audigyls_portc;
+
+typedef struct
+{
+ oss_device_t *osdev;
+ oss_native_word base;
+ oss_mutex_t mutex;
+ oss_mutex_t low_mutex;
+ char *card_name;
+ unsigned int subvendor;
+ int rec_src; /* record channel src spdif/i2s/ac97/src */
+#define RECSEL_SPDIFOUT 0
+#define RECSEL_I2SOUT 1
+#define RECSEL_SPDIFIN 2
+#define RECSEL_I2SIN 3
+#define RECSEL_AC97 4
+#define RECSEL_SRC 5
+
+ int mixer_dev;
+ ac97_devc ac97devc;
+ int has_ac97;
+ int spread; /* copy front to surr/center channels */
+ int loopback; /* record channel input from /dev/dspXX */
+ int input_source; /* input from mic/line/aux/etc */
+ int captmon; /* hear what you record*/
+ int fbvol; /* recording monitor volume */
+/*
+ * UART
+ */
+ oss_midi_inputbyte_t midi_input_intr;
+ int midi_opened, midi_disabled;
+ volatile unsigned char input_byte;
+ int midi_dev;
+ int mpu_attached;
+ int playvol[4];
+ int recvol;
+ audigyls_portc portc[MAX_PORTC];
+
+}
+audigyls_devc;
+
+
+static void audigylsuartintr (audigyls_devc * devc);
+
+static unsigned int
+read_reg (audigyls_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 (audigyls_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 int
+audigyls_ac97_read (void *devc_, int wAddr)
+{
+ audigyls_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;
+ if (i == 10000)
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return OSS_EIO;
+ }
+ dtemp = INW (devc->osdev, devc->base + 0x1c);
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+
+ return dtemp & 0xffff;
+}
+
+static int
+audigyls_ac97_write (void *devc_, int wAddr, int wData)
+{
+ audigyls_devc *devc = devc_;
+ oss_native_word flags;
+ int i;
+
+ 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;
+ if (i == 10000)
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return OSS_EIO;
+ }
+ OUTW (devc->osdev, wData, devc->base + 0x1c);
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+
+ return 0;
+}
+
+static void
+check_recording_intr (audigyls_devc * devc, audigyls_portc * portc)
+{
+#if 1
+ int pos, n = 0;
+ dmap_p dmap;
+
+ dmap = audio_engines[portc->audio_dev]->dmap_in;
+
+ pos = read_reg (devc, CRFA, portc->rec_port);
+ pos /= dmap->fragment_size;
+ while (dmap_get_qtail (dmap) != pos && n++ < dmap->nfrags)
+#endif
+ oss_audio_inputintr (portc->audio_dev, 0);
+}
+
+static void
+check_playback_intr (audigyls_devc * devc, audigyls_portc * portc)
+{
+#if 1
+ int pos, n = 0;
+ dmap_p dmap;
+
+ dmap = audio_engines[portc->audio_dev]->dmap_out;
+
+ pos = read_reg (devc, CPFA, portc->play_port);
+ pos /= dmap->fragment_size;
+ while (dmap_get_qhead (dmap) != pos && n++ < dmap->nfrags)
+#endif
+ oss_audio_outputintr (portc->audio_dev, 0);
+}
+
+static int
+audigylsintr (oss_device_t * osdev)
+{
+ int serviced = 0;
+ unsigned int status;
+ unsigned int astatus = 0;
+ int i;
+ audigyls_devc *devc = osdev->devc;
+ audigyls_portc *portc;
+ oss_native_word flags;
+
+ flags = 0; /* To fix compiler warnings about unused variable */
+ MUTEX_ENTER (devc->mutex, flags);
+
+ status = INL (devc->osdev, devc->base + 0x08);
+
+ if (status & INTR_RXA) /* MIDI RX interrupt */
+ {
+ audigylsuartintr (devc);
+ }
+
+ if (status & INTR_AI)
+ {
+ astatus = read_reg (devc, AINT_STATUS, 0);
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ portc = &devc->portc[i];
+
+ if ((portc->trigger_bits & PCM_ENABLE_OUTPUT) &&
+ (astatus & ((AI_PFF | AI_PFH) << portc->play_port)))
+ {
+ dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_out;
+
+ if (astatus & (AI_PFF << portc->play_port))
+ portc->play_ptr = 0;
+ if (astatus & (AI_PFH << portc->play_port))
+ portc->play_ptr = dmap->bytes_in_use / 2;
+
+ oss_audio_outputintr (portc->audio_dev, 0);
+ }
+ if ((portc->trigger_bits & PCM_ENABLE_INPUT) &&
+ (astatus & ((AI_RFF | AI_RFH) << portc->rec_port)))
+ {
+ dmap_t *dmap = audio_engines[portc->audio_dev]->dmap_in;
+
+ if (astatus & (AI_RFF << portc->rec_port))
+ portc->rec_ptr = 0;
+ if (astatus & (AI_RFH << portc->rec_port))
+ portc->rec_ptr = dmap->bytes_in_use / 2;
+
+ oss_audio_inputintr (portc->audio_dev, 0);
+ }
+ }
+ write_reg (devc, AINT_STATUS, 0, astatus);
+ }
+
+ if (status & INTR_IT1)
+ {
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ portc = &devc->portc[i];
+
+ if ((portc->trigger_bits & PCM_ENABLE_OUTPUT))
+ check_playback_intr (devc, portc);
+
+ if ((portc->trigger_bits & PCM_ENABLE_INPUT))
+ check_recording_intr (devc, portc);
+ }
+ }
+
+ serviced = 1;
+ OUTL (devc->osdev, status, devc->base + 0x08); /* Acknowledge */
+ MUTEX_EXIT (devc->mutex, flags);
+ return serviced;
+}
+
+static int
+audigyls_set_rate (int dev, int arg)
+{
+ audigyls_portc *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->speed;
+
+ if (audio_engines[dev]->flags & ADEV_FIXEDRATE)
+ arg = DEFAULT_RATE;
+
+ if (arg != 44100 && arg != 48000 && arg != 96000 && arg != 192000)
+ arg = 48000;
+
+ portc->speed = arg;
+ return portc->speed;
+}
+
+static short
+audigyls_set_channels (int dev, short arg)
+{
+ audigyls_portc *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->channels;
+
+ if ((arg == 1))
+ arg = 2;
+
+ 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
+audigyls_set_format (int dev, unsigned int arg)
+{
+ audigyls_portc *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->bits;
+
+ if (arg != AFMT_AC3 && arg != AFMT_S16_LE)
+ return portc->bits = AFMT_S16_LE;
+
+ return portc->bits = arg;
+}
+
+/*ARGSUSED*/
+static int
+audigyls_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
+{
+ return OSS_EINVAL;
+}
+
+static void audigyls_trigger (int dev, int state);
+
+static void
+audigyls_reset (int dev)
+{
+ audigyls_trigger (dev, 0);
+}
+
+/*ARGSUSED*/
+static int
+audigyls_open (int dev, int mode, int open_flags)
+{
+ audigyls_portc *portc = audio_engines[dev]->portc;
+ audigyls_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;
+ portc->play_ptr = portc->rec_ptr = 0;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+static void
+audigyls_close (int dev, int mode)
+{
+ audigyls_portc *portc = audio_engines[dev]->portc;
+
+ audigyls_reset (dev);
+ portc->open_mode = 0;
+ portc->audio_enabled &= ~mode;
+}
+
+/*ARGSUSED*/
+static void
+audigyls_output_block (int dev, oss_native_word buf, int count, int fragsize,
+ int intrflag)
+{
+}
+
+/*ARGSUSED*/
+static void
+audigyls_start_input (int dev, oss_native_word buf, int count, int fragsize,
+ int intrflag)
+{
+ audigyls_portc *portc = audio_engines[dev]->portc;
+
+ portc->audio_enabled |= PCM_ENABLE_INPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_INPUT;
+}
+
+#ifdef USE_ITIMER
+static void
+check_itimer (audigyls_devc * devc)
+{
+ int i;
+ unsigned int t = 0x1fffffff;
+ audigyls_portc *portc;
+ int tmp;
+
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ portc = &devc->portc[i];
+ if (portc->frag_192khz != 0 && portc->frag_192khz < t)
+ t = portc->frag_192khz;
+ }
+ if (t == 0x1fffffff) /* No audio devices active */
+ {
+ tmp = INL (devc->osdev, devc->base + IE);
+ tmp &= ~INTR_IT1;
+ OUTL (devc->osdev, tmp, devc->base + IE);
+ }
+ else
+ {
+ t /= 16;
+ if (t < 1)
+ t = 1;
+ write_reg (devc, IT, 0, t);
+ tmp = INL (devc->osdev, devc->base + IE);
+ tmp |= INTR_IT1;
+ OUTL (devc->osdev, tmp, devc->base + IE);
+ }
+}
+
+static void
+adjust_itimer (audigyls_devc * devc, audigyls_portc * portc, dmap_p dmap)
+{
+ unsigned int t;
+
+ /* Compute byte rate */
+
+ t = portc->speed * portc->channels;
+
+ switch (portc->bits)
+ {
+ case AFMT_S16_LE:
+ case AFMT_S16_BE:
+ case AFMT_AC3:
+ t *= 2;
+ break;
+
+ case AFMT_S32_LE:
+ case AFMT_S32_BE:
+ case AFMT_S24_LE:
+ case AFMT_S24_BE:
+ t *= 4;
+ break;
+ }
+
+/* Compute the number of 192kHz ticks per fragment */
+
+ t = (dmap->fragment_size * 192000) / t; /* msecs / fragment */
+ if (t < 1)
+ t = 1;
+
+ portc->frag_192khz = t;
+ check_itimer (devc);
+}
+#endif
+
+static void
+audigyls_trigger (int dev, int state)
+{
+ audigyls_devc *devc = audio_engines[dev]->devc;
+ audigyls_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))
+ {
+ tmp = read_reg (devc, SA, 0);
+ tmp |= 1 << portc->play_port;
+ write_reg (devc, SA, 0, tmp);
+#ifdef USE_ITIMER
+ check_itimer (devc);
+#else
+ tmp = read_reg (devc, AINT_ENABLE, 0);
+ tmp |= ((AI_PFH | AI_PFF) << portc->play_port);
+ write_reg (devc, AINT_ENABLE, 0, tmp);
+#endif
+ 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;
+#ifdef USE_ITIMER
+ portc->frag_192khz = 0;
+ check_itimer (devc);
+#endif
+ /* Disable Play channel */
+ tmp = read_reg (devc, SA, 0);
+ tmp &= ~(1 << portc->play_port);
+ write_reg (devc, SA, 0, tmp);
+#ifndef USE_ITIMER
+ tmp = read_reg (devc, AINT_ENABLE, 0);
+ tmp &= ~((AI_PFH | AI_PFF) << portc->play_port);
+ write_reg (devc, AINT_ENABLE, 0, tmp);
+#endif
+ }
+ }
+ }
+
+ 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 << portc->rec_port; /* enable record */
+ write_reg (devc, SA, 0, tmp);
+#ifdef USE_ITIMER
+ check_itimer (devc);
+#else
+ tmp = read_reg (devc, AINT_ENABLE, 0);
+ tmp |= ((AI_RFF | AI_RFH) << portc->rec_port);
+ write_reg (devc, AINT_ENABLE, 0, tmp);
+#endif
+ 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;
+#ifdef USE_ITIMER
+ portc->frag_192khz = 0;
+ check_itimer (devc);
+#endif
+ /* disable channel */
+ tmp = read_reg (devc, SA, 0);
+ tmp &= ~(0x100 << portc->rec_port);
+ write_reg (devc, SA, 0, tmp);
+#ifndef USE_ITIMER
+ tmp = read_reg (devc, AINT_ENABLE, 0);
+ tmp &= ~((AI_RFF | AI_RFH) << portc->rec_port);
+ write_reg (devc, AINT_ENABLE, 0, tmp);
+#endif
+ }
+ }
+ }
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+}
+
+/*ARGSUSED*/
+static int
+audigyls_prepare_for_input (int dev, int bsize, int bcount)
+{
+ unsigned int tmp, recmap, reg;
+ /*LINTED*/ unsigned int oversample;
+ audigyls_devc *devc = audio_engines[dev]->devc;
+ audigyls_portc *portc = audio_engines[dev]->portc;
+ dmap_p dmap = audio_engines[dev]->dmap_in;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ write_reg (devc, CRFA, portc->rec_port, 0);
+ write_reg (devc, CRCAV, portc->rec_port, 0);
+
+ write_reg (devc, RFBA, portc->rec_port, dmap->dmabuf_phys);
+ write_reg (devc, RFBS, portc->rec_port, (dmap->bytes_in_use) << 16);
+
+ /* set 16/24 bits */
+ tmp = INL (devc->osdev, devc->base + 0x14);
+ if (portc->bits == AFMT_S16_LE)
+ tmp &= ~0x400; /*16 bit */
+ else
+ tmp |= 0x400; /*24 bit */
+ OUTL (devc->osdev, tmp, devc->base + 0x14);
+
+ /* set recording speed */
+ reg = read_reg (devc, SRCTL, 0) & ~0xc000;
+
+ switch (portc->speed)
+ {
+ case 48000:
+ reg |= 0x0;
+ oversample = 0x2;
+ break;
+
+ case 96000:
+ reg |= 0x8000;
+ oversample = 0xa;
+ break;
+
+ case 192000:
+ reg |= 0xc000;
+ oversample = 0xa;
+ break;
+
+ default:
+ reg |= 0;
+ oversample = 0x2;
+ break;
+ }
+
+ write_reg (devc, SRCTL, 0, reg);
+/* audigyls_i2c_write(devc, 0xc, oversample);*/
+
+/* setup record input */
+ if (devc->loopback)
+ {
+ devc->rec_src = RECSEL_I2SOUT;
+ recmap = 0;
+ }
+ else
+ {
+ if (devc->has_ac97)
+ {
+ devc->rec_src = RECSEL_AC97; /* audigy LS */
+ }
+ else
+ {
+ devc->rec_src = RECSEL_I2SIN; /* sb 7.1 value */
+ }
+ recmap = 0x00;
+ }
+ tmp = recmap; /* default record input map */
+ tmp |= devc->rec_src << 28 | devc->rec_src << 24 | devc->rec_src << 20 | devc->rec_src << 16;
+//write_reg (devc, SMIXMAP_SPDIF, 0, 0x76767676);
+ write_reg (devc, P17RECSEL, 0, tmp);
+ portc->audio_enabled &= ~PCM_ENABLE_INPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_INPUT;
+
+#ifdef USE_ITIMER
+ adjust_itimer (devc, portc, dmap);
+#endif
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+audigyls_prepare_for_output (int dev, int bsize, int bcount)
+{
+ audigyls_devc *devc = audio_engines[dev]->devc;
+ audigyls_portc *portc = audio_engines[dev]->portc;
+ dmap_p dmap = audio_engines[dev]->dmap_out;
+ unsigned int tmp, reg;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ if (portc->bits == AFMT_AC3)
+ portc->channels = 2;
+
+ write_reg (devc, PTBA, portc->play_port, 0);
+ write_reg (devc, PTBS, portc->play_port, 0);
+ write_reg (devc, PTCA, portc->play_port, 0);
+
+ write_reg (devc, CPFA, portc->play_port, 0);
+ write_reg (devc, PFEA, portc->play_port, 0);
+ write_reg (devc, CPCAV, portc->play_port, 0);
+
+
+ /* set 16/24 bits */
+ tmp = INL (devc->osdev, devc->base + 0x14);
+ if (portc->bits == AFMT_S16_LE)
+ tmp &= ~0x800; /*16 bit */
+ else
+ tmp |= 0x800; /*24 bit */
+ OUTL (devc->osdev, tmp, devc->base + 0x14);
+
+ /* set playback rate */
+ tmp = read_reg (devc, SA, 0) & ~0xff0000;
+ reg = read_reg (devc, SRCTL, 0) & ~0x0303000f;
+#if 0
+ switch (portc->speed)
+ {
+ case 48000:
+ tmp |= 0;
+ reg |= 0;
+ break;
+
+ case 44100:
+ tmp |= 0x10000;
+ reg |= 0x01010005;
+ break;
+
+ case 96000:
+ tmp |= 0x20000;
+ reg |= 0x0202000a;
+ break;
+
+ case 192000:
+ tmp |= 0x30000;
+ reg |= 0x0303000f;
+ break;
+
+ default:
+ tmp |= 0; /* default is 48000 */
+ reg |= 0;
+ break;
+ }
+#endif
+ write_reg (devc, SA, 0, tmp << (portc->play_port * 2));
+ write_reg (devc, SRCTL, 0, reg);
+
+ /* Single buffering mode */
+ write_reg (devc, PFBA, portc->play_port, dmap->dmabuf_phys);
+ write_reg (devc, PFBS, portc->play_port, (dmap->bytes_in_use) << 16);
+
+ if (audigyls_spdif_enable)
+ {
+ if (portc->bits == AFMT_AC3)
+ {
+ audigyls_ac97_write (devc, 0x1c, 0x8000);
+ write_reg (devc, SCS3, 0, 0x02108006); /* Non Audio */
+#if 0
+ write_reg (devc, SCS0, 0, 0x02108006); /* Non Audio */
+ write_reg (devc, SCS1, 0, 0x02108006); /* Non Audio */
+ write_reg (devc, SCS2, 0, 0x02108006); /* Non Audio */
+#endif
+ }
+ else
+ {
+ write_reg (devc, SCS3, 0, 0x02108004); /* Audio */
+#if 0
+ write_reg (devc, SCS0, 0, 0x02108004); /* Audio */
+ write_reg (devc, SCS1, 0, 0x02108004); /* Audio */
+ write_reg (devc, SCS2, 0, 0x02108004); /* Audio */
+#endif
+ }
+ }
+ portc->audio_enabled |= PCM_ENABLE_OUTPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
+
+#ifdef USE_ITIMER
+ adjust_itimer (devc, portc, dmap);
+#endif
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+static int
+audigyls_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
+{
+ unsigned int p = 0;
+ audigyls_portc *portc = audio_engines[dev]->portc;
+
+#if 1
+ audigyls_devc *devc = audio_engines[dev]->devc;
+
+ if (direction == PCM_ENABLE_OUTPUT)
+ {
+ p = read_reg (devc, CPFA, portc->play_port);
+ }
+
+ if (direction == PCM_ENABLE_INPUT)
+ {
+ p = read_reg (devc, CRFA, portc->rec_port);
+ }
+
+ /*
+ * Round to the nearest fragment boundary.
+ */
+ p = (p + dmap->fragment_size / 2);
+ p = (p / dmap->fragment_size) * dmap->fragment_size;
+#else
+ if (direction == PCM_ENABLE_OUTPUT)
+ {
+ return portc->play_ptr;
+ }
+
+ if (direction == PCM_ENABLE_INPUT)
+ {
+ return portc->rec_ptr;
+ }
+#endif
+
+ return p % dmap->bytes_in_use;
+}
+
+static audiodrv_t audigyls_audio_driver = {
+ audigyls_open,
+ audigyls_close,
+ audigyls_output_block,
+ audigyls_start_input,
+ audigyls_ioctl,
+ audigyls_prepare_for_input,
+ audigyls_prepare_for_output,
+ audigyls_reset,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ audigyls_trigger,
+ audigyls_set_rate,
+ audigyls_set_format,
+ audigyls_set_channels,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* audigyls_alloc_buffer, */
+ NULL, /* audigyls_free_buffer */
+ NULL,
+ NULL,
+ audigyls_get_buffer_pointer
+};
+
+
+static __inline__ int
+audigylsuart_status (audigyls_devc * devc)
+{
+ return read_reg (devc, MUCMDA, 0);
+}
+
+#define input_avail(devc) (!(audigylsuart_status(devc)&INPUT_AVAIL))
+#define output_ready(devc) (!(audigylsuart_status(devc)&OUTPUT_READY))
+static void
+audigylsuart_cmd (audigyls_devc * devc, unsigned char cmd)
+{
+ write_reg (devc, MUCMDA, 0, cmd);
+}
+
+static __inline__ int
+audigylsuart_read (audigyls_devc * devc)
+{
+ return read_reg (devc, MUDATA, 0);
+}
+
+static __inline__ void
+audigylsuart_write (audigyls_devc * devc, unsigned char byte)
+{
+ write_reg (devc, MUDATA, 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_audigylsuart (audigyls_devc * devc);
+static void enter_uart_mode (audigyls_devc * devc);
+
+static void
+audigylsuart_input_loop (audigyls_devc * devc)
+{
+ while (input_avail (devc))
+ {
+ unsigned char c = audigylsuart_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
+audigylsuartintr (audigyls_devc * devc)
+{
+ audigylsuart_input_loop (devc);
+}
+
+/*ARGSUSED*/
+static int
+audigylsuart_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
+ oss_midi_inputbuf_t inputbuf,
+ oss_midi_outputintr_t outputintr)
+{
+ audigyls_devc *devc = (audigyls_devc *) midi_devs[dev]->devc;
+
+ if (devc->midi_opened)
+ {
+ return OSS_EBUSY;
+ }
+
+ while (input_avail (devc))
+ audigylsuart_read (devc);
+
+ devc->midi_input_intr = inputbyte;
+ devc->midi_opened = mode;
+ enter_uart_mode (devc);
+ devc->midi_disabled = 0;
+
+ return 0;
+}
+
+/*ARGSUSED*/
+static void
+audigylsuart_close (int dev, int mode)
+{
+ audigyls_devc *devc = (audigyls_devc *) midi_devs[dev]->devc;
+ reset_audigylsuart (devc);
+ oss_udelay (10);
+ enter_uart_mode (devc);
+ reset_audigylsuart (devc);
+ devc->midi_opened = 0;
+}
+
+
+static int
+audigylsuart_out (int dev, unsigned char midi_byte)
+{
+ int timeout;
+ audigyls_devc *devc = (audigyls_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))
+ audigylsuart_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_audigylsuart (devc);
+ enter_uart_mode (devc);
+ return 1;
+ }
+
+ audigylsuart_write (devc, midi_byte);
+ return 1;
+}
+
+/*ARGSUSED*/
+static int
+audigylsuart_ioctl (int dev, unsigned cmd, ioctl_arg arg)
+{
+ return OSS_EINVAL;
+}
+
+static midi_driver_t audigyls_midi_driver = {
+ audigylsuart_open,
+ audigylsuart_close,
+ audigylsuart_ioctl,
+ audigylsuart_out,
+};
+
+static void
+enter_uart_mode (audigyls_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;
+ audigylsuart_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 (audigylsuart_read (devc) == MPU_ACK)
+ ok = 1;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+}
+
+
+void
+attach_audigylsuart (audigyls_devc * devc)
+{
+ enter_uart_mode (devc);
+ devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "AUDIGYLS", "AudigyLS UART", &audigyls_midi_driver, sizeof (midi_driver_t),
+ 0, devc, devc->osdev);
+ devc->midi_opened = 0;
+}
+
+static int
+reset_audigylsuart (audigyls_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;
+ audigylsuart_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 (audigylsuart_read (devc) == MPU_ACK)
+ ok = 1;
+
+ }
+
+
+
+ if (ok)
+ audigylsuart_input_loop (devc); /*
+ * Flush input before enabling interrupts
+ */
+
+ return ok;
+}
+
+
+int
+probe_audigylsuart (audigyls_devc * devc)
+{
+ int ok = 0;
+ oss_native_word flags;
+
+ DDB (cmn_err (CE_CONT, "Entered probe_audigylsuart\n"));
+
+ devc->midi_input_intr = NULL;
+ devc->midi_opened = 0;
+ devc->input_byte = 0;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ ok = reset_audigylsuart (devc);
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+
+ if (ok)
+ {
+ DDB (cmn_err (CE_CONT, "Reset UART401 OK\n"));
+ }
+ else
+ {
+ DDB (cmn_err
+ (CE_CONT, "Reset UART401 failed (no hardware present?).\n"));
+ DDB (cmn_err
+ (CE_CONT, "mpu401 status %02x\n", audigylsuart_status (devc)));
+ }
+
+ DDB (cmn_err (CE_CONT, "audigylsuart detected OK\n"));
+ return ok;
+}
+
+void
+unload_audigylsuart (audigyls_devc * devc)
+{
+ reset_audigylsuart (devc);
+}
+
+
+static void
+attach_mpu (audigyls_devc * devc)
+{
+ devc->mpu_attached = 1;
+ attach_audigylsuart (devc);
+}
+
+/* only for SBLive 7.1 */
+int
+audigyls_i2c_write (audigyls_devc * devc, int reg, int data)
+{
+ int i, timeout, tmp;
+
+
+ tmp = (reg << 9 | data) << 16; /* set the upper 16 bits */
+ /* first write the command to the data reg */
+ write_reg (devc, I2C_1, 0, tmp);
+ for (i = 0; i < 20; i++)
+ {
+ tmp = read_reg (devc, I2C_A, 0) & ~0x6fe;
+ /* see audigyls.pdf for bits */
+ tmp |= 0x400 | 0x100 | 0x34;
+ write_reg (devc, I2C_A, 0, tmp);
+ /* now wait till controller sets valid bit (0x100) to 0 */
+ timeout = 0;
+ /*LINTED*/ while (1)
+ {
+ tmp = read_reg (devc, I2C_A, 0);
+ if ((tmp & 0x100) == 0)
+ break;
+
+ if (timeout > 100)
+ break;
+
+ timeout++;
+ }
+
+ /* transaction aborted */
+ if (tmp & 0x200)
+ return 0;
+ }
+ return 1;
+}
+
+int
+audigyls_spi_write (audigyls_devc * devc, int data)
+{
+ unsigned int orig;
+ unsigned int tmp;
+ int i, valid;
+
+ tmp = read_reg (devc, SPI, 0);
+ orig = (tmp & ~0x3ffff) | 0x30000;
+ write_reg (devc, SPI, 0, orig | data);
+ valid = 0;
+ /* Wait for status bit to return to 0 */
+ for (i = 0; i < 1000; i++)
+ {
+ oss_udelay (100);
+ tmp = read_reg (devc, SPI, 0);
+ if (!(tmp & 0x10000))
+ {
+ valid = 1;
+ break;
+ }
+ }
+ if (!valid) /* Timed out */
+ return 0;
+
+ return 1;
+}
+
+static unsigned int
+mix_scale (int left, int right, int bits)
+{
+ left = mix_cvt[left];
+ right = mix_cvt[right];
+
+ return ((left * ((1 << bits) - 1) / 100) << 8) | (right *
+ ((1 << bits) - 1) / 100);
+}
+
+static int
+audigyls_set_volume (audigyls_devc * devc, int codecid, int value)
+{
+ audigyls_portc *portc = NULL;
+ int left, right, i2s_vol;
+
+ portc = &devc->portc[codecid];
+
+ left = value & 0xff;
+ right = (value >> 8) & 0xff;
+ if (left > 100)
+ left = 100;
+ if (right > 100)
+ right = 100;
+ devc->playvol[codecid] = left | (right << 8);
+
+ i2s_vol = 65535 - mix_scale (left, right, 8);
+ write_reg (devc, MIXVOL_I2S, portc->play_port, (i2s_vol << 16));
+ return devc->playvol[codecid];
+}
+
+int
+audigyls_mix_control (int dev, int ctrl, unsigned int cmd, int value)
+{
+ audigyls_devc *devc = mixer_devs[dev]->hw_devc;
+ int val;
+
+ if (cmd == SNDCTL_MIX_READ)
+ {
+ value = 0;
+ switch (ctrl)
+ {
+ case 1: /* spread */
+ value = devc->spread;
+ break;
+
+ case 2: /* record what you hear */
+ value = devc->loopback;
+ break;
+
+ case 3:
+ {
+ value = devc->recvol;
+ }
+ break;
+
+ case 4:
+ value = devc->input_source;
+ break;
+ case 5:
+ value = devc->captmon;
+ break;
+ case 7:
+ value = devc->fbvol;
+ break;
+ }
+ }
+ if (cmd == SNDCTL_MIX_WRITE)
+ {
+ switch (ctrl)
+ {
+ case 1: /* recording source */
+ devc->spread = value;
+ if (value)
+ write_reg (devc, HMIXMAP_I2S, 0, 0x10101010);
+ else
+ write_reg (devc, HMIXMAP_I2S, 0, 0x76543210);
+ break;
+
+ case 2: /* record what you hear */
+ devc->loopback = value;
+ break;
+
+ case 3:
+ {
+ val = (255 - value) & 0xff;
+ write_reg (devc, P17RECVOLL, 0,
+ val << 24 | val << 16 | val << 8 | val);
+ write_reg (devc, P17RECVOLH, 0,
+ val << 24 | val << 16 | val << 8 | val);
+ /* write_reg (devc, SRCTL, 1,
+ 0xff << 24 | 0xff << 16 | val << 8 | val); */
+ devc->recvol = value & 0xff;
+ }
+ break;
+
+ case 4:
+ {
+ switch (value)
+ {
+ case 0: /* for mic input remove GPIO */
+ {
+ OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x18) | 0x400,
+ devc->base + 0x18);
+ audigyls_i2c_write (devc, 0x15, 0x2); /* Mic */
+ }
+ break;
+ case 1:
+ {
+ OUTL (devc->osdev,
+ INL (devc->osdev, devc->base + 0x18) & ~0x400,
+ devc->base + 0x18);
+ audigyls_i2c_write (devc, 0x15, 0x4); /* Line */
+ }
+ break;
+ case 2:
+ {
+ OUTL (devc->osdev,
+ INL (devc->osdev, devc->base + 0x18) & ~0x400,
+ devc->base + 0x18);
+ audigyls_i2c_write (devc, 0x15, 0x8); /* Aux */
+ }
+ break;
+ }
+ devc->input_source = value;
+ }
+ break;
+ case 5:
+ {
+ devc->captmon = value;
+ /* Send analog capture to front speakers */
+ if (value)
+ write_reg (devc, SMIXMAP_I2S, 0, 0x76767676);
+ else
+ write_reg (devc, SMIXMAP_I2S, 0, 0x10101010);
+ }
+ break;
+ case 7:
+ {
+ /*Set recording monitor volume */
+ val = (255 - value) & 0xff;
+ write_reg (devc, SRCTL, 1, val << 8 | val);
+ devc->fbvol = value & 0xff;
+ }
+ break;
+ }
+ }
+ return value;
+}
+
+static int
+audigyls_mix_init (int dev)
+{
+ int group, err;
+ audigyls_devc *devc = mixer_devs[dev]->hw_devc;
+
+ if ((group = mixer_ext_create_group (dev, 0, "EXT")) < 0)
+ return group;
+
+ if ((err = mixer_ext_create_control (dev, group, 1, audigyls_mix_control,
+ MIXT_ONOFF, "Spread", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return err;
+
+ if ((err = mixer_ext_create_control (dev, group, 2, audigyls_mix_control,
+ MIXT_ONOFF, "LOOPBACK", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return err;
+
+
+ if ((err = mixer_ext_create_control (dev, group, 3, audigyls_mix_control,
+ MIXT_MONOSLIDER, "RECORDVOL", 255,
+ MIXF_READABLE | MIXF_WRITEABLE |
+ MIXF_RECVOL)) < 0)
+ return err;
+
+ if (!devc->has_ac97)
+ {
+ if ((err =
+ mixer_ext_create_control (dev, group, 4, audigyls_mix_control,
+ MIXT_ENUM, "RECORDSRC", 3,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return err;
+ mixer_ext_set_strings (dev, err, "MIC LINE AUX", 0);
+ }
+ if ((err = mixer_ext_create_control (dev, group, 7, audigyls_mix_control,
+ MIXT_MONOSLIDER, "monitorvol", 255,
+ MIXF_READABLE | MIXF_WRITEABLE |
+ MIXF_RECVOL)) < 0)
+ return err;
+ if ((err = mixer_ext_create_control (dev, group, 5, audigyls_mix_control,
+ MIXT_ONOFF, "RecMon", 1,
+ MIXF_READABLE | MIXF_WRITEABLE)) < 0)
+ return err;
+ return 0;
+}
+
+static const int bindings[MAX_PORTC] = {
+ DSP_BIND_FRONT,
+ DSP_BIND_CENTER_LFE,
+ DSP_BIND_SURR
+};
+
+static int
+install_audio_devices (audigyls_devc * devc)
+{
+ int i;
+ int frontdev = -1;
+ int adev, flags;
+ int fmts = AFMT_S16_LE | AFMT_AC3;
+ static char *names[] = {
+ "AudigyLS front",
+ "AudigyLS center/lfe",
+ "AudigyLS surround"
+ };
+
+#if 0
+ if (audigyls_spdif_enable == 1)
+ n = 2;
+#endif
+
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ audigyls_portc *portc = &devc->portc[i];
+
+ flags =
+ ADEV_AUTOMODE | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_FIXEDRATE;
+
+ switch (i)
+ {
+ case 0:
+ portc->play_port = 0;
+ portc->rec_port = 2;
+ flags |= ADEV_DUPLEX;
+ break;
+ case 1:
+ portc->play_port = 1;
+ portc->rec_port = 2;
+ flags |= ADEV_NOINPUT;
+ break;
+ case 2:
+ portc->play_port = 3;
+ portc->rec_port = 2;
+ flags |= ADEV_NOINPUT;
+ break;
+ }
+
+ if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION,
+ devc->osdev,
+ devc->osdev,
+ names[i],
+ &audigyls_audio_driver,
+ sizeof (audiodrv_t),
+ flags, fmts, devc, -1)) < 0)
+ {
+ return 0;
+ }
+
+ if (i == 0)
+ frontdev = adev;
+
+ audio_engines[adev]->portc = portc;
+ audio_engines[adev]->max_fragments = 2;
+ audio_engines[adev]->dmabuf_alloc_flags |= DMABUF_SIZE_16BITS;
+ audio_engines[adev]->rate_source = frontdev;
+ audio_engines[adev]->mixer_dev = devc->mixer_dev;
+ audio_engines[adev]->binding = bindings[i];
+ if (audio_engines[adev]->flags & ADEV_FIXEDRATE)
+ {
+ audio_engines[adev]->fixed_rate = DEFAULT_RATE;
+ audio_engines[adev]->min_rate = DEFAULT_RATE;
+ audio_engines[adev]->max_rate = DEFAULT_RATE;
+ }
+ else
+ {
+ audio_engines[adev]->min_rate = 44100;
+ audio_engines[adev]->max_rate = 192000;
+ }
+ portc->audio_dev = adev;
+ portc->open_mode = 0;
+ devc->playvol[i] = 0x3030;
+ devc->recvol = 128;
+ portc->bits = AFMT_S16_LE;
+ }
+
+#ifdef USE_REMUX
+ if (frontdev >= 0)
+ {
+ if (audigyls_spdif_enable && devc->has_ac97)
+ remux_install ("AudigyLS 4.0 output", devc->osdev, frontdev,
+ frontdev + 2, -1, -1);
+ else
+ remux_install ("AudigyLS 5.1 output", devc->osdev, frontdev,
+ frontdev + 2, frontdev + 1, -1);
+ }
+#endif
+
+#ifdef CONFIG_OSS_VMIX
+ if (frontdev >= 0)
+ vmix_attach_audiodev(devc->osdev, frontdev, -1, 0);
+#endif
+ return 1;
+}
+
+static void
+select_out3_mode (audigyls_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, 0x00000f00);
+ }
+ else
+ {
+ write_reg (devc, SPC, 0, 0x0000000f);
+ }
+}
+
+/*ARGSUSED*/
+static int
+audigyls_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg)
+{
+ audigyls_devc *devc = mixer_devs[dev]->devc;
+
+ if (((cmd >> 8) & 0xff) == 'M')
+ {
+ int val;
+
+ if (IOC_IS_OUTPUT (cmd))
+ switch (cmd & 0xff)
+ {
+ case SOUND_MIXER_RECSRC:
+ return *arg = 0;
+ break;
+
+ case SOUND_MIXER_PCM:
+ val = *arg;
+ return *arg = audigyls_set_volume (devc, 0, val);
+
+ case SOUND_MIXER_CENTERVOL:
+ val = *arg;
+ return *arg = audigyls_set_volume (devc, 1, val);
+
+ case SOUND_MIXER_REARVOL:
+ val = *arg;
+ return *arg = audigyls_set_volume (devc, 2, val);
+ }
+ else
+ switch (cmd & 0xff) /* Return Parameter */
+ {
+ case SOUND_MIXER_RECSRC:
+ case SOUND_MIXER_RECMASK:
+ return *arg = 0;
+ break;
+
+ case SOUND_MIXER_DEVMASK:
+ return *arg =
+ SOUND_MASK_PCM | SOUND_MASK_REARVOL | SOUND_MASK_CENTERVOL;
+ break;
+
+ case SOUND_MIXER_STEREODEVS:
+ return *arg =
+ SOUND_MASK_PCM | SOUND_MASK_REARVOL | SOUND_MASK_CENTERVOL;
+ break;
+
+ case SOUND_MIXER_CAPS:
+ return *arg = SOUND_CAP_EXCL_INPUT;
+ break;
+
+ case SOUND_MIXER_PCM:
+ return *arg = devc->playvol[0];
+ break;
+
+ case SOUND_MIXER_CENTERVOL:
+ return *arg = devc->playvol[2];
+ break;
+
+ case SOUND_MIXER_REARVOL:
+ return *arg = devc->playvol[3];
+ break;
+ }
+ }
+ else
+ return *arg = 0;
+
+ return OSS_EINVAL;
+}
+
+static mixer_driver_t audigyls_mixer_driver = {
+ audigyls_mixer_ioctl
+};
+
+int
+oss_audigyls_attach (oss_device_t * osdev)
+{
+ int tmp, err, i;
+ unsigned char pci_irq_line, pci_revision;
+ unsigned short pci_command, vendor, device;
+ unsigned int pci_ioaddr;
+ unsigned int subvendor;
+ audigyls_devc *devc;
+
+ static unsigned int spi_dac[] = {
+ 0x00ff, 0x02ff, 0x0400, 0x530, 0x0622, 0x08ff, 0x0aff, 0x0cff,
+ 0x0eff, 0x10ff, 0x1200, 0x1400, 0x1800, 0x1aff, 0x1cff,
+ 0x1e00,
+ };
+
+ DDB (cmn_err (CE_WARN, "Entered AUDIGYLS 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_AUDIGYLS)
+ 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);
+
+
+ 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 = "AudigyLS";
+ devc->subvendor = subvendor;
+
+ pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO;
+ pci_write_config_word (osdev, PCI_COMMAND, pci_command);
+
+ devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr);
+ devc->base &= ~0x3;
+
+ MUTEX_INIT (osdev, devc->mutex, MH_DRV);
+ MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1);
+
+ oss_register_device (osdev, devc->card_name);
+
+ if ((err =
+ oss_register_interrupts (devc->osdev, 0, audigylsintr, NULL)) < 0)
+ {
+ cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err);
+ return 0;
+ }
+
+
+/*
+ * Init mixer
+ */
+ if (subvendor == 0x10021102) /* original audigyls */
+ {
+ devc->mixer_dev = ac97_install (&devc->ac97devc, devc->card_name,
+ audigyls_ac97_read, audigyls_ac97_write,
+ devc, devc->osdev);
+ devc->has_ac97 = 1;
+ audigyls_ac97_write (devc, 0x1c, 0x8000);
+ }
+ else
+ {
+ devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION,
+ devc->osdev,
+ devc->osdev,
+ "AudigyLS Mixer",
+ &audigyls_mixer_driver,
+ sizeof (mixer_driver_t), devc);
+ devc->has_ac97 = 0; /* no ac97 */
+ 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, audigyls_mix_init, 10);
+
+#if 0
+ write_reg (devc, SCS0, 0, 0x02108504);
+ write_reg (devc, SCS1, 0, 0x02108504);
+ write_reg (devc, SCS2, 0, 0x02108504);
+#endif
+ write_reg (devc, SCS3, 0, 0x02108504);
+
+ write_reg (devc, AUDCTL, 0, 0x0f0f003f); /* enable all outputs */
+
+ select_out3_mode (devc, audigyls_spdif_enable);
+
+/**********************************************************************
+ * In P17, there's 8 GPIO pins.
+ * GPIO register: 0x00XXYYZZ
+ * XX: Configure GPIO to be either GPI (0) or GPO (1).
+ * YY: GPO values, applicable if the pin is configure to be GPO.
+ * ZZ: GPI values, applicable if the pin is configure to be GPI.
+ *
+ * in SB570, pin 0-4 and 6 is used as GPO and pin 5 and 7 is used as GPI.
+ *
+ * GPO0:
+ * 1 ==> Analog output
+ * 0 ==> Digital output
+ * GPO1:
+ * 1 ==> Enable output on card
+ * 0 ==> Diable output on card
+ * GPO2:
+ * 1 ==> Enable Mic Bias and Mic Path
+ * 0 ==> Disable Mic Bias and Mic Path
+ * GPO3:
+ * 1 ==> Disable SPDIF-IO output
+ * 0 ==> Enable SPDIF-IO output
+ * GPO4 and GPO6:
+ * DAC sampling rate selection:
+ * Not applicable to SB570 since DAC is controlled through SPI
+ * GPI5:
+ * 1 ==> Front Panel is not connected
+ * 0 ==> Front Panel is connected
+ * GPI7:
+ * 1 ==> Front Panel Headphone is not connected
+ * 0 ==> Front Panel Headphone is connected
+ ***********************************************************/
+
+ OUTL (devc->osdev, 0, devc->base + 0x18); /* GPIO */
+ if (devc->has_ac97)
+ OUTL (devc->osdev, 0x005f03a3, devc->base + 0x18);
+ else
+ {
+ /* for SBLive 7.1 */
+ OUTL (devc->osdev, 0x005f4301, devc->base + 0x18);
+ audigyls_i2c_write (devc, 0x15, 0x4);
+ for (i = 0; i < (sizeof (spi_dac)/sizeof (spi_dac[0])); i++)
+ {
+ audigyls_spi_write (devc, spi_dac[i]);
+ }
+ }
+
+ OUTL (devc->osdev, INTR_PCI | INTR_RXA | INTR_AI, devc->base + IE);
+ OUTL (devc->osdev, 0x00000009, devc->base + 0x14); /* Enable audio */
+
+ tmp = read_reg (devc, SRCTL, 0);
+ if (devc->has_ac97)
+ tmp |= 0xf0c81000; /* record src0/src1 from ac97 */
+ else
+ tmp |= 0x50c81000; /* record src0/src1 from I2SIN */
+ write_reg (devc, SRCTL, 0, tmp);
+ write_reg (devc, HMIXMAP_I2S, 0, 0x76543210); /* default out route */
+ install_audio_devices (devc);
+
+ if (devc->has_ac97) /* only attach midi for AudigyLS */
+ attach_mpu (devc);
+
+ return 1;
+}
+
+int
+oss_audigyls_detach (oss_device_t * osdev)
+{
+ unsigned int status;
+ audigyls_devc *devc = (audigyls_devc *) osdev->devc;
+
+ if (oss_disable_device (osdev) < 0)
+ return 0;
+
+
+ write_reg (devc, SA, 0, 0);
+ OUTL (devc->osdev, 0x00000000, devc->base + IE); /* Interrupt disable */
+ write_reg (devc, AINT_ENABLE, 0, 0); /* Disable audio interrupts */
+ status = INL (devc->osdev, devc->base + 0x08);
+ OUTL (devc->osdev, status, devc->base + 0x08); /* Acknowledge */
+ oss_udelay (1000);
+ if (devc->mpu_attached)
+ unload_audigylsuart (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_audigyls/oss_audigyls.man b/kernel/drv/oss_audigyls/oss_audigyls.man
new file mode 100644
index 0000000..b490a32
--- /dev/null
+++ b/kernel/drv/oss_audigyls/oss_audigyls.man
@@ -0,0 +1,45 @@
+NAME
+oss_audigyls - Creative Labs CA106 (AudigyLS/SBLive 24bit) driver.
+
+DESCRIPTION
+Open Sound System driver for Creative Labs Audigy2-LS and SBLive 24bit 7.1
+soundcards.
+
+Audigy-LS device characteristics:
+ o 8/16/24 bit playback/record
+ o mono/stereo/4/5.1 playback
+ o 8KHz to 192Khz sample rate.
+
+ AUDIGYLS MODELS
+
+There are 2 models of the AudigyLS device: one with an AC97 codec called the
+AudigyLS and the one without called the SBLive 7.1. Essentially they are
+the same chip but behave a bit differently.
+
+When playing AC3 on the AudigyLS (the one with the AC97 mixer) - you
+need to ensure that the igain slider is set to 0.
+
+ AUDIGYLS MIXER
+
+o The AudigyLS has 4 mixer controls for each channel.
+o The "spread" button will simply duplicate the front audio on the other 3
+ channels so that every speaker is playing what the front L/R is playing.
+o LoopBack recording allows you to capture any channel that's playing audio.
+o Record Volume slider just adjusts the input gain.
+o Record Source selector selects the input.
+o The "recmon" button enables a passthrough of the analog input signal.
+o Monitor Volume slider sets the passthrough gain if recmon is enabled.
+
+OPTIONS
+o audigyls_spdif_enable=0|1
+The Audigy LS has a versa-jack (orange) that can be set as SPDIF output
+or the Side-Surround left/right speakers in a 7.1 setup.
+When set as SPDIF, you can get play PCM/AC3 audio to a Dolby(R) capable
+receiver.
+
+FILES
+CONFIGFILEPATH/oss_audigyls.conf Device configuration file
+
+AUTHOR
+4Front Technologies
+