/* * Purpose: Driver for IC Ensemble ENVY24 based audio cards. * * The audio input and output devices implemented by this driver use additional * layer of buffering for channel re-interleaving. The device itself uses * 10/12 channel interleaved 32 bit format in hardware level. The * re-interleaving engine splits these multi channel devices to several * "stereo" devices. */ /* * * 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_envy24_cfg.h" #include #include #include "envy24.h" extern int envy24_skipdevs; extern int envy24_force_mono; extern int envy24_gain_sliders; int envy24_virtualout = 0; /* This used to be an config option */ extern int envy24_devmask; #define DMASK_ANALOGOUT 1 #define DMASK_ANALOGIN 2 #define DMASK_SPDIFOUT 4 #define DMASK_SPDIFIN 8 #define DMASK_MONITORIN 16 #define DMASK_RAWDEVS 32 extern envy24_auxdrv_t default_auxdrv; extern envy24_auxdrv_t ap2496_auxdrv; extern envy24_auxdrv_t d410_auxdrv; extern envy24_auxdrv_t d1010lt_auxdrv; extern envy24_auxdrv_t tdif_auxdrv; extern envy24_auxdrv_t ewx2496_auxdrv; extern envy24_auxdrv_t ews88d_auxdrv; extern envy24_auxdrv_t dmx6fire_auxdrv; static card_spec models[] = { {0xd6301412, "M Audio Delta 1010", 8, 8, MF_MAUDIO | MF_MIDI1 | MF_SPDIF | MF_WCLOCK | MF_MEEPROM}, {0xd6311412, "M Audio Delta DiO 2496", 2, 0, MF_MAUDIO | MF_SPDIF | MF_SPDSELECT | MF_MEEPROM}, {0xd6321412, "M Audio Delta 66", 4, 4, MF_MAUDIO | MF_SPDIF | MF_AKMCODEC | MF_MEEPROM}, {0xd6331412, "M Audio Delta 44", 4, 4, MF_MAUDIO | MF_AKMCODEC | MF_MEEPROM}, {0xd6341412, "M Audio Audiophile 2496", 2, 2, MF_AP | MF_SPDIF | MF_MIDI1 | MF_MEEPROM, &ap2496_auxdrv}, {0xd6381412, "M Audio Delta 410", 8, 2, MF_D410 | MF_SPDIF | MF_MEEPROM, &d410_auxdrv}, /* Delta 1010 rev E is based on 1010LT instead of the original 1010 design */ {0xd63014ff, "M Audio Delta 1010 rev E", 8, 8, MF_MIDI1 | MF_SPDIF | MF_MEEPROM | MF_WCLOCK, &d1010lt_auxdrv}, {0xd63b1412, "M Audio Delta 1010LT", 8, 8, MF_MIDI1 | MF_SPDIF | MF_MEEPROM | MF_WCLOCK, &d1010lt_auxdrv}, {0xd6351412, "M Audio Delta TDIF", 8, 8, MF_SPDIF | MF_MEEPROM | MF_WCLOCK, &tdif_auxdrv}, {0x1115153b, "Terratec EWS88MT", 8, 8, MF_MIDI1 | MF_SPDIF | MF_EWS88 | MF_AC97}, {0x112b153b, "Terratec EWS88D", 8, 8, MF_MIDI1 | MF_MIDI2 | MF_SPDIF | MF_AC97 | MF_WCLOCK, &ews88d_auxdrv}, {0x1130153b, "Terratec EWX 24/96", 2, 2, MF_SPDIF | MF_EWX2496, &ewx2496_auxdrv}, {0x1138153b, "Terratec DMX6fire 24/96", 6, 6, MF_MIDI1 | MF_MIDI2 | MF_SPDIF, &dmx6fire_auxdrv}, {0x17121412, "Generic Envy24 based card", 8, 8, MF_SPDIF | MF_MIDI1 | MF_CONSUMER | MF_HOONTECH}, {0} }; static struct speed_sel speed_tab[] = { { 8000, 0x06} , { 9600, 0x03} , { 11025, 0x0a} , { 12000, 0x02} , { 16000, 0x05} , { 22050, 0x09} , { 24000, 0x01} , { 32000, 0x04} , { 44100, 0x08} , { 48000, 0x00} , /* {64000, 0x0f}, doesn't work */ { 88200, 0x0b} , { 96000, 0x07} , { -1, 0x10} , }; int envy24_read_cci (envy24_devc * devc, int pos) { OUTB (devc->osdev, pos, devc->ccs_base + 0x03); return INB (devc->osdev, devc->ccs_base + 0x04); } void envy24_write_cci (envy24_devc * devc, int pos, int data) { OUTB (devc->osdev, pos, devc->ccs_base + 0x03); OUTB (devc->osdev, data, devc->ccs_base + 0x04); } static int eeprom_read (envy24_devc * devc, int pos) { int i, status; for (i = 0; i < 0x10000; i++) { status = INB (devc->osdev, devc->ccs_base + 0x13); if (!(status & 1)) break; } OUTB (devc->osdev, 0xa0, devc->ccs_base + 0x10); /* EEPROM read */ OUTB (devc->osdev, pos, devc->ccs_base + 0x11); /* Offset */ for (i = 0; i < 2000; i++) { status = INB (devc->osdev, devc->ccs_base + 0x13); if (!(status & 1)) break; } oss_udelay (1); return INB (devc->osdev, devc->ccs_base + 0x12); } static int load_eeprom (envy24_devc * devc, int subid) { int status, i, check; status = INB (devc->osdev, devc->ccs_base + 0x13); if (!(status & 0x80)) return 0; /* No EEPROM */ for (i = 0; i < 32; i++) { devc->eeprom[i] = eeprom_read (devc, i); devc->eeprom[i] = eeprom_read (devc, i); } DDB (cmn_err (CE_CONT, "EEPROM=")); for (i = 0; i < 10; i++) DDB (cmn_err (CE_CONT, "0x%02x, ", devc->eeprom[i])); DDB (cmn_err (CE_CONT, "\n")); check = 0; for (i = 0; i < 4; i++) { check <<= 8; check |= devc->eeprom[i]; } if (check != subid) cmn_err (CE_CONT, "Envy24 WARNING: Possible EEPROM read error %08x != %08x\n", check, subid); return 1; } static void handle_playdev (envy24_devc * devc, envy24_portc * portc, int this_frag) { int sample, nsamples, nbytes, ch; dmap_t *dmap = audio_engines[portc->dev]->dmap_out; if (!(portc->trigger_bits & PCM_ENABLE_OUTPUT) && devc->playback_started) return; nsamples = devc->hw_fragsamples; /* Number of 32 bit samples */ nbytes = nsamples * portc->channels; if (audio_engines[portc->dev]->dmap_out->flags & DMAP_POST) { if (portc->pcm_qlen > 0) portc->pcm_qlen--; } else { if (portc->pcm_qlen < devc->writeahead) portc->pcm_qlen++; } if (portc->bits & (AFMT_S16_LE | AFMT_S16_BE | AFMT_AC3)) nbytes *= 2; else if (portc-> bits & (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE)) nbytes *= 4; if (nbytes != dmap->fragment_size) return; /* Fragment size mismatch */ switch (portc->bits) { case AFMT_U8: { unsigned char *ip; int *op; ip = audio_engines[portc->dev]->dmap_out->dmabuf; ip += (dmap_get_qhead (dmap) * dmap->fragment_size); op = (int *) (devc->playbuf + devc->hw_pfragsize * this_frag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 10 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *p++ = (*ip++ ^ 0x80) << 24; } } } break; case AFMT_AC3: case AFMT_S16_LE: { short *ip; int *op; #ifdef DO_TIMINGS oss_timing_printf ("Envy24: Copy out %d, %d", dmap_get_qhead (dmap) * dmap->fragment_size, nbytes); #endif ip = (short *) (dmap->dmabuf + (dmap_get_qhead (dmap) * dmap->fragment_size)); op = (int *) (devc->playbuf + devc->hw_pfragsize * this_frag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 10 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *p++ = *ip++ << 16; } } } break; case AFMT_S16_BE: { short *ip; int *op; ip = (short *) (audio_engines[portc->dev]->dmap_out->dmabuf + (dmap_get_qhead (dmap) * dmap->fragment_size)); op = (int *) (devc->playbuf + devc->hw_pfragsize * this_frag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 10 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { short s = (short) (((*(unsigned short *) ip & 0xff) << 8) | ((*(unsigned short *) ip & 0xff00) >> 8)); ip++; *p++ = s << 16; } } } break; case AFMT_S24_LE: { int *ip; int *op; ip = (int *) (audio_engines[portc->dev]->dmap_out->dmabuf + (dmap_get_qhead (dmap) * dmap->fragment_size)); op = (int *) (devc->playbuf + devc->hw_pfragsize * this_frag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 10 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *p++ = *ip++ << 8; } } } break; case AFMT_S32_LE: { int *ip; int *op; ip = (int *) (audio_engines[portc->dev]->dmap_out->dmabuf + (dmap_get_qhead (dmap) * dmap->fragment_size)); op = (int *) (devc->playbuf + devc->hw_pfragsize * this_frag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 10 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *p++ = *ip++; } } } break; } oss_audio_outputintr (portc->dev, 1); } #ifdef DO_RIAA static __inline__ int32_t _riaa_sat31 (register int32_t a, register int32_t b) { register int64_t v = (((int64_t) a) * b) + (1 << 30); return (int32_t) (v >> 31); } #endif static void handle_recdev (envy24_devc * devc, envy24_portc * portc) { int sample, nsamples, nbytes, ch; dmap_t *dmap = audio_engines[portc->dev]->dmap_in; if (portc->trigger_bits == 0 && devc->recording_started) return; nsamples = devc->hw_fragsamples; /* Number of 32 bit samples */ nbytes = nsamples * portc->channels; if (portc->bits & (AFMT_S16_LE | AFMT_S16_BE | AFMT_AC3)) nbytes *= 2; else if (portc->bits & (AFMT_S32_LE | AFMT_S24_LE)) nbytes *= 4; if (nbytes != dmap->fragment_size) { return; /* Fragment size mismatch */ } switch (portc->bits) { case AFMT_U8: { unsigned char *ip; int *op; ip = audio_engines[portc->dev]->dmap_in->dmabuf; ip += (dmap_get_qtail (dmap) * dmap->fragment_size); op = (int *) (devc->recbuf + devc->hw_rfragsize * devc->hw_recfrag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 12 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *ip++ = ((*p++) >> 24) ^ 0x80; } } } break; case AFMT_S16_LE: #ifdef DO_RIAA if (portc->riaa_filter) { /* RIAA filtered version */ short *ip; int *op; ip = (short *) (audio_engines[portc->dev]->dmap_in->dmabuf + (dmap_get_qtail (dmap) * dmap->fragment_size)); op = (int *) (devc->recbuf + devc->hw_rfragsize * devc->hw_recfrag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (ch = 0; ch < portc->channels; ch++) { int *p = &op[portc->chnum + ch]; short *p2 = &ip[ch]; riaa_t *ff = &portc->riaa_parms[ch]; int32_t x1 = ff->x1, x2 = ff->x2, x3 = ff->x3, y1 = ff->y1, y2 = ff->y2, y3 = ff->y3, x0, y0; for (sample = 0; sample < nsamples; sample++) { int tmp = *p; p += 12; x0 = _riaa_sat31 (tmp, 0x4C30C30C); y0 = _riaa_sat31 (x0, 0xF38FB92F) + _riaa_sat31 (x1, 0xF2492994) + _riaa_sat31 (x2, 0x1AB82385) + _riaa_sat31 (x3, 0x023FB0F8) + (_riaa_sat31 (y1, 0x574DB88C) << 1) + _riaa_sat31 (y2, 0xF650F27D) + _riaa_sat31 (y3, 0xDACB84B9); x3 = x2; x2 = x1; x1 = x0; y3 = y2; y2 = y1; y1 = y0; tmp = -y0; *p2 = tmp >> 16; p2 += portc->channels; } ff->x1 = x1; ff->x2 = x2; ff->x3 = x3; ff->y1 = y1; ff->y2 = y2; ff->y3 = y3; } /* RIAA filtered version */ } else #endif { short *ip; int *op; ip = (short *) (audio_engines[portc->dev]->dmap_in->dmabuf + (dmap_get_qtail (dmap) * dmap->fragment_size)); op = (int *) (devc->recbuf + devc->hw_rfragsize * devc->hw_recfrag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 12 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *ip++ = (*p++) >> 16; } } } break; case AFMT_S32_LE: { int *ip; int *op; ip = (int *) (audio_engines[portc->dev]->dmap_in->dmabuf + (dmap_get_qtail (dmap) * dmap->fragment_size)); op = (int *) (devc->recbuf + devc->hw_rfragsize * devc->hw_recfrag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 12 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *ip++ = *p++; } } } break; case AFMT_S24_LE: { int *ip; int *op; ip = (int *) (audio_engines[portc->dev]->dmap_in->dmabuf + (dmap_get_qtail (dmap) * dmap->fragment_size)); op = (int *) (devc->recbuf + devc->hw_rfragsize * devc->hw_recfrag); VMEM_CHECK (ip, nsamples * sizeof (*ip)); VMEM_CHECK (op, nsamples * sizeof (*op)); for (sample = 0; sample < nsamples; sample++) { int *p = &op[sample * 12 + portc->chnum]; for (ch = 0; ch < portc->channels; ch++) { *ip++ = *p++ >> 8; } } } break; } oss_audio_inputintr (portc->dev, 0); } static void tank_playback_data (envy24_devc * devc) { int i, nc = devc->nr_outdevs; envy24_portc *portc; unsigned char *p; p = devc->playbuf + devc->hw_playfrag * devc->hw_pfragsize; VMEM_CHECK (p, devc->hw_pfragsize); memset (p, 0, devc->hw_pfragsize); /* Cleanup the fragment */ for (i = 0; i < nc; i++) { portc = &devc->play_portc[i]; if (!portc->open_mode) /* Not opened */ continue; handle_playdev (devc, portc, devc->hw_playfrag); } devc->hw_playfrag = (devc->hw_playfrag + 1) % devc->hw_nfrags; } static void handle_recording (envy24_devc * devc) { int i; envy24_portc *portc; /* oss_native_word flags; */ /* * TODO: Fix mutexes and move the inputintr/outputintr calls outside the * mutex block. */ /* MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); */ for (i = 0; i < devc->nr_indevs; i++) { portc = &devc->rec_portc[i]; if (!portc->open_mode) /* Not opened */ continue; handle_recdev (devc, portc); } devc->hw_recfrag = (devc->hw_recfrag + 1) % devc->hw_nfrags; /* MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); */ } extern int envy24d_get_buffer_pointer (int dev, dmap_t * dmap, int direction); static void mt_audio_intr (envy24_devc * devc) { int status; #ifdef DO_TIMINGS oss_timing_enter (DF_INTERRUPT); oss_do_timing2 (DFLAG_PROFILE, "Envy24_audio_intr"); #endif status = INB (devc->osdev, devc->mt_base + 0x00); if (devc->playback_started && (status & 0x01)) /* Playback interrupt */ { /* cmn_err(CE_CONT, "%d\n", GET_JIFFIES()); */ if (devc->direct_audio_opened & OPEN_WRITE) { envy24d_playintr (devc); } else { int ptr, qlen, i; ptr = INW (devc->osdev, devc->mt_base + 0x14); ptr = (devc->playbuffsize - ((ptr + 1) * 4)) / devc->hw_pfragsize; /* Find the number of current fragments in the hardware level buffer */ qlen = 0; i = devc->hw_playfrag; while (qlen < 15 && i != ptr) { qlen++; i = (i + 1) % devc->hw_nfrags; } if (qlen != devc->writeahead) { tank_playback_data (devc); } if (devc->hw_playfrag == ptr) /* Out of sync */ { tank_playback_data (devc); /* Try to catch the hardware pointer */ } tank_playback_data (devc); } } if (devc->recording_started && (status & 0x02)) /* Record interrupt */ { if (devc->direct_audio_opened & OPEN_READ) envy24d_recintr (devc); else handle_recording (devc); } OUTB (devc->osdev, status, devc->mt_base + 0x00); #ifdef DO_TIMINGS oss_timing_leave (DF_INTERRUPT); oss_do_timing2 (DFLAG_PROFILE, "Envy24_audio_intr done"); #endif } static int envy24intr (oss_device_t * osdev) { int status; envy24_devc *devc; devc = osdev->devc; status = INB (devc->osdev, devc->ccs_base + 0x02); if (status == 0) return 0; if (status & 0x80) /* MIDI UART 1 */ if (devc->model_data->flags & MF_MIDI1) uart401_irq (&devc->uart401devc1); if (status & 0x20) /* MIDI UART 2 */ if (devc->model_data->flags & MF_MIDI2) uart401_irq (&devc->uart401devc2); if (status & 0x10) { /*cmn_err(CE_CONT, "%d/%d.", GET_JIFFIES(), envy24d_get_buffer_pointer(11, audio_engines[11]->dmap_out, DMODE_OUTPUT)); */ mt_audio_intr (devc); } OUTB (devc->osdev, status, devc->ccs_base + 0x02); /* ACK */ return 1; } static void envy24_setup_pro_speed (envy24_devc * devc); static void envy24_setup_consumer_speed (envy24_devc * devc); void envy24_prepare_play_engine (envy24_devc * devc) { int tmp, fragsize, buffsize; if (devc->playback_prepared) return; /* Set S/PDIF sample rate indication */ if (devc->spdif_cbits[0] & 0x01) envy24_setup_pro_speed (devc); else envy24_setup_consumer_speed (devc); if (devc->model_data->flags & MF_SPDIF) { tmp = 0x80; if (devc->ac3_mode) tmp |= 0x40; /* Audio mode off */ switch (devc->speed) { case 48000: tmp |= 0x01; break; case 44100: tmp |= 0x02; break; case 32000: tmp |= 0x03; break; } if (devc->model_data->auxdrv->spdif_set) devc->model_data->auxdrv->spdif_set (devc, tmp); } if (devc->model_data->auxdrv->set_rate) devc->model_data->auxdrv->set_rate (devc); else { tmp = devc->speedbits; if (devc->syncsource != SYNC_INTERNAL) { tmp |= 0x10; /* S/PDIF input clock select */ if (devc->model_data->flags & MF_WCLOCK) /* Has world clock too */ { int cmd = envy24_read_cci (devc, 0x20); cmd |= 0x10; /* S/PDIF */ if (devc->syncsource == SYNC_WCLOCK) cmd &= ~0x10; /* World clock */ envy24_write_cci (devc, 0x20, cmd); } } OUTB (devc->osdev, tmp, devc->mt_base + 0x01); } fragsize = devc->hw_pfragsize; buffsize = devc->playbuffsize / 4 - 1; PMEM_CHECK (devc->playbuf_phys, devc->playbuffsize); OUTL (devc->osdev, devc->playbuf_phys, devc->mt_base + 0x10); /* Base */ OUTW (devc->osdev, buffsize, devc->mt_base + 0x14); /* Count */ OUTL (devc->osdev, devc->playbuf_phys, devc->mt_base + 0x10); /* Base */ OUTW (devc->osdev, buffsize, devc->mt_base + 0x14); /* Count */ OUTL (devc->osdev, devc->playbuf_phys, devc->mt_base + 0x10); /* Base */ OUTW (devc->osdev, fragsize / 4 - 1, devc->mt_base + 0x16); /* Interrupt rate */ OUTL (devc->osdev, devc->playbuf_phys, devc->mt_base + 0x10); /* Base */ devc->playback_prepared = 1; mixer_devs[devc->mixer_dev]->modify_counter++; } void envy24_launch_play_engine (envy24_devc * devc) { /* Unmask playback interrupts */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x00) & ~0x40, devc->mt_base + 0x00); OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x00) & ~0x40, devc->mt_base + 0x00); /* Kick it */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) | 0x01, devc->mt_base + 0x18); devc->playback_started = 1; if (devc->model_data->auxdrv->set_rate) devc->model_data->auxdrv->set_rate (devc); } static void start_playback (envy24_devc * devc) { devc->hw_playfrag = 0; #ifdef DO_TIMINGS oss_do_timing ("Envy24: Start playback"); #endif tank_playback_data (devc); tank_playback_data (devc); if (devc->writeahead == 2) tank_playback_data (devc); envy24_prepare_play_engine (devc); envy24_launch_play_engine (devc); } void envy24_stop_playback (envy24_devc * devc) { #ifdef DO_TIMINGS oss_do_timing ("Envy24: Stop playback"); #endif memset (devc->playbuf, 0, devc->playbuffsize); /* * Give the engine time to eat some silent samples * This makes the corresponding digital mixer inputs to drop to 0 * which decreases noise in the monitor outputs. */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01, devc->mt_base + 0x18); OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01, devc->mt_base + 0x18); /* Mask playback interrupts */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x00) | 0x40, devc->mt_base + 0x00); devc->playback_started = 0; devc->playback_prepared = 0; } void envy24_start_recording (envy24_devc * devc) { int tmp; devc->hw_recfrag = 0; OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, devc->mt_base + 0x18); OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, devc->mt_base + 0x18); oss_udelay (20); if (devc->model_data->flags & MF_SPDIF) { tmp = 0x80; switch (devc->speed) { case 48000: tmp |= 0x01; break; case 44100: tmp |= 0x02; break; case 32000: tmp |= 0x03; break; } if (devc->model_data->auxdrv->spdif_set) devc->model_data->auxdrv->spdif_set (devc, tmp); } tmp = devc->speedbits; if (devc->syncsource != SYNC_INTERNAL) { tmp |= 0x10; /* S/PDIF input clock select */ if (devc->model_data->flags & MF_WCLOCK) /* Has world clock too */ { int cmd = envy24_read_cci (devc, 0x20); cmd |= 0x10; /* S/PDIF */ if (devc->syncsource == SYNC_WCLOCK) cmd &= ~0x10; /* World clock */ envy24_write_cci (devc, 0x20, cmd); } } OUTB (devc->osdev, tmp, devc->mt_base + 0x01); if (devc->model_data->auxdrv->set_rate) devc->model_data->auxdrv->set_rate (devc); PMEM_CHECK (devc->recbuf_phys, devc->recbuffsize); OUTL (devc->osdev, devc->recbuf_phys, devc->mt_base + 0x20); /* Base */ oss_udelay (20); OUTL (devc->osdev, devc->recbuf_phys, devc->mt_base + 0x20); /* Base */ oss_udelay (20); OUTW (devc->osdev, devc->recbuffsize / 4 - 1, devc->mt_base + 0x24); /* Count */ OUTL (devc->osdev, devc->recbuf_phys, devc->mt_base + 0x20); /* Base */ oss_udelay (60); OUTW (devc->osdev, devc->hw_rfragsize / 4 - 1, devc->mt_base + 0x26); /* Interrupt rate */ oss_udelay (60); } void envy24_launch_recording (envy24_devc * devc) { #if 1 /* Unmask recording interrupts */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x00) & ~0x80, devc->mt_base + 0x00); #endif /* Kick it */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) | 0x04, devc->mt_base + 0x18); devc->recording_started = 1; mixer_devs[devc->mixer_dev]->modify_counter++; } void envy24_stop_recording (envy24_devc * devc) { OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, devc->mt_base + 0x18); OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, devc->mt_base + 0x18); /* Mask recording interrupts */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x00) | 0x80, devc->mt_base + 0x00); devc->recording_started = 0; memset (devc->recbuf, 0, devc->recbuffsize); } /* * Audio entrypoint routines */ int envy24_audio_set_rate (int dev, int arg) { envy24_devc *devc = audio_engines[dev]->devc; #if 1 int i = 0, ix = -1, df, best = 0x7fffffff; oss_native_word flags; if (arg <= 0) return devc->speed; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (devc->recording_started || devc->playback_started) { DDB (cmn_err (CE_CONT, "Requested sampling rate(1) on device %d was %d, got %d\n", dev, arg, devc->speed)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->speed; } if ((devc->open_inputs + devc->open_outputs) > 1) { DDB (cmn_err (CE_CONT, "Requested sampling rate(2) on device %d was %d, got %d\n", dev, arg, devc->speed)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->speed; } if (devc->ratelock) { DDB (cmn_err (CE_CONT, "Requested sampling rate(3) on device %d was %d, got %d\n", dev, arg, devc->speed)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->speed; } /* This is the only open device file so change the speed */ i = 0; while (speed_tab[i].speed != -1) { df = arg - speed_tab[i].speed; if (df < 0) df = -df; if (df < best) { best = df; ix = i; if (df == 0) break; } i++; } if (ix == -1) /* No matching rate */ { DDB (cmn_err (CE_CONT, "Requested sampling rate(4) on device %d was %d, got %d\n", dev, arg, devc->speed)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->speed; } devc->speed = speed_tab[ix].speed; devc->speedbits = speed_tab[ix].speedbits; #endif if (devc->speed != arg) { DDB (cmn_err (CE_CONT, "Requested sampling rate(5) on device %d was %d, got %d\n", dev, arg, devc->speed)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->speed; } DDB (cmn_err (CE_CONT, "Sampling rate set to %d\n", devc->speed)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->speed; } static void update_fragments (envy24_portc * portc) { int nsamples, nbytes, dev = portc->dev; envy24_devc *devc = audio_engines[dev]->devc; nsamples = devc->hw_fragsamples; /* Number of 32 bit samples */ nbytes = nsamples * portc->channels; if (portc->bits & (AFMT_S16_LE | AFMT_S16_BE | AFMT_AC3)) { nbytes *= 2; } else if (portc->bits & (AFMT_S32_LE | AFMT_S24_LE)) nbytes *= 4; audio_engines[dev]->min_block = nbytes; audio_engines[dev]->max_block = nbytes; } static short envy24_audio_set_channels (int dev, short arg) { envy24_portc *portc = audio_engines[dev]->portc; envy24_devc *devc = audio_engines[dev]->devc; int i, nc = devc->nr_play_channels; oss_native_word flags; if (envy24_virtualout) nc = 10; if (arg <= portc->channels) return portc->channels; /* Force mono->stereo conversion if in skip=2 mode */ if (devc->skipdevs == 2 && arg < 2) arg = 2; if (envy24_force_mono) arg = 1; if (portc->direction == DIR_INPUT) { if ((portc->chnum + arg) > devc->nr_rec_channels) return portc->channels; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); for (i = portc->channels; i < arg; i++) if (devc->rec_channel_mask & (1 << (portc->chnum + i))) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return portc->channels; } for (i = portc->channels; i < arg; i++) devc->rec_channel_mask |= (1 << (portc->chnum + i)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } else { if ((portc->chnum + arg) > nc) return portc->channels; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); for (i = portc->channels; i < arg; i++) if (devc->play_channel_mask & (1 << (portc->chnum + i))) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return portc->channels; } for (i = portc->channels; i < arg; i++) devc->play_channel_mask |= (1 << (portc->chnum + i)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } portc->channels = arg; update_fragments (portc); return portc->channels; } static unsigned int envy24_audio_set_format (int dev, unsigned int arg) { envy24_portc *portc = audio_engines[dev]->portc; if (arg == 0) return portc->bits; if (!(arg & audio_engines[dev]->oformat_mask)) return portc->bits = AFMT_S16_LE; portc->bits = arg; if (arg == AFMT_AC3) { envy24_audio_set_channels (dev, 2); } update_fragments (portc); return portc->bits; } static int envy24_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) { envy24_devc *devc = audio_engines[dev]->devc; envy24_portc *portc = audio_engines[dev]->portc; oss_native_word flags; int rt; if (arg == NULL) return OSS_EINVAL; switch (cmd) { case SNDCTL_DSP_GET_RECSRC: case SNDCTL_DSP_SET_RECSRC: case SNDCTL_DSP_GET_PLAYTGT: case SNDCTL_DSP_SET_PLAYTGT: return *arg = 0; break; case SNDCTL_DSP_GET_RECSRC_NAMES: return oss_encode_enum ((oss_mixer_enuminfo *) arg, portc->name, 0); break; case SNDCTL_DSP_GET_PLAYTGT_NAMES: return oss_encode_enum ((oss_mixer_enuminfo *) arg, portc->name, 0); break; case SNDCTL_DSP_GET_CHNORDER: *(oss_uint64_t *) arg = CHNORDER_UNDEF; return 0; } if (devc->model_data->auxdrv->spdif_ioctl == NULL) return OSS_EINVAL; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); rt = devc->model_data->auxdrv->spdif_ioctl (devc, dev, cmd, arg); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return rt; } static void envy24_audio_trigger (int dev, int state); static void envy24_audio_reset (int dev) { #ifdef DO_TIMINGS oss_do_timing ("Envy24: Reset audio"); #endif envy24_audio_trigger (dev, 0); } #define WriteCsByte(devc, b, v) (devc)->spdif_cbits[b]=(v) #define ReadCsByte(devc, b) (devc)->spdif_cbits[b] static __inline__ void WriteCsField (envy24_devc * devc, unsigned char bByteNum, unsigned short bMask, unsigned short bBits) { /* Get current reg value. */ unsigned char bTemp = ReadCsByte (devc, bByteNum); /* Clear field to be written. */ bTemp &= ~(bMask); /* Set new values. */ WriteCsByte (devc, bByteNum, (unsigned char) (bTemp | (bBits & bMask))); } static void envy24_setup_pro_speed (envy24_devc * devc) { switch (devc->speed) { case 32000: WriteCsField (devc, 0, 0xc0, 0xc0); break; case 44100: WriteCsField (devc, 0, 0xc0, 0x40); break; case 48000: WriteCsField (devc, 0, 0xc0, 0x80); break; default: WriteCsField (devc, 0, 0xc0, 0x00); break; } } static void setup_pro_mode (envy24_devc * devc) { devc->spdif_cbits[0] |= 0x01; /* Pro mode */ devc->spdif_cbits[2] |= 0x2c; /* 24-bit data word */ envy24_setup_pro_speed (devc); } static void envy24_setup_consumer_speed (envy24_devc * devc) { /* * Set the sampling rate indication */ if (devc->ac3_mode) WriteCsField (devc, 0, 0x02, 0x02); /* 1:1 = 1 */ else WriteCsField (devc, 0, 0x02, 0x00); /* 1:1 = 0 */ switch (devc->speed) { case 22050L: WriteCsField (devc, 0, 0xC0, 0x00); /* 7:6 = 00 */ WriteCsField (devc, 3, 0x0F, 0x00); /* 3:0 = 0000 */ WriteCsField (devc, 4, 0x0F, 0x09); /* 3:0 = 1001 */ break; case 32000L: WriteCsField (devc, 0, 0xC0, 0xC0); /* 7:6 = 11 */ WriteCsField (devc, 3, 0x0F, 0x03); /* 3:0 = 0011 */ WriteCsField (devc, 4, 0x0F, 0x00); /* 3:0 = 0000 */ break; case 44100L: WriteCsField (devc, 0, 0xC0, 0x40); /* 7:6 = 01 */ WriteCsField (devc, 3, 0x0F, 0x00); /* 3:0 = 0000 */ WriteCsField (devc, 4, 0x0F, 0x00); /* 3:0 = 0000 */ break; case 48000L: WriteCsField (devc, 0, 0xC0, 0x80); /* 7:6 = 10 */ WriteCsField (devc, 3, 0x0F, 0x02); /* 3:0 = 0010 */ WriteCsField (devc, 4, 0x0F, 0x00); /* 3:0 = 0000 */ break; case 88200L: WriteCsField (devc, 0, 0xC0, 0x00); /* 7:6 = 00 */ WriteCsField (devc, 3, 0x0F, 0x00); /* 3:0 = 0000 */ WriteCsField (devc, 4, 0x0F, 0x05); /* 3:0 = 0101 */ break; case 96000L: WriteCsField (devc, 0, 0xC0, 0x00); /* 7:6 = 00 */ WriteCsField (devc, 3, 0x0F, 0x00); /* 3:0 = 0000 */ WriteCsField (devc, 4, 0x0F, 0x04); /* 3:0 = 0100 */ break; default: WriteCsField (devc, 0, 0xC0, 0x00); /* 7:6 = 00 */ WriteCsField (devc, 3, 0x0F, 0x00); /* 3:0 = 0000 */ WriteCsField (devc, 4, 0x0F, 0x00); /* 3:0 = 0000 */ break; } } static void setup_consumer_mode (envy24_devc * devc) { WriteCsByte (devc, 0, ReadCsByte (devc, 0) & ~(0x02)); /* Set audio mode */ WriteCsByte (devc, 0, ReadCsByte (devc, 0) & ~(0x38)); /* Set no emphasis */ WriteCsByte (devc, 0, ReadCsByte (devc, 0) & ~(0x04)); /* Set "original" */ WriteCsByte (devc, 1, ReadCsByte (devc, 1) | (0x80)); /* Set "original" */ envy24_setup_consumer_speed (devc); } static void setup_spdif_control (envy24_devc * devc) { /* unsigned char *cbits; */ memset (devc->spdif_cbits, 0, sizeof (devc->spdif_cbits)); /* cbits = devc->spdif_cbits; */ if (devc->spdif_pro_mode) { setup_pro_mode (devc); } else { setup_consumer_mode (devc); } } /*ARGSUSED*/ static int envy24_audio_open (int dev, int mode, int open_flags) { envy24_portc *portc = audio_engines[dev]->portc; envy24_devc *devc = audio_engines[dev]->devc; oss_native_word flags; mode |= ADEV_NOVIRTUAL; if (devc->playbuf == NULL || devc->recbuf == NULL) { cmn_err (CE_WARN, "No DMA buffer\n"); return OSS_ENOSPC; } MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode != 0 || devc->direct_audio_opened != 0) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } if (portc->direction == DIR_INPUT) { if (devc->rec_channel_mask & (1 << portc->chnum)) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } devc->rec_channel_mask |= (1 << portc->chnum); } else { if (devc->play_channel_mask & (1 << portc->chnum)) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } devc->play_channel_mask |= (1 << portc->chnum); } portc->open_mode = mode; portc->channels = 1; if (devc->skipdevs == 2) portc->channels = 2; portc->pcm_qlen = 0; if (portc->direction == DIR_INPUT) { if (devc->open_inputs++ == 0 && devc->open_outputs == 0) { devc->speed = speed_tab[devc->pending_speed_sel].speed; devc->speedbits = speed_tab[devc->pending_speed_sel].speedbits; } } else { if (devc->open_inputs == 0 && devc->open_outputs++ == 0) { if (portc->flags & PORTC_SPDOUT) { setup_spdif_control (devc); } devc->speed = speed_tab[devc->pending_speed_sel].speed; devc->speedbits = speed_tab[devc->pending_speed_sel].speedbits; } } #if 1 if (devc->use_src) { /* SRC stuff */ audio_engines[dev]->flags |= ADEV_FIXEDRATE; audio_engines[dev]->fixed_rate = devc->speed; audio_engines[dev]->min_rate = devc->speed; audio_engines[dev]->max_rate = devc->speed; } else { audio_engines[dev]->flags &= ~ADEV_FIXEDRATE; audio_engines[dev]->fixed_rate = 0; audio_engines[dev]->min_rate = 8000; audio_engines[dev]->max_rate = 96000; } #endif MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static void envy24_audio_close (int dev, int mode) { envy24_devc *devc = audio_engines[dev]->devc; envy24_portc *portc = audio_engines[dev]->portc; oss_native_word flags; int i; envy24_audio_reset (dev); MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); portc->open_mode = 0; if (portc->flags & PORTC_SPDOUT) devc->ac3_mode = 0; if (portc->direction == DIR_INPUT) { devc->open_inputs--; for (i = 0; i < portc->channels; i++) devc->rec_channel_mask &= ~(1 << (portc->chnum + i)); } else { devc->open_outputs--; for (i = 0; i < portc->channels; i++) devc->play_channel_mask &= ~(1 << (portc->chnum + i)); } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } /*ARGSUSED*/ static void envy24_audio_output_block (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { } /*ARGSUSED*/ static void envy24_audio_start_input (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { } static int envy24_sync_control (int dev, int event, int mode) { envy24_devc *devc = audio_engines[dev]->devc; envy24_portc *portc = audio_engines[dev]->portc; if (event == SYNC_PREPARE) { if (mode & PCM_ENABLE_OUTPUT) { if (!devc->playback_prepared) devc->hw_playfrag = 0; handle_playdev (devc, portc, devc->hw_playfrag); handle_playdev (devc, portc, devc->hw_playfrag + 1); if (devc->writeahead == 2) handle_playdev (devc, portc, devc->hw_playfrag + 2); envy24_prepare_play_engine (devc); portc->trigger_bits |= PCM_ENABLE_OUTPUT; } if (mode & PCM_ENABLE_INPUT) { if (devc->active_inputs == 0) { envy24_start_recording (devc); } portc->trigger_bits |= PCM_ENABLE_INPUT; } return 0; } if (event == SYNC_TRIGGER) { if (mode & PCM_ENABLE_OUTPUT) { envy24_prepare_play_engine (devc); /* Just to make sure */ devc->hw_playfrag = 1 + devc->writeahead; if (devc->active_outputs++ == 0) envy24_launch_play_engine (devc); } if (mode & PCM_ENABLE_INPUT) { if (devc->active_inputs++ == 0) { devc->hw_recfrag = 0; envy24_launch_recording (devc); } } return 0; } return OSS_EIO; } static void envy24_audio_trigger (int dev, int state) { int changed; oss_native_word flags; envy24_portc *portc = audio_engines[dev]->portc; envy24_devc *devc = audio_engines[dev]->devc; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); changed = state ^ portc->trigger_bits; if (portc->direction == DIR_OUTPUT && (changed & PCM_ENABLE_OUTPUT)) { if (state & PCM_ENABLE_OUTPUT) { #ifdef DO_TIMINGS oss_do_timing ("Envy24: Trigger start output"); #endif portc->trigger_bits = state; if (devc->active_outputs++ == 0) start_playback (devc); } else { #ifdef DO_TIMINGS oss_do_timing ("Envy24: Trigger stop output"); #endif portc->trigger_bits = state; if (--devc->active_outputs == 0) envy24_stop_playback (devc); } } if (portc->direction == DIR_INPUT && (changed & PCM_ENABLE_INPUT)) { if (state & PCM_ENABLE_INPUT) { portc->trigger_bits = state; if (devc->active_inputs++ == 0) { envy24_start_recording (devc); envy24_launch_recording (devc); } } else { if (--devc->active_inputs == 0) envy24_stop_recording (devc); portc->trigger_bits = state; } } portc->trigger_bits = state; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } /*ARGSUSED*/ static int envy24_audio_prepare_for_input (int dev, int bsize, int bcount) { int nsamples, nbytes; envy24_portc *portc = audio_engines[dev]->portc; envy24_devc *devc = audio_engines[dev]->devc; oss_native_word flags; if (audio_engines[dev]->flags & ADEV_NOINPUT) return OSS_EACCES; nsamples = devc->hw_fragsamples; /* Number of 32 bit samples */ nbytes = nsamples * portc->channels; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); #ifdef DO_RIAA memset (portc->riaa_parms, 0, sizeof (portc->riaa_parms)); if (portc->riaa_filter) cmn_err (CE_CONT, "oss: RIAA filter activated for /dev/dsp%d\n", dev); #endif if (portc->bits & (AFMT_S16_LE | AFMT_S16_BE | AFMT_AC3)) { nbytes *= 2; } else if (portc->bits & (AFMT_S32_LE | AFMT_S24_LE)) nbytes *= 4; if (nbytes != bsize) { dmap_p dmap = audio_engines[dev]->dmap_in; dmap->fragment_size = bsize = nbytes; dmap->bytes_in_use = dmap->fragment_size * dmap->nfrags; if (dmap->bytes_in_use > dmap->buffsize) { dmap->nfrags = dmap->buffsize / dmap->fragment_size; dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size; } } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static int envy24_audio_prepare_for_output (int dev, int bsize, int bcount) { int nsamples, nbytes; oss_native_word flags; envy24_portc *portc = audio_engines[dev]->portc; envy24_devc *devc = audio_engines[dev]->devc; if (audio_engines[dev]->flags & ADEV_NOOUTPUT) return OSS_EACCES; nsamples = devc->hw_fragsamples; /* Number of 32 bit samples */ nbytes = nsamples * portc->channels; if (portc->flags & PORTC_SPDOUT) if (portc->bits == AFMT_AC3) devc->ac3_mode = 1; if (portc->bits & (AFMT_S16_LE | AFMT_S16_BE | AFMT_AC3)) { nbytes *= 2; } else if (portc->bits & (AFMT_S32_LE | AFMT_S32_BE)) nbytes *= 4; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (nbytes != bsize) { dmap_p dmap = audio_engines[dev]->dmap_out; cmn_err (CE_CONT, "Fragment size mismatch: hw=%d, sw=%d\n", nbytes, bsize); cmn_err (CE_NOTE, "Application bug detected. Fix ioctl() calling order\n"); oss_audio_set_error (dev, E_PLAY, OSSERR (1012, "Wrong ioctl call order"), 0); /* * Errordesc: The envy24 driver requires that number of channels, sample format and * sampling rate are set before calling any ioctl call that may lock * the fragment size prematurely. In such case the driver cannot change the * fragment size to value that is suitable for the device. * * Please use the recommended ioctl call order defined in * http://manuals.opensound.com/developer/callorder.html. */ dmap->fragment_size = bsize = nbytes; dmap->bytes_in_use = dmap->fragment_size * dmap->nfrags; if (dmap->bytes_in_use > dmap->buffsize) { dmap->nfrags = dmap->buffsize / dmap->fragment_size; dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size; } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EIO; } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static int envy24_alloc_buffer (int dev, dmap_t * dmap, int direction) { envy24_devc *devc = audio_engines[dev]->devc; if (dmap->dmabuf != NULL) return 0; #if 0 /* * Ignore the direction parameter since it's missleading. Instead use the * ADEV_NOINPUT/ADEV_NOOUTPUT flag. */ if (audio_engines[dev]->flags & ADEV_NOINPUT) direction = OPEN_WRITE; else direction = OPEN_READ; #endif dmap->buffsize = devc->skipdevs * DEV_BUFSIZE; dmap->dmabuf_phys = 0; dmap->dmabuf = KERNEL_MALLOC (dmap->buffsize); if (dmap->dmabuf == NULL) { cmn_err (CE_WARN, "Failed to allocate a DMA buffer\n"); return OSS_ENOSPC; } memset (dmap->dmabuf, 0, dmap->buffsize); return 0; } /*ARGSUSED*/ static int envy24_free_buffer (int dev, dmap_t * dmap, int direction) { if (dmap->dmabuf == NULL) return 0; #if 1 KERNEL_FREE (dmap->dmabuf); #endif dmap->dmabuf = NULL; return 0; } static int envy24_check_input (int dev) { envy24_devc *devc = audio_engines[dev]->devc; if (!devc->recording_started) return 0; cmn_err (CE_NOTE, "Input timed out.\n"); return OSS_EIO; } static int envy24_check_output (int dev) { envy24_devc *devc = audio_engines[dev]->devc; if (!devc->playback_started) return 0; cmn_err (CE_NOTE, "Output timed out\n"); return OSS_EIO; } static int envy24_local_qlen (int dev) { envy24_portc *portc = audio_engines[dev]->portc; return portc->pcm_qlen * audio_engines[dev]->dmap_out->fragment_size; } static const audiodrv_t envy24_audio_driver = { envy24_audio_open, envy24_audio_close, envy24_audio_output_block, envy24_audio_start_input, envy24_audio_ioctl, envy24_audio_prepare_for_input, envy24_audio_prepare_for_output, envy24_audio_reset, envy24_local_qlen, NULL, NULL, NULL, envy24_audio_trigger, envy24_audio_set_rate, envy24_audio_set_format, envy24_audio_set_channels, NULL, NULL, envy24_check_input, envy24_check_output, envy24_alloc_buffer, envy24_free_buffer, NULL, NULL, NULL, /* envy24_get_buffer_pointer */ NULL, /* calibrate_speed */ envy24_sync_control }; /*ARGSUSED*/ static int envy24_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) { extern int envy24_realencoder_hack; if (!envy24_realencoder_hack) { if (cmd == SOUND_MIXER_READ_DEVMASK || cmd == SOUND_MIXER_READ_RECMASK || cmd == SOUND_MIXER_READ_RECSRC || cmd == SOUND_MIXER_READ_STEREODEVS) return *arg = 0; } if (cmd == SOUND_MIXER_READ_DEVMASK || cmd == SOUND_MIXER_READ_RECMASK || cmd == SOUND_MIXER_READ_RECSRC || cmd == SOUND_MIXER_READ_STEREODEVS) return *arg = SOUND_MASK_LINE | SOUND_MASK_PCM | SOUND_MASK_MIC | SOUND_MASK_VOLUME | SOUND_MASK_CD; if (cmd == SOUND_MIXER_READ_VOLUME || cmd == SOUND_MIXER_READ_PCM || cmd == SOUND_MIXER_READ_LINE || cmd == SOUND_MIXER_READ_MIC || cmd == SOUND_MIXER_READ_CD || cmd == MIXER_READ (SOUND_MIXER_DIGITAL1)) return *arg = 100 | (100 << 8); if (cmd == SOUND_MIXER_WRITE_VOLUME || cmd == SOUND_MIXER_WRITE_PCM || cmd == SOUND_MIXER_WRITE_LINE || cmd == SOUND_MIXER_READ_MIC || cmd == SOUND_MIXER_WRITE_CD || cmd == MIXER_WRITE (SOUND_MIXER_DIGITAL1)) return *arg = 100 | (100 << 8); if (cmd == SOUND_MIXER_READ_CAPS) return *arg = SOUND_CAP_EXCL_INPUT; if (cmd == SOUND_MIXER_PRIVATE1) return *arg = 0; return OSS_EINVAL; } static int envy24_set_control (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; if (cmd == SNDCTL_MIX_READ) switch (ctrl) { case 1: return devc->pending_speed_sel; break; case 2: return devc->syncsource; break; case 3: return devc->use_src; break; case 4: { int tmp = envy24_read_cci (devc, 0x20); return !!(tmp & 0x10); } break; case 5: return devc->ratelock; break; case 6: return devc->speed; break; case 7: return devc->sync_locked = devc->model_data->auxdrv->get_locked_status (devc); default: return OSS_EIO; } if (cmd == SNDCTL_MIX_WRITE) switch (ctrl) { case 1: if (value < 0 || value > 12) return OSS_EIO; if (value != devc->pending_speed_sel) { if (devc->open_inputs == 0 && devc->open_outputs == 0) /* IDDLE */ OUTB (devc->osdev, value, devc->mt_base + 0x01); /* Make the change now */ } return devc->pending_speed_sel = value; break; case 2: if (value < 0 || value > 2) return OSS_EIO; return devc->syncsource = value; break; case 3: return devc->use_src = value; break; case 4: { int tmp = envy24_read_cci (devc, 0x20) & ~0x10; if (value) tmp |= 0x10; /* Optical */ envy24_write_cci (devc, 0x20, tmp); return !!(tmp & 0x10); } break; case 5: return devc->ratelock = value; break; case 6: return devc->speed; break; case 7: return devc->sync_locked = devc->model_data->auxdrv->get_locked_status (devc); break; default: return OSS_EIO; } return OSS_EINVAL; } static int read_mon (envy24_devc * devc, int ch, int is_right) { int tmp; if (ch >= 20) return 0; OUTB (devc->osdev, ch, devc->mt_base + 0x3a); tmp = INW (devc->osdev, devc->mt_base + 0x38); if (is_right) tmp >>= 8; tmp &= 0x7f; if (tmp > 0x60) /* Mute? */ return 0; tmp = (tmp * 15) / 10; return 144 - tmp; } static int mon_scale (int v) { if (v == 0) return 0x7f; /* Mute */ v = 144 - v; v = (10 * v) / 15; if (v > 0x60) v = 0x7f; return v; } static void mon_set (envy24_devc * devc, int ch, int left, int right) { left = mon_scale (left); right = mon_scale (right); OUTB (devc->osdev, 1, devc->mt_base + 0x3b); /* Volume change rate */ OUTB (devc->osdev, ch, devc->mt_base + 0x3a); OUTW (devc->osdev, left | (right << 8), devc->mt_base + 0x38); } static int read_peak (envy24_devc * devc, int ch) { int tmp; if (ch >= 22) return 0; OUTB (devc->osdev, ch, devc->mt_base + 0x3e); tmp = INB (devc->osdev, devc->mt_base + 0x3f); return tmp; } /*ARGSUSED*/ static int envy24_get_peak (int dev, int ctrl, unsigned int cmd, int value) { static const unsigned char peak_cnv[256] = { 0, 18, 29, 36, 42, 47, 51, 54, 57, 60, 62, 65, 67, 69, 71, 72, 74, 75, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 89, 90, 91, 92, 93, 93, 94, 95, 95, 96, 97, 97, 98, 99, 99, 100, 100, 101, 101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108, 108, 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119, 119, 119, 119, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, 123, 123, 123, 124, 124, 124, 124, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 127, 127, 127, 127, 127, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 130, 130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132, 132, 132, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 135, 135, 135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137, 137, 137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, 139, 139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, 141, 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, 143, 143, 143, 143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 144, 144, }; envy24_devc *devc = mixer_devs[dev]->devc; int i, orign, n = -1, left = 0, right = 0; for (i = 0; i < 12 && n == -1; i++) if (ctrl & (1 << i)) n = i; if (n == -1) return OSS_EINVAL; orign = n; if (ctrl & 0x80000000) n += 10; /* Recording stream */ if (cmd == SNDCTL_MIX_READ) { left = read_peak (devc, n); if (ctrl & (1 << (orign + 1))) /* Stereo mode? */ right = read_peak (devc, n + 1); else right = left; left = peak_cnv[left]; right = peak_cnv[right]; return left | (right << 8); } return OSS_EINVAL; } static int envy24_set_mon (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int i, orign, n = -1, left = 0, right = 0; for (i = 0; i < 10 && n == -1; i++) if (ctrl & (1 << i)) n = i; if (n == -1) return OSS_EINVAL; orign = n; if (ctrl & 0x80000000) n += 10; /* Recording stream */ if (cmd == SNDCTL_MIX_READ) { left = read_mon (devc, n, 0) * 10; if (ctrl & (1 << (orign + 1))) /* Stereo mode? */ right = read_mon (devc, n + 1, 1) * 10; else right = read_mon (devc, n, 1) * 10; return left | (right << 16); } else if (cmd == SNDCTL_MIX_WRITE) { left = value & 0xffff; right = (value >> 16) & 0xffff; if (right > 1440) right = 1440; if (left > 1440) left = 1440; if (ctrl & (1 << (orign + 1))) /* Stereo mode? */ { mon_set (devc, n, left / 10, 0); mon_set (devc, n + 1, 0, right / 10); } else { mon_set (devc, n, left / 10, right / 10); } return left | (right << 16); } return OSS_EINVAL; } static int get_loopback (envy24_devc * devc, int ch) { int tmp; tmp = INL (devc->osdev, devc->mt_base + 0x34); return (tmp >> (4 * ch)) & 0x07; } static int get_spdif_loopback (envy24_devc * devc, int ch) { int tmp; tmp = INL (devc->osdev, devc->mt_base + 0x34); return (tmp >> ((4 * ch) + 3)) & 0x01; } static void set_loopback (envy24_devc * devc, int ch, int val) { int tmp = INL (devc->osdev, devc->mt_base + 0x34); tmp &= ~(0x07 << (4 * ch)); tmp |= (val & 0x07) << (4 * ch); OUTL (devc->osdev, tmp, devc->mt_base + 0x34); } static void set_spdif_loopback (envy24_devc * devc, int ch, int val) { int tmp = INL (devc->osdev, devc->mt_base + 0x34); tmp &= ~(0x08 << (4 * ch)); tmp |= (val & 0x01) << ((4 * ch) + 3); OUTL (devc->osdev, tmp, devc->mt_base + 0x34); } static int envy24_set_outrout (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int tmp, i; if (cmd == SNDCTL_MIX_READ) { tmp = INW (devc->osdev, devc->mt_base + 0x30); for (i = 0; i < 8; i++) if (ctrl & (1 << i)) { tmp = (tmp >> (2 * i)) & 0x03; switch (tmp) { case 0: /* DMA */ return 0; break; case 1: /* Monitor */ return 1; break; case 2: /* Analog input loopback */ return 2 + get_loopback (devc, i); break; case 3: /* S/PDIF input loopback */ return 10 + get_spdif_loopback (devc, i); break; } } return OSS_EINVAL; } else if (cmd == SNDCTL_MIX_WRITE) { tmp = INW (devc->osdev, devc->mt_base + 0x30); for (i = 0; i < 8; i++) if (ctrl & (1 << i)) { int ch; ch = i / 2; if (i & 1) ch += 4; tmp &= ~(0x03 << (ch * 2)); /* Cleanup */ if (value == 0) /* DMA */ continue; if (value == 1) /* Monitor */ { tmp |= 1 << (ch * 2); continue; } if (value < 10) /* Analog inputs */ { tmp |= 2 << (ch * 2); set_loopback (devc, i, value - 2); continue; } tmp |= 3 << (ch * 2); set_spdif_loopback (devc, i, value - 10); } OUTW (devc->osdev, tmp, devc->mt_base + 0x30); return value; } return OSS_EINVAL; } static int envy24_set_stereo_outrout (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; int tmp, i; if (cmd == SNDCTL_MIX_READ) { tmp = INW (devc->osdev, devc->mt_base + 0x30); for (i = 0; i < 8; i++) if (ctrl & (1 << i)) { int ch; ch = i / 2; if (i & 1) ch += 4; tmp = (tmp >> (2 * ch)) & 0x03; switch (tmp) { case 0: /* DMA */ return 0; break; case 1: /* Monitor */ return 1; break; case 2: /* Analog input loopback */ return 2 + get_loopback (devc, i) / 2; break; case 3: /* S/PDIF input loopback */ return 6; break; } } return OSS_EINVAL; } else if (cmd == SNDCTL_MIX_WRITE) { tmp = INW (devc->osdev, devc->mt_base + 0x30); for (i = 0; i < 8; i++) if (ctrl & (1 << i)) { int ch; ch = i / 2; if (i & 1) ch += 4; tmp &= ~(0x03 << (ch * 2)); /* Cleanup */ if (value == 0) /* DMA */ { continue; } if (value == 1) /* Monitor */ { tmp |= 1 << (ch * 2); continue; } if (value < 6) /* Analog inputs */ { tmp |= 2 << (ch * 2); set_loopback (devc, i, (value - 2) * 2 + (i & 1)); continue; } tmp |= 3 << (ch * 2); /* S/PDIF */ set_spdif_loopback (devc, i, (value - 10) + (i & 1)); continue; } OUTW (devc->osdev, tmp, devc->mt_base + 0x30); return value; } return OSS_EINVAL; } static int read_spdif_stereo (envy24_devc * devc) { int tmp; tmp = INL (devc->osdev, devc->mt_base + 0x32); /* * Look only at the left channel. Assume the same settings on right. */ switch (tmp & 0x03) { case 0: /* From DMA */ return 0; break; case 1: /* From digital mixer */ return 1; break; case 2: /* Analog input # loopback */ return 2 + ((tmp >> 9) & 0x03); break; case 3: /* S/PDIF input loopback */ return 6; break; } return 0; } static int read_spdif_mono (envy24_devc * devc, int ch) { int tmp, v; tmp = INL (devc->osdev, devc->mt_base + 0x32); if (ch == 0) /* Left channel ? */ v = (tmp) & 0x03; else v = (tmp >> 2) & 0x03; switch (v) { case 0: /* DMA */ return 0; break; case 1: /* Monitor */ return 1; break; case 2: /* Analog input */ if (ch == 0) /* Left or right */ v = (tmp >> 8) & 0x07; else v = (tmp >> 12) & 0x07; return 2 + v; break; case 3: if (ch == 0) /* Left or right */ v = (tmp >> 11) & 0x01; else v = (tmp >> 15) & 0x01; return 10 + v; break; } return 0; } static int write_spdif_mono (envy24_devc * devc, int ch, int val) { int tmp = 0, v; tmp = INW (devc->osdev, devc->mt_base + 0x32); if (val == 0) /* DMA */ { if (ch == 0) /* Left */ tmp &= ~0x0003; else tmp &= ~0x000c; goto do_ne; } if (val == 1) /* Monitor */ { if (ch == 0) /* Left */ { tmp &= ~0x0003; tmp |= 0x0001; } else { tmp &= ~0x000c; tmp |= 0x0004; } goto do_ne; } if (val < 10) /* Analog inputs */ { v = (val - 2) & 0x07; if (ch == 0) /* Left */ { tmp &= ~(0x0003 | (0x07 << 8)); tmp |= 0x02 | (v << 8); } else { tmp &= ~(0x000c | (0x07 << 12)); tmp |= 0x08 | (v << 12); } goto do_ne; } /* Else S/PDIF */ if (ch == 0) /* Left */ { tmp &= ~(1 << 11); tmp |= 0x0003; if (val == 11) tmp |= 1 << 11; } else { tmp &= ~(1 << 15); tmp |= 0x000c; if (val == 11) tmp |= 1 << 15; } do_ne: OUTW (devc->osdev, tmp, devc->mt_base + 0x32); return val; } static int write_spdif_stereo (envy24_devc * devc, int val) { int tmp = 0, v; if (val == 0) /* DMA */ { tmp = 0x0000; goto do_ne; } if (val == 1) /* Monitor */ { tmp = 0x0005; goto do_ne; } if (val < 6) /* Analog inputs */ { tmp = 0x000a; v = (val - 2) * 2; tmp |= (v << 8); tmp |= ((v + 1) << 12); goto do_ne; } /* Else S/PDIF */ tmp = 0x800f; do_ne: OUTW (devc->osdev, tmp, devc->mt_base + 0x32); return val; } static int envy24_set_spdifrout (int dev, int ctrl, unsigned int cmd, int value) { envy24_devc *devc = mixer_devs[dev]->devc; if (cmd == SNDCTL_MIX_READ) { if (ctrl == 3) return read_spdif_stereo (devc); else return read_spdif_mono (devc, ctrl - 1); } else if (cmd == SNDCTL_MIX_WRITE) { if (ctrl == 3) return write_spdif_stereo (devc, value); else return write_spdif_mono (devc, ctrl - 1, value); } return OSS_EINVAL; } /*ARGSUSED*/ static int create_output_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, num, skip; char tmp[64]; int nc = devc->nr_play_channels; if (envy24_virtualout) { mask = 0; nc = 10; for (i = 0; i < nc; i++) mask |= (1 << i); } if ((group = mixer_ext_create_group (dev, 0, "ENVY24_OUTPUT")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; for (i = 0; i < nc; i += skip) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ sprintf (tmp, "@pcm%d", devc->play_portc[i / 2].dev); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_MONVOL | MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; if ((err = mixer_ext_create_control (dev, group, num, envy24_get_peak, MIXT_STEREOPEAK, "-", 144, MIXF_READABLE | MIXF_DECIBEL)) < 0) return err; } return 0; } /*ARGSUSED*/ static int create_input_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->inportmask, group, err, num, skip; char tmp[64]; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_INPUT")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; for (i = 0; i < devc->nr_rec_channels && i < 10; i += skip) { num = (1 << i); if (!(mask & num)) continue; /* Not present */ num |= 0x80000000; /* Input flag */ if (i == 8) strcpy (tmp, "ENVY24_SPDIN"); else sprintf (tmp, "ENVY24_IN%d/%d", i + 1, i + 2); num |= (1 << (i + 1)); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_MONVOL | MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; if ((err = mixer_ext_create_control (dev, group, num, envy24_get_peak, MIXT_STEREOPEAK, "-", 144, MIXF_READABLE | MIXF_DECIBEL)) < 0) return err; } num = (1 << 11); if ((err = mixer_ext_create_control (dev, group, num, envy24_get_peak, MIXT_STEREOPEAK, "MONITOR", 144, MIXF_READABLE | MIXF_DECIBEL)) < 0) return err; return 0; } /*ARGSUSED*/ static int create_mon_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, num, skip; char tmp[64]; int nc = devc->nr_play_channels; if (envy24_virtualout) { mask = 0; nc = 10; for (i = 0; i < nc; i++) mask |= (1 << i); } if ((group = mixer_ext_create_group (dev, 0, "ENVY24_MON")) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; for (i = 0; i < nc; i += skip) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDOUT"); else sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_MONVOL | MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDOUTL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDOUTR"); else sprintf (tmp, "ENVY24_OUT%d", i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_MONVOL | MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; } } mask = devc->inportmask; for (i = 0; i < devc->nr_rec_channels && i < 10; i += skip) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ num |= 0x80000000; /* Input flag */ if (devc->skipdevs == 2) { if (i == 8) strcpy (tmp, "ENVY24_SPDIN"); else sprintf (tmp, "ENVY24_IN%d/%d", i + 1, i + 2); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_MONVOL | MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; } else { if (i == 8) strcpy (tmp, "ENVY24_SPDINL"); else if (i == 9) strcpy (tmp, "ENVY24_SPDINR"); else sprintf (tmp, "ENVY24_IN%d", i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_MONVOL | MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; } } return 0; } /*ARGSUSED*/ static int create_peak_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, num, skip; int nc = devc->nr_play_channels; char tmp[64]; if ((group = mixer_ext_create_group (dev, 0, "ENVY24_PEAK")) < 0) return group; skip = 2; for (i = 0; i < nc; i += skip) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ { if (i == 8) strcpy (tmp, "ENVY24_SPDOUT"); else sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_get_peak, MIXT_STEREOPEAK, tmp, 144, MIXF_READABLE | MIXF_DECIBEL)) < 0) return err; } } mask = devc->inportmask; for (i = 0; i < devc->nr_rec_channels; i += skip) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ num |= 0x80000000; /* Input flag */ { if (i == 8) strcpy (tmp, "ENVY24_SPDIN"); else if (i == 10) strcpy (tmp, "ENVY24_MAIN"); else sprintf (tmp, "ENVY24_IN%d/%d", i + 1, i + 2); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_get_peak, MIXT_STEREOPEAK, tmp, 144, MIXF_READABLE | MIXF_DECIBEL)) < 0) return err; } } return 0; } void envy24_set_enum_mask (int dev, int ctl, oss_native_word mask) { oss_mixext *ext; int i; ext = mixer_find_ext (dev, ctl); if (ext == NULL) { cmn_err (CE_WARN, "Cannot locate the mixer extension\n"); return; } memset (ext->enum_present, 0, sizeof (ext->enum_present)); for (i = 0; i < 32; i++) if (mask & (1 << i)) ext->enum_present[i / 8] |= (1 << (i % 8)); } /*ARGSUSED*/ static int create_rout_mixer (int dev, envy24_devc * devc, int root) { int i, mask = devc->outportmask, group, err, skip, num, chnum; char tmp[64]; if ((group = mixer_ext_create_group_flags (dev, 0, "ENVY24_ROUTE", MIXF_FLAT)) < 0) return group; skip = devc->skipdevs; if (skip != 2) skip = 1; for (i = 0; i < 8; i += skip) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ if (devc->skipdevs == 2) { oss_native_word tmpmask = 0x00000001; int j; if (i < 2) tmpmask |= 0x00000002; for (j = 0; j < 8; j++) if (mask & (1 << j)) tmpmask |= 1 << ((j / 2) + 2); if (devc->model_data->flags & MF_SPDIF) tmpmask |= 0x00000040; sprintf (tmp, "ENVY24_OUT%d/%d", i + 1, i + 2); chnum = i; num = (1 << chnum) | (1 << (chnum + 1)); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_stereo_outrout, MIXT_ENUM, tmp, 7, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; envy24_set_enum_mask (dev, err, tmpmask); } else { oss_native_word tmpmask = 0x00000001; int j; sprintf (tmp, "ENVY24_OUT%d", i + 1); chnum = i; num = 1 << chnum; if (i < 2) tmpmask |= (1 << 1); for (j = 0; j < 8; j++) if (mask & (1 << j)) tmpmask |= 1 << (j + 2); if (devc->model_data->flags & MF_SPDIF) tmpmask |= (3 << 10); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_outrout, MIXT_ENUM, tmp, 12, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; envy24_set_enum_mask (dev, err, tmpmask); } } mask = devc->inportmask; if (devc->model_data->flags & MF_SPDIF) { if (devc->skipdevs == 2) { oss_native_word tmpmask = 0x00000043; int j; for (j = 0; j < 8; j++) if (mask & (1 << j)) tmpmask |= (1 << ((j / 2) + 2)); if ((err = mixer_ext_create_control (dev, group, 3, envy24_set_spdifrout, MIXT_ENUM, "ENVY24_SPDIF", 7, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; envy24_set_enum_mask (dev, err, tmpmask); } else { oss_native_word tmpmask = 0x00000c03; int j; for (j = 0; j < 8; j++) if (mask & (1 << j)) tmpmask |= (1 << (j + 2)); if ((err = mixer_ext_create_control (dev, group, 1, envy24_set_spdifrout, MIXT_ENUM, "ENVY24_SPDIFL", 12, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; envy24_set_enum_mask (dev, err, tmpmask); if ((err = mixer_ext_create_control (dev, group, 2, envy24_set_spdifrout, MIXT_ENUM, "ENVY24_SPDIFR", 12, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; envy24_set_enum_mask (dev, err, tmpmask); } } #if 0 for (i = 0; i < devc->nr_rec_channels && i < 10; i += skip) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ num |= 0x80000000; /* Input flag */ if (devc->skipdevs == 2) { sprintf (tmp, "ENVY24_IN%d/%d", i + 1, i + 2); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; } else { sprintf (tmp, "ENVY24_IN%d", i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24_set_mon, MIXT_STEREOSLIDER16, tmp, 1440, MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return err; } } #endif return 0; } static int envy24_mix_init (int dev) { envy24_devc *devc = mixer_devs[dev]->devc; int group, err, ctl; int n; extern int envy24_mixerstyle; if ((group = mixer_ext_create_group (dev, 0, "ENVY24")) < 0) return group; if (envy24_skipdevs == 2) switch (envy24_mixerstyle) { case 2: /* New style input and output mixer sections */ if ((err = create_output_mixer (dev, devc, group)) < 0) return err; if ((err = create_input_mixer (dev, devc, group)) < 0) return err; break; default: /* Traditional mixer (peak meters and montor gains separated) */ if ((err = create_peak_mixer (dev, devc, group)) < 0) return err; if ((err = create_mon_mixer (dev, devc, group)) < 0) return err; break; } if ((err = create_rout_mixer (dev, devc, group)) < 0) return err; if (devc->model_data->auxdrv->mixer_init) if ((err = devc->model_data->auxdrv->mixer_init (devc, dev, group)) < 0) return err; if ((err = mixer_ext_create_control (dev, group, 1, envy24_set_control, MIXT_ENUM, "ENVY24_RATE", 12, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; mixer_ext_set_strings (dev, err, "8000 9600 11025 12000 16000 22050 24000 32000 44100 48000 88200 96000", 0); if (devc->model_data->flags & (MF_SPDIF | MF_WCLOCK)) { n = 2; if (devc->model_data->flags & MF_WCLOCK) n = 3; if ((err = mixer_ext_create_control (dev, group, 2, envy24_set_control, MIXT_ENUM, "ENVY24_SYNC", n, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } if (devc->model_data->flags & MF_SPDSELECT) { if ((err = mixer_ext_create_control (dev, group, 4, envy24_set_control, MIXT_ENUM, "ENVY24_SPDIN", 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; } #if 0 /* Always on */ if ((err = mixer_ext_create_control (dev, group, 3, envy24_set_control, MIXT_ONOFF, "ENVY24_SRC", 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; #endif if ((err = mixer_ext_create_control (dev, group, 5, envy24_set_control, MIXT_ONOFF, "ENVY24_RATELOCK", 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; if ((ctl = mixer_ext_create_control (dev, group, 6, envy24_set_control, MIXT_VALUE, "ENVY24_ACTRATE", 96000, MIXF_READABLE)) < 0) return ctl; mixer_ext_set_description(dev, ctl, "Sample rate currently used by the device"); #if 1 if (devc->model_data->auxdrv->get_locked_status) { devc->sync_locked = devc->model_data->auxdrv->get_locked_status (devc); if ((err = mixer_ext_create_control (dev, group, 7, envy24_set_control, MIXT_ONOFF, "ENVY24_LOCKED", 1, MIXF_READABLE)) < 0) return err; } #endif if (devc->model_data->auxdrv->spdif_mixer_init) if ((err = devc->model_data->auxdrv->spdif_mixer_init (devc, dev, group)) < 0) return err; return 0; } static mixer_driver_t envy24_mixer_driver = { envy24_mixer_ioctl }; static int install_adev (envy24_devc * devc, char *name, int flags, int skip, int portc_flags, char *port_id, char *devfile_name) { int dev, i; adev_p adev; int fmts = 0, last; extern int envy24_realencoder_hack; if (portc_flags & PORTC_SPDOUT) fmts |= AFMT_AC3; if ((dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, name, &envy24_audio_driver, sizeof (audiodrv_t), ADEV_AUTOMODE | ADEV_NOMMAP | flags | ADEV_NOVIRTUAL, fmts | AFMT_S16_LE | AFMT_S32_LE | AFMT_S24_LE, devc, -1, devfile_name)) < 0) { dev = -1; return 0; } else { envy24_portc *portc; adev = audio_engines[dev]; if (devc->first_dev == -1) devc->first_dev = dev; for (i = 0; speed_tab[i].speed != -1; i++) adev->rates[adev->nrates++] = speed_tab[i].speed; adev->vmix_flags = 0; if (flags == DIR_OUTPUT) { last = 10; audio_engines[dev]->port_number = devc->curr_outch; audio_engines[dev]->min_rate = 8000; audio_engines[dev]->max_rate = 96000; portc = &devc->play_portc[devc->nr_outdevs++]; portc->chnum = devc->curr_outch; strncpy (portc->name, port_id, sizeof (portc->name) - 1); portc->name[sizeof (portc->name) - 1] = 0; devc->curr_outch += skip; if (portc_flags & PORTC_SPDOUT) audio_engines[dev]->caps |= PCM_CAP_DIGITALOUT; if (portc_flags & PORTC_SPDIN) audio_engines[dev]->caps |= PCM_CAP_DIGITALIN; } else { last = 12; portc = &devc->rec_portc[devc->nr_indevs++]; audio_engines[dev]->port_number = devc->curr_inch + 10; portc->chnum = devc->curr_inch; strncpy (portc->name, port_id, sizeof (portc->name) - 1); portc->name[sizeof (portc->name) - 1] = 0; devc->curr_inch += skip; #ifdef DO_RIAA portc->riaa_filter = 0; #endif } portc->flags = portc_flags; audio_engines[dev]->devc = devc; audio_engines[dev]->portc = portc; audio_engines[dev]->rate_source = devc->first_dev; switch (skip) { case 1: audio_engines[dev]->caps |= DSP_CH_MONO; audio_engines[dev]->min_channels = 1; audio_engines[dev]->max_channels = (envy24_force_mono) ? 1 : last - portc->chnum; break; case 2: audio_engines[dev]->caps |= DSP_CH_STEREO; audio_engines[dev]->min_channels = 1; audio_engines[dev]->max_channels = last - portc->chnum; break; default: audio_engines[dev]->caps |= DSP_CH_MULTI; audio_engines[dev]->min_channels = 1; audio_engines[dev]->max_channels = last - portc->chnum; } audio_engines[dev]->mixer_dev = devc->mixer_dev; portc->dev = dev; portc->open_mode = 0; portc->is_active = 0; portc->direction = flags; portc->trigger_bits = 0; if (envy24_realencoder_hack && flags == DIR_INPUT && devc->nr_indevs > 1) if (oss_install_mixer (OSS_MIXER_DRIVER_VERSION, devc->osdev, devc->osdev, "Dummy mixer", &envy24_mixer_driver, sizeof (mixer_driver_t), devc) >= 0) { mixer_devs[devc->mixer_dev]->priority = -1; /* Don't use as the default mixer */ } } return 1; } static int install_output_devices (envy24_devc * devc, int mask) { char tmp[512], id[32]; int i, nc, portc_flags = 0; char *lr = "", *kind; nc = devc->nr_play_channels = MAX_ODEV; devc->nr_rec_channels = MAX_IDEV; if (devc->skipdevs < 1) devc->skipdevs = 1; for (i = 0; i < nc; i += devc->skipdevs) { char *devfile_name = ""; if (devc->skipdevs != 2) lr = (i & 1) ? "R" : "L"; kind = ""; if (!(mask & (1 << devc->curr_outch))) kind = "(virtual) "; switch (devc->curr_outch) { case 8: case 9: portc_flags = PORTC_SPDOUT; sprintf (tmp, "%s %sS/PDIF out %s", devc->model_data->product, kind, lr); sprintf (id, "SPDIF%s", lr); devfile_name = "spdout"; break; default: if (devc->skipdevs > 1) { sprintf (tmp, "%s %sout%d/%d", devc->model_data->product, kind, i + 1, i + devc->skipdevs); sprintf (id, "OUT%d/%d", i + 1, i + devc->skipdevs); } else { sprintf (tmp, "%s %sout%d", devc->model_data->product, kind, i + 1); sprintf (id, "OUT%d", i + 1); } } if (mask & (1 << devc->curr_outch)) install_adev (devc, tmp, ADEV_NOINPUT, devc->skipdevs, portc_flags, id, devfile_name); else devc->curr_outch += devc->skipdevs; } return 1; } /*ARGSUSED*/ static int install_virtual_output_devices (envy24_devc * devc, int mask) { #if 0 char tmp[512]; int i, nc; char *lr = ""; nc = devc->nr_play_channels = MAX_ODEV; devc->nr_rec_channels = MAX_IDEV; if (envy24_virtualout) { nc = 10; } if (devc->skipdevs < 1) devc->skipdevs = 1; for (i = 0; i < nc; i += devc->skipdevs) { if (devc->skipdevs != 2) lr = (i & 1) ? "R" : "L"; switch (devc->curr_outch) { case 8: case 9: sprintf (tmp, "%s virtual out %s", devc->model_data->product, lr); break; default: if (devc->skipdevs > 1) sprintf (tmp, "%s virtual out%d/%d", devc->model_data->product, i + 1, i + devc->skipdevs); else sprintf (tmp, "%s virtual out%d", devc->model_data->product, i + 1); } if (!(mask & (1 << devc->curr_outch))) /* Not done yet */ install_adev (devc, tmp, ADEV_NOINPUT, devc->skipdevs, 0, "virtual", ""); // TODO: Find better device file name else devc->curr_outch += devc->skipdevs; } #endif return 1; } static int install_input_devices (envy24_devc * devc, int mask) { char tmp[512], id[32]; int i, portc_flags = 0; char *lr = ""; devc->nr_play_channels = MAX_ODEV; devc->nr_rec_channels = MAX_IDEV; if (devc->skipdevs < 1) devc->skipdevs = 1; for (i = 0; i < devc->nr_rec_channels; i += devc->skipdevs) { char *devfile_name = ""; if (devc->skipdevs != 2) lr = (i & 1) ? "R" : "L"; switch (devc->curr_inch) { case 8: case 9: portc_flags = PORTC_SPDIN; sprintf (tmp, "%s S/PDIF in %s", devc->model_data->product, lr); sprintf (id, "SPDIF%s", lr); devfile_name = "spdin"; break; case 10: case 11: sprintf (tmp, "%s input from mon. mixer %s", devc->model_data->product, lr); sprintf (id, "MON%s", lr); devfile_name = "mon"; break; default: if (devc->skipdevs > 1) { sprintf (tmp, "%s in%d/%d", devc->model_data->product, i + 1, i + devc->skipdevs); sprintf (id, "IN%d/%d", i + 1, i + devc->skipdevs); } else { sprintf (tmp, "%s in%d", devc->model_data->product, i + 1); sprintf (id, "IN%d", i + 1); } } if (mask & (1 << devc->curr_inch)) install_adev (devc, tmp, ADEV_NOOUTPUT, devc->skipdevs, portc_flags, id, devfile_name); else devc->curr_inch += devc->skipdevs; } OUTL (devc->osdev, 0x00224466, devc->mt_base + 0x34); /* 1 to 1 input routing */ return 1; } static int install_audio_devices (envy24_devc * devc) { extern int envy24_swapdevs; int maskout, maskin, i; #define setmask(m, b) m|=(1<<(b)) maskout = maskin = 0; if (envy24_devmask == 0) envy24_devmask = 65535; if (envy24_devmask & DMASK_MONITORIN) { setmask (maskin, 10); /* Monitor input left */ setmask (maskin, 11); /* Monitor input right */ } if (devc->model_data->flags & MF_SPDIF) { if (envy24_devmask & DMASK_SPDIFIN) { setmask (maskin, 8); /* S/PDIF left */ setmask (maskin, 9); /* S/PDIF right */ } if (envy24_devmask & DMASK_SPDIFOUT) { setmask (maskout, 8); /* S/PDIF left */ setmask (maskout, 9); /* S/PDIF right */ } if (devc->model_data->auxdrv->spdif_set) devc->model_data->auxdrv->spdif_set (devc, 0x20); } if (envy24_devmask & DMASK_ANALOGOUT) for (i = 0; i < devc->model_data->nr_outs; i++) setmask (maskout, i); if (envy24_devmask & DMASK_ANALOGIN) for (i = 0; i < devc->model_data->nr_ins; i++) setmask (maskin, i); devc->inportmask = maskin; devc->outportmask = maskout; if (envy24_swapdevs) { install_input_devices (devc, maskin); install_output_devices (devc, maskout); install_virtual_output_devices (devc, maskout); } else { install_output_devices (devc, maskout); install_input_devices (devc, maskin); install_virtual_output_devices (devc, maskout); } for (i = 0; i < 10; i += devc->skipdevs) { int num = 1 << i; if (devc->skipdevs == 2) num |= 1 << (i + 1); if (maskout & num) envy24_set_mon (devc->mixer_dev, num, SNDCTL_MIX_WRITE, 1340 | (1340 << 16)); if (maskin & num) envy24_set_mon (devc->mixer_dev, 0x80000000 | num, SNDCTL_MIX_WRITE, 1340 | (1340 << 16)); } if (envy24_devmask & DMASK_RAWDEVS) envy24d_install (devc); return 1; } static int ac97_read (void *devc_, int reg) { envy24_devc *devc = devc_; int i, status; OUTB (devc->osdev, reg, devc->ccs_base + 0x08); OUTB (devc->osdev, reg, devc->ccs_base + 0x08); OUTB (devc->osdev, reg, devc->ccs_base + 0x08); OUTB (devc->osdev, 0x10, devc->ccs_base + 0x09); for (i = 0; i < 1000; i++) { status = INB (devc->osdev, devc->ccs_base + 0x09); if (!(status & 0x10)) { status = INW (devc->osdev, devc->ccs_base + 0x0a); return status; } } return 0xffff; } static int ac97_writereg (void *devc_, int reg, int data) { envy24_devc *devc = devc_; int i, status; OUTB (devc->osdev, reg, devc->ccs_base + 0x08); OUTB (devc->osdev, reg, devc->ccs_base + 0x08); OUTB (devc->osdev, reg, devc->ccs_base + 0x08); OUTB (devc->osdev, 0x20, devc->ccs_base + 0x09); for (i = 0; i < 1000; i++) { status = INB (devc->osdev, devc->ccs_base + 0x09); if (!(status & 0x20)) { OUTW (devc->osdev, data & 0xffff, devc->ccs_base + 0x0a); return 1; } } return 0; } static int ac97_write (void *devc, int reg, int data) { int ret; ac97_writereg (devc, reg, data); ac97_writereg (devc, reg, data); ret = ac97_writereg (devc, reg, data); return ret; } static void install_consumer_devices (envy24_devc * devc) { #if 1 int i, status; OUTB (devc->osdev, 0x80, devc->ccs_base + 0x09); /* Cold reset mixer */ oss_udelay (200); OUTB (devc->osdev, 0x00, devc->ccs_base + 0x09); /* Release reset */ oss_udelay (200); for (i = 0; i < 1000; i++) { status = INB (devc->osdev, devc->ccs_base + 0x09); if (status & 0x80) break; oss_udelay (1000); } if (i >= 1000) { } #endif devc->consumer_mixer_dev = ac97_install (&devc->ac97devc, "Envy24 consumer mixer", ac97_read, ac97_write, devc, devc->osdev); /* Route monitor output to consumer AC97 */ OUTB (devc->osdev, 0x01, devc->mt_base + 0x3c); /* Set consumer volumes to full */ envy24_write_cci (devc, 3, 0); envy24_write_cci (devc, 4, 0); } static int maudio_load_eeprom (envy24_devc * devc) { int status; status = INB (devc->osdev, devc->ccs_base + 0x13); if (!(status & 0x80)) return 0; /* No EEPROM */ envy24_write_cci (devc, 0x22, devc->eeprom[0xc]); /* GPIO direction */ envy24_write_cci (devc, 0x21, devc->eeprom[0xa]); /* GPIO write mask */ envy24_write_cci (devc, 0x20, devc->eeprom[0xb]); /* GPIO data */ return 1; } static int envy24_init (envy24_devc * devc) { extern int envy24_nfrags; oss_native_word phaddr; int err; /* Disable all interrupts */ OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01); OUTB (devc->osdev, 0xff, devc->mt_base + 0x00); if ((err = oss_register_interrupts (devc->osdev, 0, envy24intr, NULL)) < 0) { cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); return 0; } if (envy24_skipdevs < 1) envy24_skipdevs = 1; if (envy24_skipdevs > 2) envy24_skipdevs = 2; devc->skipdevs = envy24_skipdevs; if (envy24_skipdevs != 1) envy24_force_mono = 0; if (devc->model_data->flags & MF_MIDI1) { char name[128]; oss_native_word flags; sprintf (name, "%s #1", devc->model_data->product); MUTEX_ENTER (devc->mutex, flags); uart401_init (&devc->uart401devc1, devc->osdev, devc->ccs_base + 0x0c, name); MUTEX_EXIT (devc->mutex, flags); /* Enable UART1 interrupts */ OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) & ~0x80, devc->ccs_base + 0x01); } if (devc->model_data->flags & MF_MIDI2) { char name[128]; oss_native_word flags; sprintf (name, "%s #2", devc->model_data->product); MUTEX_ENTER (devc->mutex, flags); uart401_init (&devc->uart401devc2, devc->osdev, devc->ccs_base + 0x1c, name); MUTEX_EXIT (devc->mutex, flags); /* Enable UART2 interrupts */ OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) & ~0x20, devc->ccs_base + 0x01); } devc->speedbits = 0; devc->speed = 48000; devc->pending_speed_sel = 9; if (devc->model_data->flags & (MF_MEEPROM)) maudio_load_eeprom (devc); if (devc->model_data->auxdrv->card_init) devc->model_data->auxdrv->card_init (devc); if (devc->model_data->auxdrv->spdif_init) devc->model_data->auxdrv->spdif_init (devc); if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, devc->osdev, devc->osdev, devc->model_data->product, &envy24_mixer_driver, sizeof (mixer_driver_t), devc)) >= 0) { int n = 50; if (devc->skipdevs == 1) n += 30; mixer_ext_set_init_fn (devc->mixer_dev, envy24_mix_init, n); } if (envy24_nfrags != 2 && envy24_nfrags != 4 && envy24_nfrags != 8 && envy24_nfrags != 16 && envy24_nfrags != 32 && envy24_nfrags != 64) envy24_nfrags = 16; devc->playbuffsize = HW_PLAYBUFFSIZE; devc->recbuffsize = HW_RECBUFFSIZE; devc->hw_nfrags = envy24_nfrags; devc->hw_pfragsize = devc->playbuffsize / devc->hw_nfrags; devc->hw_rfragsize = devc->recbuffsize / devc->hw_nfrags; devc->hw_fragsamples = devc->hw_pfragsize / 40; /* # of 32 bit samples/fragment/channel */ if (devc->hw_pfragsize % 40) cmn_err (CE_WARN, "Error! Bad per channel fragment size\n"); devc->hw_playfrag = 0; devc->hw_recfrag = 0; devc->playbuf = CONTIG_MALLOC (devc->osdev, HW_ALLOCSIZE, MEMLIMIT_28BITS, &phaddr, devc->playbuf_dma_handle); if (devc->playbuf == NULL) { cmn_err (CE_WARN, "Failed to allocate %d bytes of DMA buffer\n", HW_ALLOCSIZE); return 0; } devc->playbuf_phys = phaddr; if ((devc->playbuf_phys + HW_ALLOCSIZE) >= (256 * 1024 * 1024)) { cmn_err (CE_WARN, "Got DMA buffer beyond address 256M.\n"); cmn_err (CE_CONT, "Reboot and try again\n"); return 1; } OUTL (devc->osdev, devc->playbuf_phys, devc->mt_base + 0x10); /* Play base */ devc->recbuf = CONTIG_MALLOC (devc->osdev, HW_ALLOCSIZE, MEMLIMIT_28BITS, &phaddr, devc->recbuf_dma_handle); if (devc->recbuf == NULL) { cmn_err (CE_WARN, "Failed to allocate %d bytes of DMA buffer\n", HW_ALLOCSIZE); return 0; } devc->recbuf_phys = phaddr; if ((devc->recbuf_phys + HW_ALLOCSIZE) >= (256 * 1024 * 1024)) { cmn_err (CE_WARN, "Got DMA buffer beyond address 256M.\n"); cmn_err (CE_CONT, "Reboot and try again\n"); return 1; } OUTL (devc->osdev, devc->recbuf_phys, devc->mt_base + 0x20); /* Rec base */ devc->playback_started = 0; devc->recording_started = 0; devc->playback_prepared = 0; devc->recording_prepared = 0; devc->writeahead = 1; #ifdef __VXWORKS__ devc->ratelock = 0; #else devc->ratelock = 1; #endif memset (devc->playbuf, 0, HW_ALLOCSIZE); memset (devc->recbuf, 0, HW_ALLOCSIZE); install_audio_devices (devc); if (devc->consumer_ac97_present || (devc->model_data->flags & MF_CONSUMER)) install_consumer_devices (devc); /* Enable professional rec/play interrupts */ OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) & ~0x10, devc->ccs_base + 0x01); setup_spdif_control (devc); if (devc->model_data->auxdrv->spdif_set) devc->model_data->auxdrv->spdif_set (devc, 0x20); #if 1 /* Make sure everything is initialized */ envy24_prepare_play_engine (devc); envy24_launch_play_engine (devc); oss_udelay (10000); envy24_stop_playback (devc); #endif #if 0 { char line[200], *s = line; *line = 0; for (i = 0; i < 0x20; i++) { if (!(i % 16)) { if (*line != 0) cmn_err (CE_CONT, "%s\n", line); s = line; sprintf (s, "CCS%02x: ", i); s = line + strlen (line); } sprintf (s, "%02x ", INB (devc->osdev, devc->ccs_base + i)); s = line + strlen (line); } *line = 0; for (i = 0; i < 0x40; i++) { if (!(i % 16)) { if (*line != 0) cmn_err (CE_CONT, "%s\n", line); s = line; sprintf (s, "MT%02x: ", i); s = line + strlen (line); } sprintf (s, "%02x ", INB (devc->osdev, devc->mt_base + i)); s = line + strlen (line); } cmn_err (CE_CONT, "%s\n", line); } #endif return 1; } int oss_envy24_attach (oss_device_t * osdev) { envy24_devc *devc; static int status; unsigned char pci_irq_line; unsigned short pci_command, vendor, device; unsigned int subvendor; unsigned int pci_ioaddr, pci_ioaddr3; int i; char *name = "Generic ENVY24"; DDB (cmn_err (CE_CONT, "Entered Envy24 probe routine\n")); if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) { cmn_err (CE_WARN, "Out of memory\n"); return 0; } devc->osdev = osdev; osdev->devc = devc; pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); pci_read_config_word (osdev, PCI_DEVICE_ID, &device); if (vendor != ICENSEMBLE_VENDOR_ID || device != ICENSEMBLE_ENVY24_ID) return 0; pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr); DDB (cmn_err (CE_CONT, "Device found at I/O %x\n", pci_ioaddr)); pci_read_config_dword (osdev, PCI_BASE_ADDRESS_3, &pci_ioaddr3); devc->active_inputs = 0; devc->active_outputs = 0; devc->sync_locked = 1; devc->first_dev = -1; 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, 0x2c, &subvendor); devc->ccs_base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr) & ~0x3; DDB (cmn_err (CE_CONT, "CCS base %x/%lx\n", pci_ioaddr, devc->ccs_base)); devc->mt_base = MAP_PCI_IOADDR (devc->osdev, 3, pci_ioaddr3) & ~0x3; DDB (cmn_err (CE_CONT, "MT base %x/%lx\n", pci_ioaddr3, devc->mt_base)); pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; pci_write_config_word (osdev, PCI_COMMAND, pci_command); /* Reset the chip */ OUTB (devc->osdev, 0x81, devc->ccs_base + 0x00); oss_udelay (200); /* Release reset */ OUTB (devc->osdev, 0x01, devc->ccs_base + 0x00); oss_udelay (200); devc->nr_outdevs = devc->nr_indevs = 0; devc->curr_outch = devc->curr_inch = 0; devc->playbuffsize = 0; devc->recbuffsize = 0; devc->playbuf = devc->recbuf = NULL; status = INB (devc->osdev, devc->ccs_base + 0x13); if (status & 0x80) /* EEPROM present */ { static char resol_tab[] = { 16, 18, 20, 24 }; unsigned char tmpbyte; load_eeprom (devc, subvendor); /* Fix bit 0x80 of EEPROM location 0x07. */ pci_read_config_byte (osdev, 0x61, &tmpbyte); tmpbyte &= 0x80; devc->eeprom[0x07] &= ~0x80; devc->eeprom[0x07] |= tmpbyte; #if 1 devc->eeprom[0x07] |= 0x80; #endif pci_write_config_byte (osdev, 0x60, devc->eeprom[0x06]); pci_write_config_byte (osdev, 0x61, devc->eeprom[0x07]); pci_write_config_byte (osdev, 0x62, devc->eeprom[0x08]); pci_write_config_byte (osdev, 0x63, devc->eeprom[0x09]); #if 1 if (devc->eeprom[0x06] & 0x20) DDB (cmn_err (CE_CONT, "Two MPU401 UARTs present.\n")); if (devc->eeprom[0x06] & 0x10) { DDB (cmn_err (CE_CONT, "Consumer AC97 not present.\n")); } else { DDB (cmn_err (CE_CONT, "Consumer AC97 present.\n")); } DDB (cmn_err (CE_CONT, "%d stereo ADC(s) available\n", ((devc->eeprom[0x06] >> 2) & 0x03) + 1)); DDB (cmn_err (CE_CONT, "%d stereo DAC(s) available\n", ((devc->eeprom[0x06] >> 0) & 0x03) + 1)); DDB (cmn_err (CE_CONT, "MT converter type %s\n", (devc->eeprom[0x07] & 0x80) ? "I2S" : "AC97")); if (devc->eeprom[0x08] & 0x80) { DDB (cmn_err (CE_CONT, "Has I2S volume control and mute\n")); } else { DDB (cmn_err (CE_CONT, "No I2S volume control and mute\n")); } if (devc->eeprom[0x08] & 0x20) DDB (cmn_err (CE_CONT, "Has 96kHz support\n")); DDB (cmn_err (CE_CONT, "Converter resolution %d bits\n", resol_tab[(devc->eeprom[0x08] >> 4) & 0x03])); if (devc->eeprom[0x09] & 0x02) DDB (cmn_err (CE_CONT, "Has S/PDIF in support\n")); if (devc->eeprom[0x09] & 0x01) DDB (cmn_err (CE_CONT, "Has S/PDIF out support\n")); #endif if (subvendor == 0xd6301412) /* Delta 1010 */ if (devc->eeprom[0xc] == 0x7b) /* Looks like Delta 1010 rev E */ subvendor = 0xd63014ff; /* Delta 1010E */ #if 1 if (!(devc->eeprom[0x07] & 0x80)) { cmn_err (CE_WARN, "Cards with AC97 codecs are not supported\n"); return 0; } #endif } i = 0; while (models[i].svid != 0) { if (models[i].svid == subvendor) { name = models[i].product; devc->model_data = &models[i]; if (devc->model_data->auxdrv == NULL) devc->model_data->auxdrv = &default_auxdrv; DDB (cmn_err (CE_CONT, "Card id '%s'\n", name)); break; } i++; } if (devc->model_data->flags & MF_AC97) devc->consumer_ac97_present = 1; if (models[i].svid == 0) { cmn_err (CE_NOTE, "Unknown device ID (%08x).\n", subvendor); cmn_err (CE_NOTE, "This card is not supported (yet).\n"); return 0; } MUTEX_INIT (osdev, devc->mutex, MH_DRV); oss_register_device (osdev, name); devc->irq = pci_irq_line; return envy24_init (devc); /* Detected */ } int oss_envy24_detach (oss_device_t * osdev) { envy24_devc *devc; devc = osdev->devc; if (oss_disable_device (osdev) < 0) return 0; /* Disable all interrupts */ OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01); /* Stop playback */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01, devc->mt_base + 0x18); oss_udelay (100); OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01, devc->mt_base + 0x18); /* Stop recording */ OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, devc->mt_base + 0x18); oss_udelay (100); OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, devc->mt_base + 0x18); if (devc->model_data->flags & MF_MIDI1) uart401_disable (&devc->uart401devc1); if (devc->model_data->flags & MF_MIDI2) uart401_disable (&devc->uart401devc2); oss_unregister_interrupts (osdev); MUTEX_CLEANUP (devc->mutex); UNMAP_PCI_IOADDR (devc->osdev, 0); UNMAP_PCI_IOADDR (devc->osdev, 3); if (devc->playbuf != NULL) CONTIG_FREE (devc->osdev, devc->playbuf, HW_ALLOCSIZE, devc->playbuf_dma_handle); if (devc->recbuf != NULL) CONTIG_FREE (devc->osdev, devc->recbuf, HW_ALLOCSIZE, devc->recbuf_dma_handle); devc->playbuf = devc->recbuf = NULL; oss_unregister_device (osdev); return 1; }