/* * Purpose: Driver for Trident 4DWAVE, ALI 5451 and SiS 7918 audio chips */ /* * * 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_trident_cfg.h" #include "oss_pci.h" #include "ac97.h" #if defined(sparc) #define MEM_MAPPED_REGISTERS #else #undef MEM_MAPPED_REGISTERS #endif #define TRIDENT_VENDOR_ID 0x1023 #define TRIDENT_4DWAVEDX_ID 0x2000 #define TRIDENT_4DWAVENX_ID 0x2001 #define TRIDENT_4DWAVECX_ID 0x2002 #define ALI_VENDOR_ID 0x10b9 #define ALI_5451_ID 0x5451 #define SIS_VENDOR_ID 0x1039 #define SIS_7018_ID 0x7018 /* DX/NX/CX IO Registers */ #define SP_CSO 0x24 #define SP_LBA 0x28 #define SP_ESO 0x2C #define ACR0 0x40 #define SP_STAT 0x64 #define TLBC 0x6C #define START_A 0x80 #define STOP_A 0x84 #define INT_A 0x98 #define INTEN_A 0xA4 #define VOL 0xA8 #define DELTA 0xAC #define MISCINT 0xB0 #define START_B 0xB4 #define STOP_B 0xB8 #define INT_B 0xD8 #define INTEN_B 0xDC #define CIR 0xA0 #define CSO 0xE0 #define LBA 0xE4 #define ESO 0xE8 #define FMC 0xEC #define TCTRL 0xF0 #define EBUF1 0xF4 #define EBUF2 0xF8 #define DMAR0 0x00 #define DMAR4 0x04 #define DMAR6 0x06 #define DMAR11 0x0b #define DMAR15 0x0f #define SBDELTA 0xac #define SBBL 0xc0 #define SBCTL 0xc4 /* SIS 7018 Define */ #define SECONDARY_ID 0x00004000 #define PCMOUT 0x00010000 #define SURROUT 0x00020000 #define CENTEROUT 0x00040000 #define LFEOUT 0x00080000 #define LINE1OUT 0x00100000 #define LINE2OUT 0x00200000 #define GPIOOUT 0x00400000 #define CHANNEL_PB 0x0000 #define CHANNEL_SPC_PB 0x4000 #define CHANNEL_REC 0x8000 #define CHANNEL_REC_PB 0xc000 #define MODEM_LINE1 0x0000 #define MODEM_LINE2 0x0400 #define PCM_LR 0x0800 #define HSET 0x0c00 #define I2S_LR 0x1000 #define CENTER_LFE 0x1400 #define SURR_LR 0x1800 #define SPDIF_LR 0x1c00 #define MIC 0x1400 #define MONO_LEFT 0x0000 #define MONO_RIGHT 0x0100 #define MONO_MIX 0x0200 #define SRC_ENABLE 0x0080 #define INTR(bank) ((bank==1)?INT_A:INT_B) #define INTEN(bank) ((bank==1)?INTEN_A:INTEN_B) #define STOP(bank) ((bank==1)?STOP_A:STOP_B) #define START(bank) ((bank==1)?START_A:START_B) /* ALI5451 definitions */ #define ALI_5451_V02 0x2 #define SIS_7018_V02 0x2 extern int trident_mpu_ioaddr; #define MAX_PORTC 8 typedef struct trident_portc { int speed, bits, channels; int open_mode; int audio_enabled; int trigger_bits; int audiodev; int port_type; #define DF_PCM 0 #define DF_SPDIF 1 unsigned char play_chan, play_intr_chan; unsigned char rec_chan, rec_intr_chan; int pbank, rbank; #define BANK_A 1 #define BANK_B 0 } trident_portc; typedef struct trident_devc { oss_device_t *osdev; char *chip_name; int chip_type; int revision; int irq; #ifdef MEM_MAPPED_REGISTERS unsigned int bar1addr; char *bar1virt; #else /* */ oss_native_word base; #endif /* */ volatile unsigned char intr_mask; oss_mutex_t mutex; oss_mutex_t low_mutex; /* Legacy */ int mpu_base, mpu_irq, mpu_attached; /* Mixer parameters */ ac97_devc ac97devc; int mixer_dev; /* Audio parameters */ trident_portc portc[MAX_PORTC]; int audio_opened; unsigned char sb_dma_flags; } trident_devc; #ifndef MEM_MAPPED_REGISTERS /* I/O mapped register access */ #define READL(o,a) INL(o, devc->base+(a)) #define READW(o,a) INW(o, devc->base+(a)) #define READB(o,a) INB(o, devc->base+(a)) #define WRITEL(o,d,a) OUTL(o, d, devc->base+(a)) #define WRITEW(o,d,a) OUTW(o, d, devc->base+(a)) #define WRITEB(o,d,a) OUTB(o, d, devc->base+(a)) #else /* Mem mapped I/O registers */ #define READL(o,a) *(volatile unsigned int*)(devc->bar1virt+(a)) #define READW(o,a) *(volatile unsigned short*)(devc->bar1virt+(a)) #define READB(o,a) *(volatile unsigned char*)(devc->bar1virt+(a)) #define WRITEL(o,d,a) *(volatile unsigned int*)(devc->bar1virt+(a))=d #define WRITEW(o,d,a) *(volatile unsigned short*)(devc->bar1virt+(a))=d #define WRITEB(o,d,a) *(volatile unsigned char*)(devc->bar1virt+(a))=d #endif static int ac97_read (void *devc_, int addr) { trident_devc *devc = devc_; int t, ret; oss_native_word data, reg = 0; oss_native_word rmask = 0; oss_native_word dmask = 0; oss_native_word wport = 0; oss_native_word rport = 0; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); t = 20000; switch (devc->chip_type) { case TRIDENT_4DWAVEDX_ID: case SIS_7018_ID: wport = 0x40; rport = 0x44; if (devc->revision == SIS_7018_V02) { rport = 0x40; } rmask = 0x00008000; dmask = 0x00008000; break; case TRIDENT_4DWAVENX_ID: wport = 0x44; rport = 0x48; rmask = 0x00000800; dmask = 0x00000400; break; case ALI_5451_ID: wport = 0x40; rport = 0x44; rmask = 0x00008000; dmask = 0x00008000; if (devc->revision == ALI_5451_V02) { rport = 0x40; } break; default: { static int already_done = 0; if (!already_done) cmn_err (CE_WARN, "Unknown codec interface\n"); already_done = 1; MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return OSS_EIO; } } /* Check to make sure No Writes are pending */ reg = READL (devc->osdev, wport); while ((reg & dmask) && --t) reg = READL (devc->osdev, wport); /* re-read status/data */ /* Check to make sure No reads are pending */ reg = READL (devc->osdev, rport); while ((reg & dmask) && --t) reg = READL (devc->osdev, rport); /* re-read status/data */ data = addr | rmask; WRITEL (devc->osdev, data, rport); /* select register for reading */ reg = READL (devc->osdev, rport); /* read status/data */ t = 20000; while ((reg & dmask) && --t) { /* busy reading */ reg = READL (devc->osdev, rport); /* re-read status/data */ } if (t) ret = (reg >> 16); else { DDB (cmn_err (CE_WARN, "AC97 mixer read timed out\n")); MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return OSS_EIO; } MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return ret; } static int ac97_write (void *devc_, int addr, int data) { trident_devc *devc = devc_; int t; oss_native_word wport = 0; oss_native_word reg = 0; unsigned int wmask = 0; unsigned int dmask = 0; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); t = 2000; switch (devc->chip_type) { case TRIDENT_4DWAVEDX_ID: case SIS_7018_ID: wport = 0x40; wmask = 0x00008000; dmask = 0x00008000; break; case TRIDENT_4DWAVENX_ID: wport = 0x44; wmask = 0x00000800; dmask = 0x00000800; break; case ALI_5451_ID: wport = 0x40; wmask = 0x00008000; dmask = 0x00008000; if (devc->revision == 2) dmask = 0x100; break; default: { static int already_done = 0; if (!already_done) cmn_err (CE_WARN, "Unknown codec interface\n"); already_done = 1; MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return OSS_EIO; } } /* Check to make sure No Writes are pending */ reg = READL (devc->osdev, wport); while ((reg & wmask) && --t) { reg = READL (devc->osdev, wport); /* re-read status/data */ } if (t) { reg = data << 16; reg |= (addr & 0xff); reg |= wmask | dmask; WRITEL (devc->osdev, reg, wport); } else DDB (cmn_err (CE_WARN, "AC97 mixer write timed out\n")); MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return 0; } static int tridentintr (oss_device_t * osdev) { trident_devc *devc = (trident_devc *) osdev->devc; int i; oss_native_word status, intstat; int serviced = 0; dmap_t *dmapin; oss_native_word flag; /* check the interrupt status register MISCINT */ status = READL (devc->osdev, MISCINT); serviced = 1; /* If it is a not 4DWave Playback or MIDI interrupt then return */ if (!(status & (0x00000020 | 0x00000008 | 0x4))) { WRITEL (devc->osdev, status, MISCINT); return 1; } #ifdef OBSOLETED_STUFF /* * This device has "ISA style" MIDI and FM subsystems. Such devices don't * use PCI config space for the I/O ports and interrupts. Instead the driver * needs to allocate proper resources itself. This functionality is no longer * possible. For this reason the MIDI and FM parts are not accessible. */ /* Check if this is a MIDI interrupt */ if (status & 0x00000008) { uart401intr (INT_HANDLER_CALL (devc->irq)); serviced = 1; } #endif MUTEX_ENTER_IRQDISABLE (devc->mutex, flag); /* Check to see if this is our channel */ if (status & 0x20) for (i = 0; i < MAX_PORTC; i++) { trident_portc *portc = &devc->portc[i]; serviced = 1; /* Handle Playback Interrupts */ intstat = READL (devc->osdev, INTR (portc->pbank)); if (intstat & 1L << portc->play_intr_chan) { WRITEL (devc->osdev, READL (devc->osdev, INTR (portc->pbank)) | 1L << portc->play_intr_chan, INTR (portc->pbank)); oss_audio_outputintr (portc->audiodev, 1); } /* Handle Record Interrupts */ intstat = READL (devc->osdev, INTR (portc->rbank)); if (intstat & 1L << portc->rec_intr_chan) { WRITEL (devc->osdev, READL (devc->osdev, INTR (portc->rbank)) | 1L << portc->rec_intr_chan, INTR (portc->rbank)); if ((devc->chip_type == ALI_5451_ID) || (devc->chip_type == SIS_7018_ID)) { unsigned int ptr; int i; int chan; dmapin = audio_engines[portc->audiodev]->dmap_in; if (portc->rbank == BANK_A) chan = portc->rec_chan; else chan = portc->rec_chan + 32; WRITEL (devc->osdev, (READL (devc->osdev, CIR) & ~0x3F) | chan, CIR); ptr = READW (devc->osdev, CSO + 2); ptr++; ptr *= portc->channels * (portc->bits / 8); if (dmapin->bytes_in_use == 0 || dmapin->fragment_size == 0) { cmn_err(CE_WARN, "bytes_in_use=%d, fragment_size=%d\n", dmapin->bytes_in_use, dmapin->fragment_size); continue; } ptr %= dmapin->bytes_in_use; ptr /= dmapin->fragment_size; i = 0; while (dmap_get_qtail (dmapin) != ptr && i++ < dmapin->nfrags) oss_audio_inputintr (portc->audiodev, 0); } else oss_audio_inputintr (portc->audiodev, 0); } } if (status & 0x4) { serviced = 1; READB (devc->osdev, 0x1E); /*SB ESP ack */ READB (devc->osdev, 0x1F); /*SB IRQ ack */ } MUTEX_EXIT_IRQRESTORE (devc->mutex, flag); return serviced; } /***************************************************************************/ static int trident_audio_set_rate (int dev, int arg) { trident_portc *portc = audio_engines[dev]->portc; if (arg == 0) return portc->speed; if (arg > 48000) arg = 48000; if (arg < 5000) arg = 5000; portc->speed = arg; return portc->speed; } static short trident_audio_set_channels (int dev, short arg) { trident_portc *portc = audio_engines[dev]->portc; if ((arg != 1) && (arg != 2)) return portc->channels; portc->channels = arg; return portc->channels; } static unsigned int trident_audio_set_format (int dev, unsigned int arg) { trident_portc *portc = audio_engines[dev]->portc; if (arg == 0) return portc->bits; if (!(arg & (AFMT_U8 | AFMT_S16_LE | AFMT_AC3))) return portc->bits; portc->bits = arg; return portc->bits; } /*ARGSUSED*/ static int trident_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) { return OSS_EINVAL; } static void trident_audio_trigger (int dev, int state); static void trident_audio_reset (int dev) { trident_audio_trigger (dev, 0); } static void trident_audio_reset_input (int dev) { trident_portc *portc = audio_engines[dev]->portc; trident_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); } static void trident_audio_reset_output (int dev) { trident_portc *portc = audio_engines[dev]->portc; trident_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); } /*ARGSUSED*/ static int trident_audio_open (int dev, int mode, int open_flags) { trident_portc *portc = audio_engines[dev]->portc; trident_devc *devc = audio_engines[dev]->devc; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } portc->open_mode = mode; portc->audio_enabled = ~mode; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } static void trident_audio_close (int dev, int mode) { trident_portc *portc = audio_engines[dev]->portc; trident_audio_reset (dev); portc->open_mode = 0; portc->audio_enabled &= ~mode; } /*ARGSUSED*/ static void trident_audio_output_block (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { trident_portc *portc = audio_engines[dev]->portc; portc->audio_enabled |= PCM_ENABLE_OUTPUT; portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; } /*ARGSUSED*/ static void trident_audio_start_input (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { trident_portc *portc = audio_engines[dev]->portc; portc->audio_enabled |= PCM_ENABLE_INPUT; portc->trigger_bits &= ~PCM_ENABLE_INPUT; } static void trident_audio_trigger (int dev, int state) { trident_devc *devc = audio_engines[dev]->devc; trident_portc *portc = audio_engines[dev]->portc; oss_native_word ainten = 0, playstop = 0, recstop = 0; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode & OPEN_WRITE) { if (state & PCM_ENABLE_OUTPUT) { if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) && !(portc->trigger_bits & PCM_ENABLE_OUTPUT)) { /* Enable the channel interrupt */ WRITEL (devc->osdev, READL (devc->osdev, INTEN (portc->pbank)) | (1L << portc->play_intr_chan), INTEN (portc->pbank)); /* start playback and playback interrupt channels */ WRITEL (devc->osdev, READL (devc->osdev, START (portc->pbank)) | (1L << portc->play_chan) | (1L << portc->play_intr_chan), START (portc->pbank)); portc->trigger_bits |= PCM_ENABLE_OUTPUT; } } else { if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) && (portc->trigger_bits & PCM_ENABLE_OUTPUT)) { portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; /* disable playback interrupt channel */ ainten = READL (devc->osdev, INTEN (portc->pbank)); ainten = ainten & ~(1L << portc->play_intr_chan); WRITEL (devc->osdev, ainten, INTEN (portc->pbank)); /* stop playback and playback interrupt channels */ playstop = ((1L << portc->play_chan) | (1L << portc->play_intr_chan)); WRITEL (devc->osdev, playstop, STOP (portc->pbank)); } } } 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 the channel interrupts */ WRITEL (devc->osdev, READL (devc->osdev, INTEN (portc->rbank)) | (1L << portc->rec_intr_chan), INTEN (portc->rbank)); if ((devc->chip_type != ALI_5451_ID) && (devc->chip_type != SIS_7018_ID)) { /* set recording flags */ WRITEB (devc->osdev, devc->sb_dma_flags, SBCTL); /* start record interrupt channel */ WRITEL (devc->osdev, 1L << portc->rec_intr_chan, START (portc->rbank)); } else { WRITEL (devc->osdev, READL (devc->osdev, START (portc->rbank)) | (1L << portc-> rec_chan) | (1L << portc-> rec_intr_chan), START (portc->rbank)); } 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; if ((devc->chip_type != ALI_5451_ID) && (devc->chip_type != SIS_7018_ID)) { /* disable the record interrrupt enable channel */ ainten = READL (devc->osdev, INTEN (portc->rbank)); ainten = ainten & ~(1L << portc->rec_intr_chan); WRITEL (devc->osdev, ainten, INTEN (portc->rbank)); /* stop DMA controller */ WRITEB (devc->osdev, 0x00, SBCTL); /* stop record interrupt channel */ recstop |= 1L << portc->rec_intr_chan; WRITEL (devc->osdev, recstop, STOP (portc->rbank)); } else { /* disable rec interrupt channel */ ainten = READL (devc->osdev, INTEN (portc->rbank)); ainten = ainten & ~(1L << portc->rec_intr_chan); WRITEL (devc->osdev, ainten, INTEN (portc->rbank)); /* stop playback and playback interrupt channels */ recstop = ((1L << portc->rec_chan) | (1L << portc->rec_intr_chan)); WRITEL (devc->osdev, recstop, STOP (portc->rbank)); } /* ack any interrupts on INT_A */ recstop = READL (devc->osdev, INTR (portc->rbank)); recstop |= 1L << portc->rec_intr_chan; WRITEL (devc->osdev, recstop, INTR (portc->rbank)); } } } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } /*ARGSUSED*/ static int trident_audio_prepare_for_input (int dev, int bsize, int bcount) { trident_devc *devc = audio_engines[dev]->devc; trident_portc *portc = audio_engines[dev]->portc; dmap_t *dmap = audio_engines[dev]->dmap_in; oss_native_word bufsize, delta, eso; oss_native_word temp; unsigned int attribute = 0; unsigned char bValue; unsigned short wValue; oss_native_word dwValue; unsigned short wRecCodecSamples; int chan; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (devc->chip_type == TRIDENT_4DWAVEDX_ID) { bValue = READB (devc->osdev, 0x48); /*enable AC97 ADC */ WRITEB (devc->osdev, bValue | 0x48, 0x48); /*disable rec intr */ } if (devc->chip_type == TRIDENT_4DWAVENX_ID) { wValue = READW (devc->osdev, MISCINT); WRITEW (devc->osdev, wValue | 0x1000, MISCINT); } if ((devc->chip_type != ALI_5451_ID) && (devc->chip_type != SIS_7018_ID)) { /* Initialize legacy recording */ WRITEB (devc->osdev, 0, DMAR15); bValue = READB (devc->osdev, DMAR11) & 0x03; WRITEB (devc->osdev, bValue | 0x54, DMAR11); /* Set base address in DMAR0-DMAR3 */ WRITEL (devc->osdev, dmap->dmabuf_phys, DMAR0); /* Set count in DMAR4/DMAR5 */ eso = dmap->bytes_in_use; WRITEW (devc->osdev, eso - 1, DMAR4); /* Set speed in SBDelta */ dwValue = (48000 << 12) / portc->speed; WRITEW (devc->osdev, (unsigned short) dwValue, SBDELTA); /*Set channel interrupt blk length */ if ((portc->bits == 16) || (portc->channels == 2)) wRecCodecSamples = (unsigned short) ((eso >> 1) - 1); else wRecCodecSamples = (unsigned short) (eso - 1); dwValue = wRecCodecSamples << 16; dwValue |= wRecCodecSamples & 0x0000ffff; WRITEL (devc->osdev, dwValue, SBBL); /*set format */ devc->sb_dma_flags |= 0x19; if (portc->bits == 16) devc->sb_dma_flags |= 0xA0; if (portc->channels == 2) devc->sb_dma_flags |= 0x40; } else { #if !defined(sparc) if (devc->chip_type == ALI_5451_ID) WRITEL (devc->osdev, READL (devc->osdev, 0xd4) | ((unsigned int) 1 << 31), 0xd4); #endif /* for SiS 7018: Rec with PCM_LR, MONO_MIX, SRC_EN */ if (devc->chip_type == SIS_7018_ID) attribute = 0x8880; /* PCM->IN */ delta = (48000 << 12) / portc->speed; bufsize = dmap->bytes_in_use; if (portc->bits == 16) bufsize /= 2; if (portc->channels == 2) bufsize /= 2; bufsize--; if (portc->rbank == BANK_A) chan = portc->rec_chan; else chan = portc->rec_chan + 32; WRITEL (devc->osdev, (READL (devc->osdev, CIR) & ~0x3F) | chan, CIR); /*select current chan */ /* Now set the buffer address pointer */ WRITEL (devc->osdev, dmap->dmabuf_phys, LBA); /* Set the Size and Sampling Rate */ eso = (bufsize << 16) | (delta & 0x0000FFFF); WRITEL (devc->osdev, eso, ESO); temp = 0x80000000; /*enable gvsel */ if (portc->channels == 2) temp |= 0x00004000; /*stereo/mono */ if (portc->bits == 16) temp |= 0x0000A000; /*unsigned 8/signed 16bit */ temp |= 0x00001000; /*enable loop */ temp |= 0x003F0000; /*set pan to off */ temp |= 0x00000FFF; /*set vol to off */ WRITEL (devc->osdev, temp, TCTRL); /*set format */ WRITEL (devc->osdev, attribute << 16, FMC); WRITEL (devc->osdev, 0, EBUF1); WRITEL (devc->osdev, 0, EBUF2); /* WRITEL (devc->osdev, 0xFFFF, VOL); *//*Music/WaveVol set to max */ WRITEL (devc->osdev, 0, CSO); /*set CSO and Alpha to 0 */ } /* Now prepare the Record Interrupt Channel */ delta = (48000 << 12) / portc->speed; bufsize = dmap->fragment_size; if (portc->bits == 16) bufsize /= 2; if (portc->channels == 2) bufsize /= 2; bufsize--; if (portc->rbank == BANK_A) chan = portc->rec_intr_chan; else chan = portc->rec_intr_chan + 32; WRITEL (devc->osdev, (READL (devc->osdev, CIR) & ~0x3F) | chan, CIR); /* Now set the buffer address pointer */ WRITEL (devc->osdev, dmap->dmabuf_phys, LBA); /* Set the Size and Sampling Rate */ if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) { eso = (bufsize << 16) | (delta & 0x0000FFFF); WRITEL (devc->osdev, eso, ESO); } else { eso = ((delta << 16) & 0xff000000) | (bufsize & 0x00ffffff); WRITEL (devc->osdev, eso, ESO); } temp = 0x80000000; /*enable gvsel */ if (portc->channels == 2) temp |= 0x00004000; /*stereo/mono */ if (portc->bits == 16) temp |= 0x0000A000; /*8 unsigned/16bit signed data */ temp |= 0x00001000; /*enable loop */ temp |= 0x003F0000; /*set pan to off */ temp |= 0x00000FFF; /*set vol to off */ WRITEL (devc->osdev, temp, TCTRL); /*set format */ /* for the record interrupt channel note that no attribute should be * set for SIS7018 */ WRITEL (devc->osdev, 0, FMC); WRITEL (devc->osdev, 0, EBUF1); WRITEL (devc->osdev, 0, EBUF2); /* WRITEL (devc->osdev, 0xFFFF, VOL); *//*Music/WaveVol set to min */ if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) { WRITEL (devc->osdev, 0, CSO); /*set CSO and Alpha to 0 */ } else { WRITEL (devc->osdev, (delta << 24) | (0 & 0x00ffffff), CSO); } /* Now set the mode on portc to record */ portc->audio_enabled &= ~PCM_ENABLE_INPUT; portc->trigger_bits &= ~PCM_ENABLE_INPUT; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static int trident_audio_prepare_for_output (int dev, int bsize, int bcount) { trident_devc *devc = audio_engines[dev]->devc; trident_portc *portc = audio_engines[dev]->portc; dmap_t *dmap = audio_engines[dev]->dmap_out; oss_native_word bufsize, delta, eso; oss_native_word temp; int chan, spdif_rate, spdif_chan; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); /* prepare the main playback channel */ delta = (portc->speed * 4096) / 48000; bufsize = dmap->bytes_in_use; if (portc->bits == 16) bufsize /= 2; if (portc->channels == 2) bufsize /= 2; bufsize--; if ((devc->chip_type == ALI_5451_ID) && (devc->revision == ALI_5451_V02) && (portc->port_type == DF_SPDIF)) { ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_AUDIO, SNDCTL_MIX_WRITE, 0); portc->play_chan = 15; portc->play_intr_chan = 14; if (portc->bits == AFMT_AC3) { ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits); portc->bits = 16; portc->channels = 2; portc->speed = 48000; /* set non-pcm (AC3) mode on SPDIF control */ WRITEW (devc->osdev, (READW (devc->osdev, 0x70) & ~0x2) | 0x2, 0x70); } else /* set pcm mode on SPDIF control */ WRITEW (devc->osdev, READW (devc->osdev, 0x70) & ~0x2, 0x70); /*set up the sampling rate */ switch (portc->speed) { case 44100: spdif_rate = 0; break; case 32000: spdif_rate = 0x300; break; case 48000: default: spdif_rate = 0x200; break; } spdif_chan = READB (devc->osdev, 0x74) & 0xbf; /*select spdif_out */ spdif_chan |= 0x80; /*select right */ WRITEB (devc->osdev, spdif_chan, 0x74); WRITEB (devc->osdev, spdif_rate | 0x20, 0x72); spdif_chan &= (~0x80); /*select left */ WRITEB (devc->osdev, spdif_chan, 0x74); WRITEB (devc->osdev, spdif_rate | 0x10, 0x72); } if (portc->pbank == BANK_A) chan = portc->play_chan; else chan = portc->play_chan + 32; WRITEL (devc->osdev, (READL (devc->osdev, CIR) & ~0x3F) | chan, CIR); /*select current chan */ /* Now set the buffer address pointer */ WRITEL (devc->osdev, dmap->dmabuf_phys, LBA); /* Set the Size and Sampling Rate */ if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) { eso = (bufsize << 16) | (delta & 0x0000FFFF); WRITEL (devc->osdev, eso, ESO); } else { eso = ((delta << 16) & 0xff000000) | (bufsize & 0x00ffffff); WRITEL (devc->osdev, eso, ESO); } temp = 0x80000000; /*enable gvsel */ if (portc->channels == 2) temp |= 0x00004000; /*stereo/mono */ if (portc->bits == 16) temp |= 0x0000A000; /*8 unsigned/16bit signed data */ temp |= 0x00001000; /*enable loop */ WRITEL (devc->osdev, temp, TCTRL); /*set format */ WRITEL (devc->osdev, 0, FMC); WRITEL (devc->osdev, 0, EBUF1); WRITEL (devc->osdev, 0, EBUF2); WRITEL (devc->osdev, 0, VOL); /*Music/WaveVol set to max */ if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) { WRITEL (devc->osdev, 0, CSO); /*set CSO and Alpha to 0 */ } else { WRITEL (devc->osdev, (delta << 24) | (0 & 0x00ffffff), CSO); } /* Now prepare the playback interrupt channel */ delta = (portc->speed * 4096) / 48000; bufsize = dmap->fragment_size; if (portc->bits == 16) bufsize /= 2; if (portc->channels == 2) bufsize /= 2; bufsize--; if (portc->pbank == BANK_A) chan = portc->play_intr_chan; else chan = portc->play_intr_chan + 32; WRITEL (devc->osdev, (READL (devc->osdev, CIR) & ~0x3F) | chan, CIR); /*select current chan */ /* Now set the buffer address pointer */ WRITEL (devc->osdev, dmap->dmabuf_phys, LBA); /* Set the Size and Sampling Rate */ if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) { eso = (bufsize << 16) | (delta & 0x0000FFFF); WRITEL (devc->osdev, eso, ESO); } else { eso = ((delta << 16) & 0xff000000) | (bufsize & 0x00ffffff); WRITEL (devc->osdev, eso, ESO); } temp = 0x80000000; /*enable gvsel */ if (portc->channels == 2) temp |= 0x00004000; /*stereo/mono */ if (portc->bits == 16) temp |= 0x0000A000; /*8 unsigned/16bit signed data */ temp |= 0x00001000; /*enable loop */ temp |= 0xFFF; /* no vol */ temp |= 0x003F0000; /*set volume to off */ WRITEL (devc->osdev, temp, TCTRL); /*set format */ WRITEL (devc->osdev, 0, FMC); WRITEL (devc->osdev, 0, EBUF1); WRITEL (devc->osdev, 0, EBUF2); WRITEL (devc->osdev, 0, VOL); /*Music/WaveVol set to max */ if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) { WRITEL (devc->osdev, 0, CSO); /*set CSO and Alpha to 0 */ } else { WRITEL (devc->osdev, (delta << 24) | (0 & 0x00ffffff), CSO); } /* Now set the mode on portc to playback */ portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } #if 0 static int trident_get_buffer_pointer (int dev, dmap_t * dmap, int direction) { trident_devc *devc = audio_engines[dev]->devc; trident_portc *portc = audio_engines[dev]->portc; oss_native_word flags; int ptr = 0; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (direction == PCM_ENABLE_INPUT) { WRITEB (devc->osdev, (READB (devc->osdev, CIR) & ~0x7F) | portc->rec_chan, CIR); if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) ptr = READW (devc->osdev, CSO + 2); else /* ID_4DWAVE_NX */ { ptr = READW (devc->osdev, SBBL) & 0xFFFF; if (portc->channels > 1) ptr >>= 1; if (ptr > 0) ptr = dmap->bytes_in_use - ptr; } } if (direction == PCM_ENABLE_OUTPUT) { WRITEB (devc->osdev, (READB (devc->osdev, CIR) & ~0x7F) | portc->play_chan, CIR); if ((devc->chip_type == TRIDENT_4DWAVEDX_ID) || (devc->chip_type == SIS_7018_ID) || (devc->chip_type == ALI_5451_ID)) { ptr = READW (devc->osdev, CSO + 2); } else { ptr = READL (devc->osdev, CSO) & 0x00ffffff; } if (ptr > dmap->bytes_in_use) ptr = 0; } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return ptr; } #endif #if !defined(sparc) static int trident_alloc_buffer (int dev, dmap_t * dmap, int direction) { int err; if (dmap->dmabuf != NULL) return 0; if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0) { cmn_err (CE_WARN, "Failed to allocate a DMA buffer.\n"); return err; } if (dmap->dmabuf_phys & 0x80000000) { cmn_err (CE_WARN, "Got DMA buffer address beyond 2G limit.\n"); oss_free_dmabuf (dev, dmap); dmap->dmabuf = NULL; return OSS_ENOSPC; } return 0; } #endif static audiodrv_t trident_audio_driver = { trident_audio_open, trident_audio_close, trident_audio_output_block, trident_audio_start_input, trident_audio_ioctl, trident_audio_prepare_for_input, trident_audio_prepare_for_output, trident_audio_reset, NULL, NULL, trident_audio_reset_input, trident_audio_reset_output, trident_audio_trigger, trident_audio_set_rate, trident_audio_set_format, trident_audio_set_channels, NULL, NULL, NULL, /* trident_check_input, */ NULL, /* trident_check_output, */ #if !defined(sparc) trident_alloc_buffer, #else NULL, #endif NULL, /* trident_free_buffer, */ NULL, NULL, NULL /* trident_get_buffer_pointer */ }; #ifdef OBSOLETED_STUFF static void attach_mpu (trident_devc * devc) { struct address_info hw_config; hw_config.io_base = devc->mpu_base; hw_config.irq = -devc->irq; hw_config.dma = -1; hw_config.dma2 = -1; hw_config.always_detect = 0; hw_config.name = devc->chip_name; hw_config.driver_use_1 = 0; hw_config.driver_use_2 = 0; hw_config.osdev = devc->osdev; #ifdef CREATE_OSP CREATE_OSP (hw_config.osdev); #endif hw_config.card_subtype = 0; if (!probe_uart401 (&hw_config)) { cmn_err (CE_WARN, "MPU-401 was not detected\n"); return; } devc->mpu_attached = 1; attach_uart401 (&hw_config); } static void unload_mpu (trident_devc * devc) { struct address_info hw_config; hw_config.io_base = devc->mpu_base; hw_config.irq = devc->mpu_irq; hw_config.dma = -1; hw_config.dma2 = -1; hw_config.always_detect = 0; hw_config.name = devc->chip_name; hw_config.driver_use_1 = 0; hw_config.driver_use_2 = 0; hw_config.osdev = devc->osdev; #ifdef CREATE_OSP CREATE_OSP (hw_config.osdev); #endif /* */ hw_config.card_subtype = 0; devc->mpu_attached = 0; unload_uart401 (&hw_config); } #endif static int init_trident (trident_devc * devc) { int my_mixer, i; oss_device_t *osdev = devc->osdev; unsigned char legacy; oss_native_word global_control; int adev; unsigned int dwVal; int first_dev = 0; /* * Legacy I/O setup */ devc->mpu_attached = 0; legacy = 0xA0; /* Enable MPU, GP, FM */ switch (devc->mpu_base) { case 0x330: legacy |= 0x00; break; case 0x300: legacy |= 0x40; break; default: devc->mpu_base = 0; } pci_write_config_byte (osdev, 0x44, legacy); /*setup legacy devs */ /* pci_write_config_byte (osdev, 0x45, 0x12); *//*setup DDMA */ /* pci_write_config_byte (osdev, 0x46, 0x02); *//*setup device r/w */ /* Now reset AC97 and unmute the codec channels */ if (devc->chip_type == TRIDENT_4DWAVEDX_ID) { WRITEB (devc->osdev, 0x09, 0x40); /*set ddma */ WRITEL (devc->osdev, 0x2, 0x48); /*enable the AC97 */ } if (devc->chip_type == SIS_7018_ID) { WRITEL (devc->osdev, 0, 0x4c); WRITEL (devc->osdev, 0xF0000, 0x48); /* enable ac97 link */ } if (devc->chip_type == TRIDENT_4DWAVENX_ID) { WRITEL (devc->osdev, 0x2, 0x40); /* Setup 48KHz S/PDIF output on 4DWave NX */ WRITEL (devc->osdev, INL (devc->osdev, SP_STAT) | 4, SP_STAT); WRITEB (devc->osdev, 0x38, SP_CSO + 3); } if (devc->chip_type == ALI_5451_ID) { #ifndef sparc pci_read_config_dword (osdev, 0x7c, &dwVal); pci_write_config_dword (osdev, 0x7c, dwVal | 0x08000000); oss_udelay (5000); pci_read_config_dword (osdev, 0x7c, &dwVal); pci_write_config_dword (osdev, 0x7c, dwVal & 0xf7ffffff); oss_udelay (5000); pci_read_config_dword (osdev, 0x44, &dwVal); pci_write_config_dword (osdev, 0x44, dwVal | 0x000c0000); oss_udelay (500); pci_read_config_dword (osdev, 0x44, &dwVal); pci_write_config_dword (osdev, 0x44, dwVal & 0xfffbffff); #endif /* enable full 32bit and disable DDMA */ pci_write_config_dword (osdev, 0x40, dwVal & 0x00000008); oss_udelay (5000); } my_mixer = ac97_install (&devc->ac97devc, devc->chip_name, ac97_read, ac97_write, devc, devc->osdev); if (my_mixer >= 0) { devc->mixer_dev = my_mixer; mixer_devs[my_mixer]->priority = 9; } else return 0; #ifdef OBSOLETED_STUFF if (devc->mpu_base > 0) attach_mpu (devc); #endif for (i = 0; i < 2; i++) { char tmp_name[1024]; trident_portc *portc = &devc->portc[i]; int caps = ADEV_AUTOMODE /* | ADEV_HWMIX */ ; int porttype = DF_PCM; sprintf (tmp_name, "%s (rev %d)", devc->chip_name, devc->revision); if (i == 0) { caps |= ADEV_DUPLEX; } else { caps |= ADEV_NOINPUT; if (i > 1) caps |= ADEV_SHADOW; } #if 0 /* This will not work any more */ if ((devc->chip_type == ALI_5451_ID) && (devc->revision == ALI_5451_V02) && (i == 7)) { /* need to set SPDIF output in PCI southbridge chip 0x1533 */ while ((osdev = (pci_find_class (0x601 << 8, osdev)))) { unsigned short vendorid, deviceid; unsigned char temp; pci_read_config_word (osdev, PCI_VENDOR_ID, &vendorid); pci_read_config_word (osdev, PCI_DEVICE_ID, &deviceid); if (vendorid != 0x10b9 || deviceid != 0x1533) continue; pci_read_config_byte (osdev, 0x61, &temp); temp |= 0x40; pci_write_config_byte (osdev, 0x61, temp); pci_read_config_byte (osdev, 0x7d, &temp); temp |= 0x01; pci_write_config_byte (osdev, 0x7d, temp); pci_read_config_byte (osdev, 0x7e, &temp); temp &= (~0x20); temp |= 0x10; pci_write_config_byte (osdev, 0x7e, temp); /* SPDIF Magic!!! */ pci_read_config_byte (osdev, 0x63, &temp); temp |= 0x3; /* enable SPDIF OUT bit0=out bit1=in */ pci_write_config_byte (osdev, 0x63, temp); /* SPDIF Output in SERIAL Config reg */ temp = READL (devc->osdev, 0x48); WRITEB (devc->osdev, temp | 0x20, 0x48); temp = READL (devc->osdev, 0x74); WRITEB (devc->osdev, temp & 0xbf, 0x74); /* Enable SPDIF on play_channel 15 and setup as SPDIFOUT */ WRITEW (devc->osdev, READW (devc->osdev, 0xd4) | 0x8000, 0xd4); WRITEW (devc->osdev, READW (devc->osdev, 0xd4) & ~0x0400, 0xd4); porttype = DF_SPDIF; caps |= ADEV_SPECIAL | ADEV_FIXEDRATE; sprintf (tmp_name, "%s (S/PDIF Output)", devc->chip_name); } } #endif if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, tmp_name, &trident_audio_driver, sizeof (audiodrv_t), caps, AFMT_U8 | AFMT_S16_LE | AFMT_AC3, devc, -1)) < 0) { adev = -1; return 0; } else { if (i == 0) first_dev = adev; audio_engines[adev]->portc = portc; audio_engines[adev]->mixer_dev = my_mixer; audio_engines[adev]->rate_source = first_dev; audio_engines[adev]->min_rate = 8000; audio_engines[adev]->max_rate = 48000; audio_engines[adev]->caps |= PCM_CAP_FREERATE; #if !defined(sparc) /* * Only 31 bit memory addresses are supported except under Sparc * where the on-board audio chip supports the top half of 32 bit PCI * address space. */ audio_engines[adev]->dmabuf_maxaddr = MEMLIMIT_31BITS; #endif portc->audiodev = adev; portc->open_mode = 0; portc->audio_enabled = 0; if (audio_engines[adev]->flags & ADEV_FIXEDRATE) { audio_engines[adev]->fixed_rate = 48000; audio_engines[adev]->min_rate = 48000; audio_engines[adev]->max_rate = 48000; } portc->port_type = porttype; portc->play_chan = 3 + (2 * i); portc->play_intr_chan = 2 + (2 * i); portc->pbank = BANK_A; portc->rec_chan = 1; portc->rec_intr_chan = 0; portc->rbank = BANK_A; if (devc->chip_type == ALI_5451_ID) { portc->rec_chan = 31; portc->rec_intr_chan = 30; portc->rbank = BANK_A; } if (devc->chip_type == SIS_7018_ID) { portc->pbank = BANK_B; portc->rec_chan = 1; portc->rec_intr_chan = 0; portc->rbank = BANK_B; WRITEL (devc->osdev, READL (devc->osdev, CIR) | 0x10000, CIR); } #ifdef CONFIG_OSS_VMIX if (i == 0) vmix_attach_audiodev(devc->osdev, adev, -1, 0); #endif } devc->audio_opened = 0; /* set the global control and enable end interrupt condition */ global_control = READL (devc->osdev, CIR); global_control |= (1 << 12); /* control end intr */ WRITEL (devc->osdev, global_control, CIR); } return 1; } int oss_trident_attach (oss_device_t * osdev) { unsigned char pci_irq_line, pci_revision /*, pci_latency */ ; unsigned short pci_command, vendor, device; unsigned int pci_ioaddr; trident_devc *devc; DDB (cmn_err (CE_WARN, "Entered Trident 4DWAVE probe routine\n")); oss_pci_byteswap (osdev, 1); pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); pci_read_config_word (osdev, PCI_DEVICE_ID, &device); if ((vendor != TRIDENT_VENDOR_ID && vendor != ALI_VENDOR_ID && vendor != SIS_VENDOR_ID) || (device != TRIDENT_4DWAVEDX_ID && device != TRIDENT_4DWAVENX_ID && device != TRIDENT_4DWAVECX_ID && device != ALI_5451_ID && device != SIS_7018_ID)) return 0; 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 (%d).\n", pci_irq_line); 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->revision = pci_revision; devc->irq = pci_irq_line; switch (device) { case TRIDENT_4DWAVEDX_ID: devc->chip_name = "Trident 4DWAVEDX"; devc->chip_type = TRIDENT_4DWAVEDX_ID; break; case TRIDENT_4DWAVENX_ID: devc->chip_name = "Trident 4DWAVENX"; devc->chip_type = TRIDENT_4DWAVENX_ID; break; case TRIDENT_4DWAVECX_ID: devc->chip_name = "Trident 4DWAVECX"; devc->chip_type = TRIDENT_4DWAVECX_ID; break; case ALI_5451_ID: devc->chip_name = "ALI M5451"; devc->chip_type = ALI_5451_ID; break; case SIS_7018_ID: devc->chip_name = "SiS 7018"; devc->chip_type = SIS_7018_ID; break; default: devc->chip_name = "Trident 4DWAVE"; devc->chip_type = TRIDENT_4DWAVEDX_ID; } #ifdef MEM_MAPPED_REGISTERS pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_1, &devc->bar1addr); devc->bar1virt = (char *) MAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, 1024 * 1024); pci_command |= PCI_COMMAND_MEMORY; #else devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr); /* Remove I/O space marker in bit 0. */ devc->base &= ~0x03; pci_command |= PCI_COMMAND_IO; #endif devc->mpu_base = trident_mpu_ioaddr; devc->mpu_irq = devc->irq; /* activate the device */ pci_command |= PCI_COMMAND_MASTER; pci_write_config_word (osdev, PCI_COMMAND, pci_command); MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1); oss_register_device (osdev, devc->chip_name); if (oss_register_interrupts (devc->osdev, 0, tridentintr, NULL) < 0) { cmn_err (CE_WARN, "Can't allocate IRQ%d\n", pci_irq_line); return 0; } return init_trident (devc); /*Detected */ } int oss_trident_detach (oss_device_t * osdev) { trident_devc *devc = (trident_devc *) osdev->devc; if (oss_disable_device (osdev) < 0) return 0; WRITEL (devc->osdev, 0, CIR); if (devc->chip_type == TRIDENT_4DWAVENX_ID) WRITEL (devc->osdev, 0x0, SP_CSO); /*disable S/PDIF on NX */ #ifdef OBSOLETED_STUFF if (devc->mpu_attached) { unload_mpu (devc); devc->mpu_attached = 0; } #endif oss_unregister_interrupts (devc->osdev); MUTEX_CLEANUP (devc->mutex); MUTEX_CLEANUP (devc->low_mutex); #ifdef MEM_MAPPED_REGISTERS UNMAP_PCI_MEM(osdev, 1, devc->bar1addr, devc->bar1virt, 1024x1024); #else UNMAP_PCI_IOADDR (devc->osdev, 0); #endif oss_unregister_device (devc->osdev); return 1; }