Open Sound System |
Do you have problems with sound/audio application development? Don't panic! Click here for help! |
The same design is also used in many PC chipsets by nVidia, AMD and SiS for audio functionality.
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_ich_cfg.h" #include <oss_pci.h> #include <ac97.h> extern int intelpci_force_mmio; #define INTEL_VENDOR_ID 0x8086 #define SIS_VENDOR_ID 0x1039 #define NVIDIA_VENDOR_ID 0x10de #define AMD_VENDOR_ID 0x1022 #define SIS_DEVICE_7012 0x7012 #define INTEL_DEVICE_ICH1 0x2415 #define INTEL_DEVICE_ICH1R1 0x2425 #define INTEL_DEVICE_ICH1R2 0x7195 #define INTEL_DEVICE_ICH2 0x2445 #define INTEL_DEVICE_ICH3 0x2485 #define INTEL_DEVICE_ICH4 0x24c5 #define INTEL_DEVICE_ICH5 0x24d5 #define INTEL_DEVICE_ESB 0x25a6 #define INTEL_DEVICE_ICH6 0x266e #define INTEL_DEVICE_ICH7 0x27de #define NVIDIA_DEVICE_NFORCE 0x01b1 #define NVIDIA_DEVICE_MCP4 0x003a #define NVIDIA_DEVICE_NFORCE2 0x006a #define NVIDIA_DEVICE_CK8 0x008a #define NVIDIA_DEVICE_NFORCE3 0x00da #define NVIDIA_DEVICE_CK8S 0x00ea #define NVIDIA_DEVICE_NFORCE4 0x0059 #define NVIDIA_DEVICE_MCP51 0x026b #define AMD_DEVICE_768 0x7445 #define AMD_DEVICE_8111 0x746d extern int intelpci_rate_tuning; #define MAX_PORTC 3 #define BDL_SIZE 32 extern int ich_jacksense; typedef struct { int open_mode; int speed, bits, channels; int audio_enabled; int trigger_bits; int audiodev; int port_type; #define DF_PCM 0 #define DF_SPDIF 1 } ich_portc; typedef struct { unsigned int addr; unsigned short size; unsigned short flags; } bdl_t; typedef struct ich_devc { oss_device_t *osdev; oss_native_word base, ac97_base; oss_native_word membar_addr, ac97_membar_addr; char *membar_virt, *ac97_membar_virt; #define CTL_BASE 0 /* addressing controller regs */ #define MIXER_BASE 1 /* addressing mixer regs */ int mem_mode; #define MMAP_MODE 0 /* ICH4/ICH5 uses MEM BARS */ #define IO_MODE 1 /* ICH1/2/3/4/5 uses IO BARS */ int irq; oss_mutex_t mutex; oss_mutex_t low_mutex; /* Mixer */ ac97_devc ac97devc; int mixer_dev; int inverted_amplifier; /* Audio parameters */ int open_mode; int fifo_errors; /* Buffer Descriptor List */ char *bdlBuffer; bdl_t *playBDL, *recBDL, *spdifBDL; oss_native_word playBDL_phys, recBDL_phys, spdifBDL_phys; oss_dma_handle_t bldbuf_dma_handle; int play_currbuf, play_currfrag; int spdif_currbuf, spdif_currfrag; int rec_currbuf, rec_currfrag; #define INTEL_ICH1 0 #define INTEL_ICH3 1 #define INTEL_ICH4 2 #define SIS_7012 3 #define AMD_768 4 #define AMD_8111 5 #define NVIDIA_NFORCE 6 #define NVIDIA_NFORCE2 7 int model; char *chip_name; ich_portc portc[MAX_PORTC]; int play_frag_index[BDL_SIZE]; int rec_frag_index[BDL_SIZE]; int spdif_frag_index[BDL_SIZE]; } ich_devc; static unsigned int ich_INL (ich_devc * devc, int base, unsigned int a) { if (devc->mem_mode == MMAP_MODE) { if (base == CTL_BASE) return *(volatile unsigned int *) (devc->membar_virt + (a)); else return *(volatile unsigned int *) (devc->ac97_membar_virt + (a)); } else { if (base == CTL_BASE) return INL (devc->osdev, devc->base + a); else return INL (devc->osdev, devc->ac97_base + a); } } static unsigned short ich_INW (ich_devc * devc, int base, unsigned short a) { if (devc->mem_mode == MMAP_MODE) { if (base == CTL_BASE) return *(volatile unsigned short *) (devc->membar_virt + (a)); else return *(volatile unsigned short *) (devc->ac97_membar_virt + (a)); } else { if (base == CTL_BASE) return INW (devc->osdev, devc->base + a); else return INW (devc->osdev, devc->ac97_base + a); } } static unsigned char ich_INB (ich_devc * devc, int base, unsigned char a) { if (devc->mem_mode == MMAP_MODE) { if (base == CTL_BASE) return *(volatile unsigned char *) (devc->membar_virt + (a)); else return *(volatile unsigned char *) (devc->ac97_membar_virt + (a)); } else { if (base == CTL_BASE) return INB (devc->osdev, devc->base + a); else return INB (devc->osdev, devc->ac97_base + a); } } static void ich_OUTL (ich_devc * devc, unsigned int d, int base, unsigned int a) { if (devc->mem_mode == MMAP_MODE) { if (base == CTL_BASE) *(volatile unsigned int *) (devc->membar_virt + (a)) = d; else *(volatile unsigned int *) (devc->ac97_membar_virt + (a)) = d; } else { if (base == CTL_BASE) OUTL (devc->osdev, d, devc->base + a); else OUTL (devc->osdev, d, devc->ac97_base + a); } } static void ich_OUTW (ich_devc * devc, unsigned short d, int base, unsigned short a) { if (devc->mem_mode == MMAP_MODE) { if (base == CTL_BASE) *(volatile unsigned short *) (devc->membar_virt + (a)) = d; else *(volatile unsigned short *) (devc->ac97_membar_virt + (a)) = d; } else { if (base == CTL_BASE) OUTW (devc->osdev, d, devc->base + a); else OUTW (devc->osdev, d, devc->ac97_base + a); } } static void ich_OUTB (ich_devc * devc, unsigned char d, int base, unsigned char a) { if (devc->mem_mode == MMAP_MODE) { if (base == CTL_BASE) *(volatile unsigned char *) (devc->membar_virt + (a)) = d; else *(volatile unsigned char *) (devc->ac97_membar_virt + (a)) = d; } else { if (base == CTL_BASE) OUTB (devc->osdev, d, devc->base + a); else OUTB (devc->osdev, d, devc->ac97_base + a); } } static int ac97_read (void *devc_, int reg) { ich_devc *devc = devc_; int i = 100, status; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); status = ich_INB (devc, CTL_BASE, 0x34); while (status & 0x01 && i-- > 0) { MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); oss_udelay (10); MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); status = ich_INB (devc, CTL_BASE, 0x34); } MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return ich_INW (devc, MIXER_BASE, reg); } static int ac97_write (void *devc_, int reg, int data) { ich_devc *devc = devc_; int i = 100, status; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); status = ich_INB (devc, CTL_BASE, 0x34); while (status & 0x01 && i-- > 0) { MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); oss_udelay (10); MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); status = ich_INB (devc, CTL_BASE, 0x34); } ich_OUTW (devc, data, MIXER_BASE, reg); MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return 1; }
The top half interrupt handler
static int ichintr (oss_device_t * osdev) { int serviced = 0; ich_devc *devc = osdev->devc; ich_portc *portc; unsigned int glob_status, status, p, f; oss_native_word flags; int i; flags = 0; /* To prevent compiler warnings */ MUTEX_ENTER (devc->mutex, flags); /* Get pending interrupts and acknowledge them */ glob_status = ich_INL (devc, CTL_BASE, 0x30); ich_OUTL (devc, glob_status, CTL_BASE, 0x30);
Check the interrupt bits of the status register
if (!(glob_status & 0x0cf7)) { /* Not for me */ MUTEX_EXIT (devc->mutex, flags); return 0; } /*-------------------- Handle PCM -------------------*/ if (devc->model == SIS_7012) status = ich_INB (devc, CTL_BASE, 0x18); else status = ich_INB (devc, CTL_BASE, 0x16); if (status & 0x10) /* FIFO error */ devc->fifo_errors++; if (status & 0x08) { for (i = 0; i < MAX_PORTC - 1; i++) { portc = &devc->portc[i]; serviced = 1; if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)) /* IOC interrupt */ { dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out; p = ich_INB (devc, CTL_BASE, 0x14); if (p != devc->play_currbuf) { p = devc->play_currbuf; f = devc->play_currfrag; devc->playBDL[p].addr = dmap->dmabuf_phys + (f * dmap->fragment_size); /* SIS uses bytes, intelpci uses samples */ if (devc->model == SIS_7012) devc->playBDL[p].size = (dmap->fragment_size); else devc->playBDL[p].size = (dmap->fragment_size / 2); devc->playBDL[p].flags = 0xc000; /* IOC interrupts */ ich_OUTB (devc, p, CTL_BASE, 0x15); /* Set LVD */ devc->play_frag_index[p] = f; devc->play_currbuf = (p + 1) % BDL_SIZE; devc->play_currfrag = (f + 1) % dmap->nfrags; } oss_audio_outputintr (portc->audiodev, 1); } } if (devc->model == SIS_7012) ich_OUTB (devc, status, CTL_BASE, 0x18); /* Clear interrupts */ else ich_OUTB (devc, status, CTL_BASE, 0x16); /* Clear interrupts */ } /*------------------- handle SPDIF interrupts -------------------------*/ if (devc->model == NVIDIA_NFORCE2) { status = ich_INB (devc, CTL_BASE, 0x76); if (status & 0x08) { portc = &devc->portc[2]; serviced = 1; if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)) /* IOC interrupt */ { dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out; p = ich_INB (devc, CTL_BASE, 0x74); if (p != devc->spdif_currbuf) { p = devc->spdif_currbuf; f = devc->spdif_currfrag; devc->spdifBDL[p].addr = dmap->dmabuf_phys + (f * dmap->fragment_size); /* SIS uses bytes, intelpci uses samples */ devc->spdifBDL[p].size = (dmap->fragment_size / 2); devc->spdifBDL[p].flags = 0xc000; /* IOC interrupts */ ich_OUTB (devc, p, CTL_BASE, 0x75); /* Set LVD */ devc->spdif_frag_index[p] = f; devc->spdif_currbuf = (p + 1) % BDL_SIZE; devc->spdif_currfrag = (f + 1) % dmap->nfrags; } oss_audio_outputintr (portc->audiodev, 1); } ich_OUTB (devc, status, CTL_BASE, 0x76); /* Clear interrupts */ } } /*----------------------- Handle Recording Interrupts --------------------*/ if (devc->model == SIS_7012) status = ich_INB (devc, CTL_BASE, 0x08); else status = ich_INB (devc, CTL_BASE, 0x06); if (status & 0x08) { for (i = 0; i < MAX_PORTC - 1; i++) { portc = &devc->portc[i]; serviced = 1; if ((portc->trigger_bits & PCM_ENABLE_INPUT)) /* IOC interrupt */ { dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in; p = ich_INB (devc, CTL_BASE, 0x04); if (p != devc->rec_currbuf) { p = devc->rec_currbuf; f = devc->rec_currfrag; devc->recBDL[p].addr = dmap->dmabuf_phys + (f * dmap->fragment_size); /* SIS uses bytes, intelpci uses samples */ if (devc->model == SIS_7012) devc->recBDL[p].size = (dmap->fragment_size); else devc->recBDL[p].size = (dmap->fragment_size / 2); devc->recBDL[p].flags = 0xc000; /* IOC interrupts */ ich_OUTB (devc, p, CTL_BASE, 0x05); /* Set LVD */ devc->rec_frag_index[p] = f; devc->rec_currbuf = (p + 1) % BDL_SIZE; devc->rec_currfrag = (f + 1) % dmap->nfrags; } oss_audio_inputintr (portc->audiodev, 0); } } if (devc->model == SIS_7012) ich_OUTB (devc, status, CTL_BASE, 0x08); /* Clear int */ else ich_OUTB (devc, status, CTL_BASE, 0x06); /* Clear int */ } MUTEX_EXIT (devc->mutex, flags); return serviced; }
Audio routines
static int ich_audio_set_rate (int dev, int arg) { ich_portc *portc = audio_engines[dev]->portc; if (arg == 0) return portc->speed; if (audio_engines[dev]->flags & ADEV_FIXEDRATE) arg = 48000; if (arg > 48000) arg = 48000; if (arg < 5000) arg = 5000; portc->speed = arg; return portc->speed; } static short ich_audio_set_channels (int dev, short arg) { ich_portc *portc = audio_engines[dev]->portc; if ((arg == 1) || (arg == 2)) { audio_engines[dev]->flags |= ADEV_STEREOONLY; arg = 2; } else audio_engines[dev]->flags &= ~ADEV_STEREOONLY; if (arg>6) arg=6; if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6)) return portc->channels; portc->channels = arg; return portc->channels; } static unsigned int ich_audio_set_format (int dev, unsigned int arg) { ich_portc *portc = audio_engines[dev]->portc; if (arg == 0) return portc->bits; #if 1 if (portc->open_mode & OPEN_READ) return portc->bits = AFMT_S16_LE; #endif if (!(arg & (AFMT_S16_LE | AFMT_AC3))) return portc->bits; portc->bits = arg; return portc->bits; } /*ARGSUSED*/ static int ich_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) { return OSS_EINVAL; } static void ich_audio_trigger (int dev, int state); static void ich_audio_reset (int dev) { ich_audio_trigger (dev, 0); } static void ich_audio_reset_input (int dev) { ich_portc *portc = audio_engines[dev]->portc; ich_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); } static void ich_audio_reset_output (int dev) { ich_portc *portc = audio_engines[dev]->portc; ich_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); } /*ARGSUSED*/ static int ich_audio_open (int dev, int mode, int openflags) { ich_portc *portc = audio_engines[dev]->portc; ich_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; } if (portc->port_type == DF_SPDIF) { if (mode & OPEN_READ) { cmn_err (CE_NOTE, "The S/PDIF device supports only playback\n"); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EIO; } } else { if (devc->open_mode & mode) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } devc->open_mode |= mode; } portc->open_mode = mode; portc->audio_enabled &= ~mode; devc->fifo_errors = 0; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } static void ich_audio_close (int dev, int mode) { ich_portc *portc = audio_engines[dev]->portc; ich_devc *devc = audio_engines[dev]->devc; ich_audio_reset (dev); portc->open_mode = 0; if (devc->fifo_errors > 0) cmn_err (CE_CONT, "%d fifo errors were detected\n", devc->fifo_errors); if (portc->port_type != DF_SPDIF) devc->open_mode &= ~mode; portc->audio_enabled &= ~mode; } /*ARGSUSED*/ static void ich_audio_output_block (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { ich_portc *portc = audio_engines[dev]->portc; portc->audio_enabled |= PCM_ENABLE_OUTPUT; portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; } /*ARGSUSED*/ static void ich_audio_start_input (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { ich_portc *portc = audio_engines[dev]->portc; portc->audio_enabled |= PCM_ENABLE_INPUT; portc->trigger_bits &= ~PCM_ENABLE_INPUT; } static void ich_audio_trigger (int dev, int state) { ich_devc *devc = audio_engines[dev]->devc; ich_portc *portc = audio_engines[dev]->portc; 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)) { if (portc->port_type == DF_SPDIF) ich_OUTB (devc, 0x1d, CTL_BASE, 0x7b); /* Kickstart */ else ich_OUTB (devc, 0x1d, CTL_BASE, 0x1b); /* Kickstart */ 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; if (portc->port_type == DF_SPDIF) ich_OUTB (devc, 0x00, CTL_BASE, 0x7b); /* reset */ else ich_OUTB (devc, 0x00, CTL_BASE, 0x1b); /* reset */ } } } if (portc->open_mode & OPEN_READ) { if (state & PCM_ENABLE_INPUT) { if ((portc->audio_enabled & PCM_ENABLE_INPUT) && !(portc->trigger_bits & PCM_ENABLE_INPUT)) { ich_OUTB (devc, 0x1d, CTL_BASE, 0x0b); /* Kickstart */ 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; ich_OUTB (devc, 0x00, CTL_BASE, 0x0b); /* reset */ } } } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } /*ARGSUSED*/ static int ich_audio_prepare_for_input (int dev, int bsize, int bcount) { ich_devc *devc = audio_engines[dev]->devc; ich_portc *portc = audio_engines[dev]->portc; dmap_t *dmap = audio_engines[dev]->dmap_in; int i, n, speed; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); ich_OUTB (devc, 0x02, CTL_BASE, 0x0b); /* Reset */ ich_OUTL (devc, devc->recBDL_phys, CTL_BASE, 0x00); /* BDL base */ speed = portc->speed; speed = (speed * 240) / intelpci_rate_tuning; ac97_recrate (&devc->ac97devc, speed); n = bcount; if (n > BDL_SIZE) n = BDL_SIZE; for (i = 0; i < n; i++) { devc->recBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size); /* SiS7012 uses bytes, ICH uses samples */ if (devc->model == SIS_7012) devc->recBDL[i].size = (dmap->fragment_size); else devc->recBDL[i].size = (dmap->fragment_size / 2); devc->recBDL[i].flags = 0xc000; /* IOC interrupts */ devc->rec_frag_index[i] = i; } ich_OUTB (devc, n - 1, CTL_BASE, 0x05); /* Set last valid descriptor */ devc->rec_currbuf = n % BDL_SIZE; devc->rec_currfrag = n; if (devc->rec_currfrag >= dmap->nfrags) devc->rec_currfrag = 0; portc->audio_enabled &= ~PCM_ENABLE_INPUT; portc->trigger_bits &= ~PCM_ENABLE_INPUT; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static int ich_audio_prepare_for_output (int dev, int bsize, int bcount) { ich_devc *devc = audio_engines[dev]->devc; ich_portc *portc = audio_engines[dev]->portc; dmap_t *dmap = audio_engines[dev]->dmap_out; int i, n, speed; unsigned int tmp; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); /* We need to add ac3 pass through support */ if (devc->model == SIS_7012) { if (portc->bits == AFMT_AC3) ich_OUTB (devc, 0, CTL_BASE, 0x4c); else ich_OUTB (devc, 1, CTL_BASE, 0x4c); } ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits); if (portc->bits == AFMT_AC3) { portc->channels = 2; portc->bits = 16; } /* do SPDIF out */ if ((portc->port_type == DF_SPDIF) && (devc->model == NVIDIA_NFORCE2)) { ich_OUTB (devc, 0x02, CTL_BASE, 0x7b); /* Reset */ ich_OUTL (devc, devc->spdifBDL_phys, CTL_BASE, 0x70); /* BDL base */ n = bcount; if (n > BDL_SIZE) n = BDL_SIZE; for (i = 0; i < n; i++) { devc->spdifBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size); devc->spdifBDL[i].size = (dmap->fragment_size / 2); devc->spdifBDL[i].flags = 0xc000; /* IOC interrupts */ devc->spdif_frag_index[i] = i; }
descriptor
devc->spdif_currbuf = n % BDL_SIZE; devc->spdif_currfrag = n; if (devc->spdif_currfrag >= dmap->nfrags) devc->spdif_currfrag = 0; portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /* else do PCM */ ich_OUTB (devc, 0x02, CTL_BASE, 0x1b); /* Reset */ ich_OUTL (devc, devc->playBDL_phys, CTL_BASE, 0x10); /* BDL base */ speed = portc->speed; speed = (speed * 240) / intelpci_rate_tuning; ac97_playrate (&devc->ac97devc, speed); /* Handle 4/6 channel output on 7012 */ if (devc->model == SIS_7012) { tmp = ich_INL (devc, CTL_BASE, 0x30); /* set default to 2 channel mode */ ich_OUTB (devc, ich_INB (devc, CTL_BASE, 0x2c) & 0x3f, CTL_BASE, 0x2c); if ((portc->channels == 4) && (tmp & (1 << 20))) ich_OUTB (devc, (ich_INB (devc, CTL_BASE, 0x2c) & 0x3f) | 0x40, CTL_BASE, 0x2c); if ((portc->channels == 6) && (tmp & (1 << 21))) ich_OUTB (devc, (ich_INB (devc, CTL_BASE, 0x2c) & 0x3f) | 0x80, CTL_BASE, 0x2c); } /* Handle 4/6 channel output on evrything other than ICH1 and SIS7012 */ if ((devc->model != INTEL_ICH1) && (devc->model != SIS_7012)) { tmp = ich_INL (devc, CTL_BASE, 0x30); /* set default to 2 channel mode */ ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x2c) & 0x0cfffff, CTL_BASE, 0x2c); if ((portc->channels == 4) && (tmp & (1 << 20))) ich_OUTL (devc, (ich_INL (devc, CTL_BASE, 0x2c) & 0x00fffff) | 0x0100000, CTL_BASE, 0x2c); if ((portc->channels == 6) && (tmp & (1 << 21))) ich_OUTL (devc, (ich_INL (devc, CTL_BASE, 0x2c) & 0x00fffff) | 0x0200000, CTL_BASE, 0x2c); } n = bcount; if (n > BDL_SIZE) n = BDL_SIZE; for (i = 0; i < n; i++) { devc->playBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size); /* SiS7012 uses bytes, ICH uses samples */ if (devc->model == SIS_7012) devc->playBDL[i].size = (dmap->fragment_size); else devc->playBDL[i].size = (dmap->fragment_size / 2); devc->playBDL[i].flags = 0xc000; /* IOC interrupts */ devc->play_frag_index[i] = i; } ich_OUTB (devc, n - 1, CTL_BASE, 0x15); /* Set last valid descriptor */ devc->play_currbuf = n % BDL_SIZE; devc->play_currfrag = n; if (devc->play_currfrag >= dmap->nfrags) devc->play_currfrag = 0; portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } static int ich_get_buffer_pointer (int dev, dmap_t * dmap, int direction) { ich_devc *devc = audio_engines[dev]->devc; ich_portc *portc = audio_engines[dev]->portc; int p = 0, f = 0, c = 0; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); if (direction == PCM_ENABLE_OUTPUT) { if (portc->port_type == DF_PCM) { f = ich_INB (devc, CTL_BASE, 0x14); /* Current buffer */ if (devc->model == SIS_7012)
buffer
else
buffer
c = devc->play_frag_index[f]; /* Current fragment */ if (devc->model == SIS_7012) p = dmap->fragment_size - (p); /* Remaining bytes */ else p = dmap->fragment_size - (p * 2); /* Remaining bytes */ } if ((portc->port_type == DF_SPDIF) && (devc->model == NVIDIA_NFORCE2)) { f = ich_INB (devc, CTL_BASE, 0x74); /* Current buffer */
buffer
c = devc->play_frag_index[f]; /* Current fragment */ p = dmap->fragment_size - (p * 2); /* Remaining bytes */ } }
Handle input
if (direction == PCM_ENABLE_INPUT) { f = ich_INB (devc, CTL_BASE, 0x04); /* Current buffer */ if (devc->model == SIS_7012)
buffer
else
buffer
c = devc->rec_frag_index[f]; /* Current fragment */ if (devc->model == SIS_7012) p = dmap->fragment_size - (p); /* Remaining bytes */ else p = dmap->fragment_size - (p * 2); /* Remaining bytes */ } MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return p + c * dmap->fragment_size; } #if 0 static int ich_calibrate_speed (int dev, int nominal_speed, int true_speed) { unsigned int fix; DDB (smn_err (CE_CONT, "ich_calibrate_speed(%d, %d, %d)\n", dev, nominal_speed, true_speed)); fix = ((240 * true_speed) + nominal_speed / 2) / nominal_speed; DDB (cmn_err (CE_NOTE, "intelpci_rate_tuning = %d\n", fix)); if (fix > 1) intelpci_rate_tuning = fix; return 0; } #endif static const audiodrv_t ich_audio_driver = { ich_audio_open, ich_audio_close, ich_audio_output_block, ich_audio_start_input, ich_audio_ioctl, ich_audio_prepare_for_input, ich_audio_prepare_for_output, ich_audio_reset, NULL, NULL, ich_audio_reset_input, ich_audio_reset_output, ich_audio_trigger, ich_audio_set_rate, ich_audio_set_format, ich_audio_set_channels, NULL, NULL, NULL, /* ich_check_input, */ NULL, /* ich_check_output, */ NULL, /* ich_alloc_buffer, */ NULL, /* ich_free_buffer, */ NULL, NULL, ich_get_buffer_pointer, NULL /* ich_calibrate_speed */ }; static int ich_init (ich_devc * devc) { int my_mixer, adev, opts; int i, max_port; unsigned int reg; int first_dev = 0; oss_native_word phaddr; /* ACLink on, warm reset */ reg = ich_INL (devc, CTL_BASE, 0x2c); if ((reg & 0x02) == 0) reg |= 2; else reg |= 4; reg &= ~8; ich_OUTL (devc, reg, CTL_BASE, 0x2c); oss_udelay (500); if (devc->model == SIS_7012) { reg |= 0x10; ich_OUTL (devc, reg, CTL_BASE, 0x2c); oss_udelay (500); } /* disable interrupts */ ich_OUTB (devc, 0x00, CTL_BASE, 0x0b); ich_OUTB (devc, 0x00, CTL_BASE, 0x1b); ich_OUTB (devc, 0x00, CTL_BASE, 0x2b); ich_OUTB (devc, 0x00, CTL_BASE, 0x7b); devc->bdlBuffer = CONTIG_MALLOC (devc->osdev, 4 * 32 * 32, MEMLIMIT_32BITS, &phaddr, devc->bldbuf_dma_handle); if (devc->bdlBuffer == NULL) { cmn_err (CE_WARN, "Failed to allocate BDL\n"); return 0; } devc->playBDL = (bdl_t *) devc->bdlBuffer; devc->playBDL_phys = phaddr; devc->recBDL = (bdl_t *) (devc->bdlBuffer + (32 * 32)); devc->recBDL_phys = phaddr + 32 * 32; devc->spdifBDL = (bdl_t *) (devc->bdlBuffer + (2 * 32 * 32)); devc->spdifBDL_phys = phaddr + 2 * 32 * 32;
Init mixer
my_mixer = ac97_install_full (&devc->ac97devc, "ICH AC97 Mixer", ac97_read, ac97_write, devc, devc->osdev, devc->inverted_amplifier | (ich_jacksense?AC97_FORCE_SENSE:0)); if (my_mixer == -1) { cmn_err (CE_WARN, "AC97 mixer installation failed\n"); return 0; /* No mixer */ } devc->mixer_dev = my_mixer; mixer_devs[my_mixer]->priority = 10; /* Known motherboard device */ /* enable S/PDIF */ devc->ac97devc.spdif_slot = SPDIF_SLOT34; ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 1); /* enable variable rate mode */ ac97_write (devc, 0x2a, ac97_read (devc, 0x2a) | 9); if (!(ac97_read (devc, 0x2a) & 1)) DDB (cmn_err (CE_NOTE, "VRA not supported...using GRC\n")); /* Enable SPDIF for SiS 7012 */ if (devc->model == SIS_7012) { ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x2c) | (1 << 10), CTL_BASE, 0x2c); ich_OUTL (devc, ich_INL (devc, CTL_BASE, 0x4c) | 1, CTL_BASE, 0x4c); } if (devc->model == NVIDIA_NFORCE2) max_port = 3; else max_port = 2; for (i = 0; i < max_port; i++) { ich_portc *portc = &devc->portc[i]; char tmp_name[100]; int port_fmt = DF_PCM; int formats = AFMT_S16_LE | AFMT_AC3; char *devfile_name = ""; strcpy (tmp_name, devc->chip_name); opts = ADEV_AUTOMODE | ADEV_16BITONLY | ADEV_STEREOONLY | ADEV_COLD; if (!ac97_varrate (&devc->ac97devc)) { opts |= ADEV_FIXEDRATE; } if (i == 0) { strcpy (tmp_name, devc->chip_name); opts |= ADEV_DUPLEX; } if (i == 1) { strcpy (tmp_name, devc->chip_name); opts |= ADEV_DUPLEX | ADEV_SHADOW; } if (i == 2) { sprintf (tmp_name, "%s S/PDIF out", devc->chip_name); opts |= ADEV_NOINPUT | ADEV_SPECIAL | ADEV_FIXEDRATE; port_fmt = DF_SPDIF; devfile_name = "spdout"; } if ((adev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, tmp_name, &ich_audio_driver, sizeof (audiodrv_t), opts, formats, devc, -1, devfile_name)) < 0) { adev = -1; return 0; } else { if (i == 0) first_dev = adev; audio_engines[adev]->portc = portc; audio_engines[adev]->rate_source = first_dev; audio_engines[adev]->mixer_dev = my_mixer; audio_engines[adev]->max_block = 64 * 1024; /* fix a timeout bug with Nforce2 */ if ((devc->model == NVIDIA_NFORCE) || (devc->model == NVIDIA_NFORCE2)) { audio_engines[adev]->min_block = 4096; audio_engines[adev]->max_block = 4096; } audio_engines[adev]->min_rate = (opts & ADEV_FIXEDRATE) ? 48000 : 5000; audio_engines[adev]->max_rate = 48000; audio_engines[adev]->caps |= PCM_CAP_FREERATE; audio_engines[adev]->min_channels = 2; audio_engines[adev]->max_channels = 6; portc->open_mode = 0; portc->audio_enabled = 0; portc->audiodev = adev; portc->port_type = port_fmt; if (audio_engines[adev]->flags & ADEV_FIXEDRATE) audio_engines[adev]->fixed_rate = 48000; #ifdef CONFIG_OSS_VMIX if (i == 0) vmix_attach_audiodev(devc->osdev, adev, -1, 0); #endif } } return 1; } int oss_ich_attach (oss_device_t * osdev) { unsigned char pci_irq_line, pci_revision /* , pci_latency */ ; unsigned short pci_command, vendor, device, sub_vendor, sub_id; unsigned int pci_ioaddr0, pci_ioaddr1; unsigned int dw; ich_devc *devc; if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) { cmn_err (CE_WARN, "Out of memory\n"); return 0; } devc->osdev = osdev; osdev->devc = devc; devc->open_mode = 0; pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); pci_read_config_word (osdev, PCI_DEVICE_ID, &device); DDB (cmn_err (CE_CONT, "oss_ich_attach(Vendor %x, device %x)\n", vendor, device)); #if 0 // This check is not necessary because the kernel has already checked // the vendor&device ID if ((vendor != INTEL_VENDOR_ID && vendor != SIS_VENDOR_ID && vendor != NVIDIA_VENDOR_ID && vendor != AMD_VENDOR_ID) || (device != INTEL_DEVICE_ICH1 && device != INTEL_DEVICE_ICH1R1 && device != INTEL_DEVICE_ICH1R2 && device != INTEL_DEVICE_ICH2 && device != INTEL_DEVICE_ICH3 && device != INTEL_DEVICE_ICH4 && device != INTEL_DEVICE_ICH5 && device != INTEL_DEVICE_ESB && device != INTEL_DEVICE_ICH6 && device != INTEL_DEVICE_ICH7 && device != SIS_DEVICE_7012 && device != AMD_DEVICE_768 && device != AMD_DEVICE_8111 && device != NVIDIA_DEVICE_NFORCE && device != NVIDIA_DEVICE_NFORCE2 && device != NVIDIA_DEVICE_NFORCE3 && device != NVIDIA_DEVICE_CK8S && device != NVIDIA_DEVICE_NFORCE4 && device != NVIDIA_DEVICE_CK8 && device != NVIDIA_DEVICE_MCP51 && device != NVIDIA_DEVICE_MCP4 )) { cmn_err (CE_WARN, "Hardware not recognized (vendor=%x, dev=%x)\n", vendor, device); return 0; } #endif pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &sub_vendor); pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &sub_id); dw = (sub_id << 16) | sub_vendor; switch (dw) { case 0x202f161f: /* Gateway 7326GZ */ case 0x203a161f: /* Gateway 4028GZ or 4542GZ */ case 0x203e161f: /* Gateway 3520GZ/M210 */ case 0x204c161f: /* Kvazar-Micro Senator 3592XT */ case 0x8144104d: /* Sony VAIO PCG-TR* */ case 0x8197104d: /* Sony S1XP */ case 0x81c0104d: /* Sony VAIO type T */ case 0x81c5104d: /* Sony VAIO VGN B1VP/B1XP */ case 0x3089103c: /* Compaq Presario B3800 */ case 0x309a103c: /* HP Compaq nx4300 */ case 0x82131033: /* NEC VersaPro VJ10F/BH */ case 0x82be1033: /* NEC VersaPro VJ12F/CH */ devc->inverted_amplifier = AC97_INVERTED; cmn_err (CE_CONT, "An inverted amplifier has been autodetected\n"); break; default: devc->inverted_amplifier = 0; break; } 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_ioaddr0); pci_read_config_dword (osdev, PCI_BASE_ADDRESS_1, &pci_ioaddr1); switch (device) { case INTEL_DEVICE_ICH1: devc->model = INTEL_ICH1; devc->chip_name = "Intel ICH (2415)"; break; case INTEL_DEVICE_ICH1R1: devc->model = INTEL_ICH1; devc->chip_name = "Intel ICHR1(2425)"; break; case INTEL_DEVICE_ICH1R2: devc->model = INTEL_ICH1; devc->chip_name = "Intel ICHR2 (7195)"; break; case INTEL_DEVICE_ICH2: devc->model = INTEL_ICH1; devc->chip_name = "Intel ICH2 (2445)"; break; case INTEL_DEVICE_ICH3: devc->model = INTEL_ICH3; devc->chip_name = "Intel ICH3 (2485)"; break; case INTEL_DEVICE_ICH4: devc->model = INTEL_ICH4; devc->chip_name = "Intel ICH4 (24C5)"; break; case INTEL_DEVICE_ICH5: devc->model = INTEL_ICH4; devc->chip_name = "Intel ICH5 (24D5)"; break; case INTEL_DEVICE_ICH6: devc->model = INTEL_ICH4; devc->chip_name = "Intel ICH6 (266E)"; break; case INTEL_DEVICE_ICH7: devc->model = INTEL_ICH4; devc->chip_name = "Intel ICH7 (27DE)"; break; case INTEL_DEVICE_ESB: devc->model = INTEL_ICH4; devc->chip_name = "Intel ICH5 (25a6)"; break; case SIS_DEVICE_7012: devc->model = SIS_7012; devc->chip_name = "SiS 7012"; break; case NVIDIA_DEVICE_NFORCE: devc->model = NVIDIA_NFORCE; devc->chip_name = "Nvidia nForce"; break; case NVIDIA_DEVICE_NFORCE2: devc->model = NVIDIA_NFORCE2; devc->chip_name = "Nvidia nForce2"; pci_read_config_dword (osdev, 0x4c, &dw); dw |= 0x1000000; pci_write_config_dword (osdev, 0x4c, dw); break; case NVIDIA_DEVICE_NFORCE3: devc->model = NVIDIA_NFORCE2; devc->chip_name = "Nvidia nForce3"; pci_read_config_dword (osdev, 0x4c, &dw); dw |= 0x1000000; pci_write_config_dword (osdev, 0x4c, dw); break; case NVIDIA_DEVICE_CK8S: devc->model = NVIDIA_NFORCE2; devc->chip_name = "Nvidia CK8S"; pci_read_config_dword (osdev, 0x4c, &dw); dw |= 0x1000000; pci_write_config_dword (osdev, 0x4c, dw); break; case NVIDIA_DEVICE_NFORCE4: devc->model = NVIDIA_NFORCE2; devc->chip_name = "Nvidia nForce4"; pci_read_config_dword (osdev, 0x4c, &dw); dw |= 0x1000000; pci_write_config_dword (osdev, 0x4c, dw); break; case NVIDIA_DEVICE_CK8: devc->model = NVIDIA_NFORCE2; devc->chip_name = "Nvidia CK8"; pci_read_config_dword (osdev, 0x4c, &dw); dw |= 0x1000000; pci_write_config_dword (osdev, 0x4c, dw); break; case NVIDIA_DEVICE_MCP51: devc->model = NVIDIA_NFORCE2; devc->chip_name = "Nvidia MCP51"; pci_read_config_dword (osdev, 0x4c, &dw); dw |= 0x1000000; pci_write_config_dword (osdev, 0x4c, dw); break; case NVIDIA_DEVICE_MCP4: devc->model = NVIDIA_NFORCE2; devc->chip_name = "Nvidia MCP4"; pci_read_config_dword (osdev, 0x4c, &dw); dw |= 0x1000000; pci_write_config_dword (osdev, 0x4c, dw); break; case AMD_DEVICE_768: devc->model = AMD_768; devc->chip_name = "AMD 768"; break; case AMD_DEVICE_8111: devc->model = AMD_8111; devc->chip_name = "AMD 8111"; break; default: devc->chip_name = "Unknown ICH chip"; } if (((pci_ioaddr1 == 0) || (intelpci_force_mmio)) && (devc->model == INTEL_ICH4)) { unsigned int ioaddr; /* read bar2 and bar3 for getting mmap address */ pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_2, &ioaddr); devc->ac97_membar_addr = ioaddr; pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_3, &ioaddr); devc->membar_addr = ioaddr; /* get virtual address */ devc->ac97_membar_virt = (char *) MAP_PCI_MEM (devc->osdev, 2, devc->ac97_membar_addr, 512); devc->membar_virt = (char *) MAP_PCI_MEM (devc->osdev, 3, devc->membar_addr, 256); devc->mem_mode = MMAP_MODE; } else devc->mem_mode = IO_MODE; if (devc->mem_mode == IO_MODE) { if (devc->model == INTEL_ICH4) {
enable the IOSE bit in 0x41 for legacy mode for ICH4/ICH5
pci_write_config_byte (osdev, 0x41, 1); /* Set the secondary codec ID */ pci_write_config_byte (osdev, 0x40, 0x39); } /* Remove I/O space marker in bit 0. */ devc->ac97_base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr0); devc->base = MAP_PCI_IOADDR (devc->osdev, 1, pci_ioaddr1); devc->ac97_base &= ~0xF; devc->base &= ~0xF; } pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO | PCI_COMMAND_MEMORY; pci_write_config_word (osdev, PCI_COMMAND, pci_command); devc->irq = pci_irq_line; if (devc->mem_mode != IO_MODE) { devc->base = devc->membar_addr; } 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, ichintr, NULL) < 0) { cmn_err (CE_WARN, "Unable to install interrupt handler\n"); return 0; } return ich_init (devc); /* Detected */ } int oss_ich_detach (oss_device_t * osdev) { ich_devc *devc = (ich_devc *) osdev->devc; if (oss_disable_device (osdev) < 0) return 0; /* disable interrupts */ ich_OUTB (devc, 0x00, CTL_BASE, 0x0b); ich_OUTB (devc, 0x00, CTL_BASE, 0x1b); ich_OUTB (devc, 0x00, CTL_BASE, 0x2b); ich_OUTB (devc, 0x00, CTL_BASE, 0x7b); oss_unregister_interrupts (devc->osdev); /* disable S/PDIF */ if (devc->mixer_dev) ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 0); if (devc->bdlBuffer) CONTIG_FREE (devc->osdev, devc->bdlBuffer, 4 * 32 * 32, devc->bldbuf_dma_handle); devc->bdlBuffer = NULL; MUTEX_CLEANUP (devc->mutex); MUTEX_CLEANUP (devc->low_mutex); if ((devc->mem_mode == MMAP_MODE) && (devc->membar_addr != 0)) { UNMAP_PCI_MEM (devc->osdev, 2, devc->ac97_membar_addr, devc->ac97_membar_virt, 512); UNMAP_PCI_MEM (devc->osdev, 3, devc->membar_addr, devc->membar_virt, 256); devc->membar_addr = 0; devc->ac97_membar_addr = 0; } else { UNMAP_PCI_IOADDR (devc->osdev, 0); UNMAP_PCI_IOADDR (devc->osdev, 1); } oss_unregister_device (osdev); return 1; } #ifdef OSS_POWER_MANAGE /* Not activated in .config at this moment */ int oss_ich_power (oss_device_t *osdev, int component, int level) { // cmn_err(CE_CONT, "oss_ich_power(%d, %d)\n", component, level); return 0; /* Failed */ } #endif #ifdef OSS_SUSPEND_RESUME int oss_ich_suspend(oss_device_t *osdev) { //cmn_err(CE_CONT, "oss_ich_suspend()\n"); return 0; /* Failed */ } int oss_ich_resume(oss_device_t *osdev) { //cmn_err(CE_CONT, "oss_ich_resume()\n"); return 0; /* Failed */ } #endif