diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
commit | 1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch) | |
tree | 4495d23e7b54ab5700e3839081e797c1eafe0db9 /kernel/drv/oss_envy24/oss_envy24.c | |
download | oss4-upstream.tar.gz |
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'kernel/drv/oss_envy24/oss_envy24.c')
-rw-r--r-- | kernel/drv/oss_envy24/oss_envy24.c | 4043 |
1 files changed, 4043 insertions, 0 deletions
diff --git a/kernel/drv/oss_envy24/oss_envy24.c b/kernel/drv/oss_envy24/oss_envy24.c new file mode 100644 index 0000000..b3b82bd --- /dev/null +++ b/kernel/drv/oss_envy24/oss_envy24.c @@ -0,0 +1,4043 @@ +/* + * 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 <ac97.h> +#include <oss_pci.h> + +#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; +} |