diff options
Diffstat (limited to 'kernel/drv/oss_audigyls/oss_audigyls.c')
-rw-r--r-- | kernel/drv/oss_audigyls/oss_audigyls.c | 1860 |
1 files changed, 1860 insertions, 0 deletions
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; +} |