Open Sound System |
Do you have problems with sound/audio application development? Don't panic! Click here for help! |
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)
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);
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, 2, val); case SOUND_MIXER_REARVOL: val = *arg; return *arg = audigyls_set_volume (devc, 3, 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, tries; 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); tries = 0; again: for (i = 0; i < sizeof (spi_dac); i++) { if (!audigyls_spi_write (devc, spi_dac[i]) && tries < 100) { tries++; goto again; } } } 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; }