Open Sound System |
Do you have problems with sound/audio application development? Don't panic! Click here for help! |
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_envy24ht_cfg.h" #include <oss_pci.h> #define SUPPORTED_FORMAT AFMT_S32_LE #include <spdif.h> #include "envy24ht.h" /* extern envy24ht_auxdrv_t envy24ht_viaref_auxdrv; */ extern envy24ht_auxdrv_t envy24ht_ac97_auxdrv; extern envy24ht_auxdrv_t envy24ht_revo51_auxdrv; extern envy24ht_auxdrv_t envy24ht_revo71_auxdrv; extern envy24ht_auxdrv_t envy24ht_aureon_auxdrv; extern envy24ht_auxdrv_t envy24ht_julia_auxdrv; extern envy24ht_auxdrv_t envy24ht_ap192_auxdrv; #define OUTCH_NAMES "front c/l side rear" static char channel_names[4][10] = { "front", "c/l", "side", "rear" }; static card_spec models[] = { {0x17241412, "Generic Envy24PT motherboard audio", 6, 2, MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, {0xf641270f, "Chaintech ZNF3-150", 6, 2, MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, {0x2723270f, "Chaintech 9CJS", 6, 2, MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, {0x50361297, "Shuttle SN25P", 6, 2, MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, {0x020010b0, "Gainward Hollywood Envy24HT-S", 6, 2, MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, {0x36311412, "M Audio Revolution 5.1", 6, 2, MF_SPDIFOUT | MF_192K, &envy24ht_revo51_auxdrv}, {0x36301412, "M Audio Revolution 7.1", 8, 2, MF_SPDIFOUT | MF_192K, &envy24ht_revo71_auxdrv}, {SSID_AUREON_SPACE, "Terratec Aureon 7.1 Space", 8, 2, MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, {SSID_AUREON_UNIVERSE, "Terratec Aureon 7.1 Universe", 8, 2, MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, {SSID_AUREON_SKY, "Terratec Aureon 7.1 Sky", 8, 2, MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, {SSID_PRODIGY71, "Audiotrak Prodigy 7.1", 8, 2, MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, {SSID_JULIA, "Ego Systems Juli@", 2, 2, MF_SPDIFOUT | MF_SPDIFIN | MF_192K | MF_NOAC97 | MF_MIDI, &envy24ht_julia_auxdrv}, {SSID_PHASE28, "Terratec PHASE 28", 8, 2, MF_SPDIFOUT | MF_192K | MF_NOAC97 | MF_MIDI, &envy24ht_aureon_auxdrv}, {SSID_AP192, "M-Audio Audiophile 192", 2, 2, MF_SPDIFOUT | MF_SPDIFIN | MF_192K | MF_NOAC97 | MF_MIDI, &envy24ht_ap192_auxdrv}, {0x24031412, "VIA Vinyl Tremor Audio", 6, 2, MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, /* XXX Do a separate auxdrv, to adjust for Envy24HT-S and other differences. */ {SSID_PRODIGYHD2, "Audiotrak Prodigy HD2", 2, 2, MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_ap192_auxdrv}, {SSID_PRODIGYHD2_ADE, "Audiotrak Prodigy HD2 Advance DE", 2, 2, MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_ap192_auxdrv}, /* {0x17241412, "VIA Envy24HT reference design", 6, 2, */ /* MF_SPDIFOUT | MF_MIDI, &envy24ht_viaref_auxdrv}, */ {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} , { 88200, 0x0b} , { 96000, 0x07} , { 176400, 0x0c} , { 192000, 0x0e} , {-1, 0x10} , }; static const envy24ht_auxdrv_t dummy_auxdrv = { NULL }; static int ac97_read (void *devc_, int wAddr) { envy24ht_devc *devc = devc_; oss_native_word flags; int n = 0, dat; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x30); OUTB (devc->osdev, wAddr, devc->mt_base + 0x04); OUTB (devc->osdev, 0x10, devc->mt_base + 0x05); /* Codec read */ n = 0; while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x10); dat = INW (devc->osdev, devc->mt_base + 0x06); MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return dat; } static int ac97_write (void *devc_, int wAddr, int wData) { envy24ht_devc *devc = devc_; oss_native_word flags; int n = 0; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x30); OUTB (devc->osdev, wAddr, devc->mt_base + 0x04); OUTW (devc->osdev, wData, devc->mt_base + 0x06); OUTB (devc->osdev, 0x20, devc->mt_base + 0x05); /* Codec write */ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return 0; } static __inline__ int input_avail (envy24ht_devc * devc) { unsigned char status; status = INB (devc->osdev, devc->ccs_base + 0x0b); return status & 0x1f; /* Number of bytes in RX queue */ } static __inline__ int output_ready (envy24ht_devc * devc) { unsigned char status; status = INB (devc->osdev, devc->ccs_base + 0x0a); return (31 - (status & 0x1f)) > 0; /* Number of free bytes in TX queue */ } static __inline__ void midi_cmd (envy24ht_devc * devc, unsigned char cmd) { OUTB (devc->osdev, cmd, devc->ccs_base + 0x0d); } static __inline__ int midi_read (envy24ht_devc * devc) { return INB (devc->osdev, devc->ccs_base + 0x0c); } static __inline__ void midi_write (envy24ht_devc * devc, unsigned char byte) { OUTB (devc->osdev, byte, devc->ccs_base + 0x0c); } static void reset_midi (envy24ht_devc * devc); static void enter_uart_mode (envy24ht_devc * devc); static void midi_input_loop (envy24ht_devc * devc) { int n = 0; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); while (input_avail (devc) && n++ < 33) { unsigned char c = midi_read (devc); devc->input_byte = c; if (devc->midi_opened & OPEN_READ && devc->midi_input_intr) devc->midi_input_intr (devc->midi_dev, c); } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } static void midiintr (envy24ht_devc * devc) { int status; status = INB (devc->osdev, devc->ccs_base + 0x02); if (status & 0x80) midi_input_loop (devc); if ((status & 0x20)) { OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) | 0x20, devc->ccs_base + 0x01); #if 0 if (devc->midi_output_intr) devc->midi_output_intr (devc->midi_dev); #endif } } /*ARGSUSED*/ static int midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte, oss_midi_inputbuf_t inputbuf, oss_midi_outputintr_t outputintr) { envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc; int n = 0, tmp; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (devc->midi_opened & mode) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } devc->midi_opened |= mode; if (mode & OPEN_READ) { while (n++ < 33 && input_avail (devc)) midi_read (devc); devc->midi_input_intr = inputbyte; devc->midi_output_intr = outputintr; } enter_uart_mode (devc); tmp = INB (devc->osdev, devc->ccs_base + 0x01); if (mode & OPEN_READ) tmp &= ~0x80; OUTB (devc->osdev, tmp, devc->ccs_base + 0x01); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } static void midi_close (int dev, int mode) { envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); reset_midi (devc); enter_uart_mode (devc); reset_midi (devc); OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) | 0xa0, devc->ccs_base + 0x01); devc->midi_opened &= ~mode; devc->midi_input_intr = NULL; devc->midi_output_intr = NULL; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } static int midi_out (int dev, unsigned char midi_byte) { envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (!output_ready (devc)) { OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) & ~0x20, devc->ccs_base + 0x01); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } midi_write (devc, midi_byte); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 1; } /*ARGSUSED*/ static int midi_ioctl (int dev, unsigned cmd, ioctl_arg arg) { return OSS_EINVAL; } static midi_driver_t envy24ht_midi_driver = { midi_open, midi_close, midi_ioctl, midi_out }; static void enter_uart_mode (envy24ht_devc * devc) { devc->input_byte = 0; midi_cmd (devc, 1); } void attach_midi (envy24ht_devc * devc) { char name[128]; enter_uart_mode (devc); sprintf (name, "%s input", devc->model_data->product); devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "ENVY24HT", name, &envy24ht_midi_driver, sizeof (midi_driver_t), MFLAG_INPUT, devc, devc->osdev); sprintf (name, "%s output", devc->model_data->product); devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "ENVY24HT", name, &envy24ht_midi_driver, sizeof (midi_driver_t), MFLAG_OUTPUT, devc, devc->osdev); devc->midi_opened = 0; devc->midi_attached = 1; } static void reset_midi (envy24ht_devc * devc) {
Send the RESET command. Try again if no success at the first time.
midi_cmd (devc, 0); } void unload_midi (envy24ht_devc * devc) { if (devc->midi_attached) reset_midi (devc); devc->midi_attached = 0; } static int envy24htintr (oss_device_t * osdev) { envy24ht_devc *devc = osdev->devc; int port, status, mt_status, serviced = 0; status = INB (devc->osdev, devc->ccs_base + 0x02); if (status != 0) serviced = 1; if (status & 0xa0) midiintr (devc);
Handle audio interrupts
mt_status = INB (devc->osdev, devc->mt_base + 0x00); if (mt_status & 0x08) /* FIFO underrun/overrun */ { OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x1A), devc->mt_base + 0x1A); serviced = 1; } if ((status & 0x10) || mt_status != 0) { for (port = 0; port < devc->nr_outdevs; port++) { envy24ht_portc *portc = &devc->play_portc[port]; if (mt_status & portc->mask) { oss_audio_outputintr (portc->dev, 1); } } for (port = 0; port < devc->nr_indevs; port++) { envy24ht_portc *portc = &devc->rec_portc[port]; if (mt_status & portc->mask) { oss_audio_inputintr (portc->dev, 0); } } } OUTB (devc->osdev, mt_status, devc->mt_base + 0x00); OUTB (devc->osdev, status, devc->ccs_base + 0x02); return serviced; } /*ARGSUSED*/ static int envy24ht_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) { return OSS_EINVAL; } static mixer_driver_t envy24ht_mixer_driver = { envy24ht_mixer_ioctl }; static int envy24ht_set_route (int dev, int ctrl, unsigned int cmd, int value) { envy24ht_devc *devc = mixer_devs[dev]->hw_devc; unsigned int tmp; oss_native_word flags; if (ctrl < 0 || ctrl > 8) return OSS_EINVAL; if (cmd == SNDCTL_MIX_READ) { tmp = INL (devc->osdev, devc->mt_base + 0x2c); switch (ctrl) { case 0: tmp >>= 8; break; case 2: tmp >>= 11; break; case 4: tmp >>= 14; break; case 6: tmp >>= 17; break; } tmp = (tmp & 0x03) >> 1; if (tmp == 3) return 2; return tmp; } else if (cmd == SNDCTL_MIX_WRITE) { int left_shift = 0, right_shift = 0; static const unsigned char cnv_tab[3] = { 0x00, 0x02, 0x06 }; if (value < 0 || value > 2) return OSS_EINVAL; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); tmp = INL (devc->osdev, devc->mt_base + 0x2c); switch (ctrl) { case 0: left_shift = 8; right_shift = 20; break; case 2: left_shift = 11; right_shift = 23; break; case 4: left_shift = 14; right_shift = 26; break; case 6: left_shift = 17; right_shift = 29; break; case 8: left_shift = 0; right_shift = 3; break; } tmp &= ~(0x7 << left_shift); tmp &= ~(0x7 << right_shift); tmp |= cnv_tab[value] << left_shift; if (ctrl != 8) tmp |= (cnv_tab[value] + 1) << right_shift; else tmp |= cnv_tab[value] << right_shift; OUTL (devc->osdev, tmp, devc->mt_base + 0x2c); MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return value; } return OSS_EINVAL; } static int read_peak (envy24ht_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 envy24ht_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, }; envy24ht_devc *devc = mixer_devs[dev]->hw_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; } /*ARGSUSED*/ static int create_peak_mixer (int dev, envy24ht_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_SPDIFOUT"); else sprintf (tmp, devc->channel_names[i / 2], i + 1, i + 2); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24ht_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_SPDIFIN"); else strcpy (tmp, "ENVY24_IN"); num |= 1 << (i + 1); if ((err = mixer_ext_create_control (dev, group, num, envy24ht_get_peak, MIXT_STEREOPEAK, tmp, 144, MIXF_READABLE | MIXF_DECIBEL)) < 0) return err; } } return 0; } /*ARGSUSED*/ static int create_rout_mixer (int dev, envy24ht_devc * devc, int root) { int i, mask = devc->outportmask, group, ret, num; char *name; if ((group = mixer_ext_create_group_flags (dev, 0, "ENVY24_ROUTE", MIXF_FLAT)) < 0) return group; for (i = 0; i < 8; i += 2) { num = 1 << i; if (!(mask & num)) continue; /* Not present */ name = devc->channel_names[i / 2]; if ((ret = mixer_ext_create_control (dev, group, i, envy24ht_set_route, MIXT_ENUM, name, 3, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return ret; mixer_ext_set_strings (dev, ret, "DMA ANALOGIN DIGITALIN", 0); } if (devc->model_data == NULL) { cmn_err (CE_CONT, "Internal error: No model data\n"); return 0; } mask = devc->inportmask; if (devc->model_data->flags & MF_SPDIFOUT) { if ((ret = mixer_ext_create_control (dev, group, 8, envy24ht_set_route, MIXT_ENUM, "ENVY24_SPDIFOUT", 3, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return ret; } return 0; }
S/PDIF lowlevel driver
/*ARGSUSED*/ static int default_reprogram_device (void *_devc, void *_portc, oss_digital_control * ctl, unsigned int mask) { unsigned char c; unsigned short cbits = 0; envy24ht_devc *devc = _devc; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); cbits |= (ctl->rate_bits & 0x0f) << 12; /* Sample rate */ if (ctl->out_vbit == VBIT_ON) cbits |= 0x8000; /* Turn on the V bit */ cbits |= ctl->cbitout[0] & 0x07; /* Consumer/pro, audio/data, copyright */ cbits |= (!!(ctl->cbitout[1] & 0x80)) << 11; /* Generation level */ cbits |= (ctl->cbitout[1] & 0x7f) << 4; /* Category code */ cbits |= (ctl->emphasis_type & 1) << 3; /* Pre-emphasis */ if (cbits != INW (devc->osdev, devc->mt_base + 0x3c)) { c = INB (devc->osdev, devc->ccs_base + 0x07); OUTB (devc->osdev, c & ~0x80, devc->ccs_base + 0x07); /* Disable S/PDIF transmitter */ OUTW (devc->osdev, cbits, devc->mt_base + 0x3c); OUTB (devc->osdev, c | 0x80, devc->ccs_base + 0x07); /* (Re)enable S/PDIF transmitter */ } MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); return 0; } spdif_driver_t default_spdif_driver = { /* reprogram_device: */ default_reprogram_device, }; static void setup_sample_rate (envy24ht_devc * devc) { unsigned char bits; int change = 0; devc->speedbits = bits = speed_tab[devc->pending_speed_sel].speedbits; if (devc->speed != speed_tab[devc->pending_speed_sel].speed) change = 1; devc->speed = devc->pending_speed = speed_tab[devc->pending_speed_sel].speed; mixer_devs[devc->mixer_dev]->modify_counter++; if (change) { oss_spdif_setrate (&devc->spdc, devc->speed); if (devc->model_data->svid == SSID_JULIA) goto JULIA; if (devc->syncsource != 0) /* External sync */ bits |= 0x10; if (devc->speed > 120000) { OUTB (devc->osdev, 0x08, devc->mt_base + 0x02); /* 128x I2S setup */ } else { OUTB (devc->osdev, 0x00, devc->mt_base + 0x02); /* 256x I2S setup */ } OUTB (devc->osdev, bits & 0x0f, devc->mt_base + 0x01); /* Sampling rate */ JULIA: if (devc->auxdrv->set_rate) devc->auxdrv->set_rate (devc); } } static int envy24ht_set_control (int dev, int ctrl, unsigned int cmd, int value) { envy24ht_devc *devc = mixer_devs[dev]->hw_devc; oss_native_word flags; 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 5: return devc->ratelock; break; case 6: return devc->speed; break; case 7: return 1; default: return OSS_EIO; } if (cmd == SNDCTL_MIX_WRITE) switch (ctrl) { case 1: if (value < 0 || value > devc->max_ratesel) return OSS_EIO; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (devc->configured_rate_sel != value) { devc->configured_rate_sel = value; if (devc->open_count < 1) { devc->pending_speed_sel = devc->configured_rate_sel; setup_sample_rate (devc); } } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->configured_rate_sel; break; case 2: if (value < 0 || value > 2) return OSS_EIO; devc->syncsource = value; if (devc->model_data->svid == SSID_JULIA) devc->auxdrv->private1 (devc, value); return devc->syncsource; break; case 3: devc->use_src = !!value; return devc->use_src; break; case 5: return devc->ratelock = !!value; break; case 6: return devc->speed; break; case 7: return 1; break; default: return OSS_EIO; } return OSS_EINVAL; } static int envy24ht_mix_init (int dev) { envy24ht_devc *devc = mixer_devs[dev]->hw_devc; int group, err, n; if ((group = mixer_ext_create_group_flags (dev, 0, "ENVY24", MIXF_FLAT)) < 0) return group; if ((err = create_peak_mixer (dev, devc, group)) < 0) return err; if ((err = create_rout_mixer (dev, devc, group)) < 0) return err; n = devc->max_ratesel + 1; if ((err = mixer_ext_create_control (dev, group, 1, envy24ht_set_control, MIXT_ENUM, "ENVY24_RATE", n, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; mixer_ext_set_strings (dev, err, "8000 9600 11025 12000 16000 22050 24000 32000 44100 48000 64000 88200 96000 176400 192000", 0); if ((err = mixer_ext_create_control (dev, group, 2, envy24ht_set_control, MIXT_ENUM, "ENVY24_SYNC", 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; if ((err = mixer_ext_create_control (dev, group, 3, envy24ht_set_control, MIXT_ONOFF, "ENVY24_SRC", 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; if ((err = mixer_ext_create_control (dev, group, 5, envy24ht_set_control, MIXT_ONOFF, "ENVY24_RATELOCK", 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return err; if ((err = mixer_ext_create_control (dev, group, 6, envy24ht_set_control, MIXT_VALUE, "ENVY24_ACTRATE", 192000, MIXF_READABLE)) < 0) return err; if (devc->auxdrv->mixer_init) if ((err = devc->auxdrv->mixer_init (devc, dev, 0)) < 0) return err; if ((err = oss_spdif_mix_init (&devc->spdc)) < 0) return err; return 0; } static int eeprom_read (envy24ht_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, pos, devc->ccs_base + 0x11); /* Offset */ OUTB (devc->osdev, 0xa0, devc->ccs_base + 0x10); /* EEPROM read */ for (i = 0; i < 0x10000; 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 void envy24pt_init (envy24ht_devc * devc) { int gpio; gpio = INW (devc->osdev, devc->ccs_base + 0x14); gpio |= INB (devc->osdev, devc->ccs_base + 0x1e) << 16; #if 0 #define GPBIT(nn) !!(1<<nn) cmn_err (CE_CONT, "GPIO=%06x\n", gpio); cmn_err (CE_CONT, "With SPDIF_IN 'optical' connector: %d\n", GPBIT (1)); cmn_err (CE_CONT, "With SPDIF_IN 'coaxial' connector: %d\n", GPBIT (2)); cmn_err (CE_CONT, "AC97 with stereo DAC for 7.1: %d\n", !GPBIT (8)); cmn_err (CE_CONT, "Extra ADC/DAC connected to S/PDIF pins: %d\n", GPBIT (9)); cmn_err (CE_CONT, "S/PDIF connected to RDMA1: %d\n", !GPBIT (10)); cmn_err (CE_CONT, "Smart 5.1 function supported: %d\n", GPBIT (11)); cmn_err (CE_CONT, "De-POP function supported: %d\n", !GPBIT (12)); cmn_err (CE_CONT, "PDMA4 DAC: 0=cs4321 1=WM8728: %d\n", GPBIT (13)); #endif OUTB (devc->osdev, 0x00, devc->ccs_base + 0x04); /* System configuration */ OUTB (devc->osdev, 0x02, devc->ccs_base + 0x05); /* AC-link configuration */ OUTB (devc->osdev, 0x00, devc->ccs_base + 0x06); /* I2S configuration */ OUTB (devc->osdev, 0x83, devc->ccs_base + 0x07); /* S/PDIF configuration */ /* TODO: GPIO initialization */ } static void julia_eeprom_init (envy24ht_devc * devc) { OUTB (devc->osdev, 0x39, devc->ccs_base + 0x04); /* System configuration */ OUTB (devc->osdev, 0x80, devc->ccs_base + 0x05); /* AC-link configuration */ OUTB (devc->osdev, 0x78, devc->ccs_base + 0x06); /* I2S configuration */ OUTB (devc->osdev, 0xc3, devc->ccs_base + 0x07); /* S/PDIF configuration */ OUTW (devc->osdev, 0xffff, devc->ccs_base + 0x18); /* GPIO direction */ OUTW (devc->osdev, 0x0000, devc->ccs_base + 0x16); /* GPIO write mask */ OUTW (devc->osdev, 0x801A, devc->ccs_base + 0x14); /* Initital bit state */ } static int init_eeprom_v2 (envy24ht_devc * devc) { unsigned char *eeprom = (unsigned char *) &devc->eeprom; OUTB (devc->osdev, eeprom[6], devc->ccs_base + 0x04); OUTB (devc->osdev, eeprom[7], devc->ccs_base + 0x05); OUTB (devc->osdev, eeprom[8], devc->ccs_base + 0x06); OUTB (devc->osdev, eeprom[9], devc->ccs_base + 0x07); OUTB (devc->osdev, eeprom[10], devc->ccs_base + 0x18); OUTB (devc->osdev, eeprom[11], devc->ccs_base + 0x19); OUTB (devc->osdev, eeprom[12], devc->ccs_base + 0x1a); OUTB (devc->osdev, eeprom[13], devc->ccs_base + 0x16); OUTB (devc->osdev, eeprom[14], devc->ccs_base + 0x17); OUTB (devc->osdev, eeprom[15], devc->ccs_base + 0x1f); OUTB (devc->osdev, eeprom[16], devc->ccs_base + 0x14); OUTB (devc->osdev, eeprom[17], devc->ccs_base + 0x15); OUTB (devc->osdev, eeprom[18], devc->ccs_base + 0x1e); return 1; } static int load_eeprom (envy24ht_devc * devc) { int status, i, check; unsigned char *eeprom = (unsigned char *) &devc->eeprom, c; static const char resolutions[4] = { 16, 18, 20, 24 }; c = 0; status = INB (devc->osdev, devc->ccs_base + 0x13); if (!(status & 0x80)) return 0; /* No EEPROM */ devc->eeprom.bSize = sizeof (devc->eeprom); /* Guess the size */ for (i = 0; i < devc->eeprom.bSize; i++) { eeprom[i] = eeprom_read (devc, i); eeprom[i] = eeprom_read (devc, i); if (devc->eeprom.bSize > sizeof (devc->eeprom)) devc->eeprom.bSize = sizeof (devc->eeprom); } #if 1 DDB (cmn_err (CE_CONT, "EEPROM=")); for (i = 0; i < devc->eeprom.bSize; i++) DDB (cmn_err (CE_CONT, "0x%02x, ", eeprom[i])); DDB (cmn_err (CE_CONT, "\n")); #endif check = 0; for (i = 0; i < 4; i++) { check <<= 8; check |= eeprom[i]; } if (check != devc->model_data->svid) cmn_err (CE_CONT, "Envy24 WARNING: Possible EEPROM read error %08x != %08x\n", check, devc->model_data->svid); DDB (cmn_err (CE_CONT, "EEPROM version %d\n", devc->eeprom.bVersion)); if (devc->eeprom.bVersion == 2) { return init_eeprom_v2 (devc); } /* Init the controller registers based on the EEPROM data */ OUTB (devc->osdev, devc->eeprom.bCodecConfig, devc->ccs_base + 0x04); OUTB (devc->osdev, devc->eeprom.bACLinkConfig, devc->ccs_base + 0x05); OUTB (devc->osdev, devc->eeprom.bI2SID, devc->ccs_base + 0x06); OUTB (devc->osdev, devc->eeprom.bSpdifConfig, devc->ccs_base + 0x07); /* GPIO ports */ OUTB (devc->osdev, devc->eeprom.bGPIODirection2, devc->ccs_base + 0x18); OUTB (devc->osdev, devc->eeprom.bGPIODirection1, devc->ccs_base + 0x19); OUTB (devc->osdev, devc->eeprom.bGPIODirection0, devc->ccs_base + 0x1a); OUTB (devc->osdev, devc->eeprom.bGPIOInitMask2, devc->ccs_base + 0x14); OUTB (devc->osdev, devc->eeprom.bGPIOInitMask1, devc->ccs_base + 0x15); OUTB (devc->osdev, devc->eeprom.bGPIOInitMask0, devc->ccs_base + 0x1f); OUTB (devc->osdev, devc->eeprom.bGPIOInitState2, devc->ccs_base + 0x14); OUTB (devc->osdev, devc->eeprom.bGPIOInitState1, devc->ccs_base + 0x15); OUTB (devc->osdev, devc->eeprom.bGPIOInitState0, devc->ccs_base + 0x1e); #if 1 DDB (cmn_err (CE_CONT, "GPIO=%02x%02x%02x (%02x%02x%02x, %02x%02x%02x)\n", devc->eeprom.bGPIOInitState2, devc->eeprom.bGPIOInitState1, devc->eeprom.bGPIOInitState0, devc->eeprom.bGPIODirection2, devc->eeprom.bGPIODirection1, devc->eeprom.bGPIODirection0, devc->eeprom.bGPIOInitMask2, devc->eeprom.bGPIOInitMask1, devc->eeprom.bGPIOInitMask0)); c = devc->eeprom.bCodecConfig; switch ((c >> 6) % 0x03) { case 0: DDB (cmn_err (CE_CONT, "24.576MHz crystal\n")); break; case 1: DDB (cmn_err (CE_CONT, "49.152MHz crystal\n")); break; default: DDB (cmn_err (CE_CONT, "Unknown crystal frequency\n")); } if (c & 0x20) { DDB (cmn_err (CE_CONT, "Has MPU401 UART\n")); } else { DDB (cmn_err (CE_CONT, "No MPU401 UART\n")); } DDB (cmn_err (CE_CONT, "%d stereo ADC pairs connected\n", ((c >> 2) & 0x03) + 1)); DDB (cmn_err (CE_CONT, "%d stereo DAC pairs connected\n", (c & 0x03) + 1)); c = devc->eeprom.bACLinkConfig; DDB (cmn_err (CE_CONT, "Converter type: %s\n", (c & 0x80) ? "I2S" : "AC97")); if (!(c & 0x80)) { if (!(devc->model_data->flags & MF_NOAC97)) devc->codec_type = CODEC_AC97; DDB (cmn_err (CE_NOTE, "AC link connection mode type: %s\n", (c & 0x02) ? "packed" : "split")); } else { c = devc->eeprom.bI2SID; DDB (cmn_err (CE_CONT, "I2C codec has volume control/mute: %s\n", (c % 0x80) ? "YES" : "NO")); DDB (cmn_err (CE_CONT, "I2C codec has 96 KHz S/R support: %s\n", (c % 0x40) ? "YES" : "NO")); DDB (cmn_err (CE_CONT, "I2C codec has 192 KHz S/R support: %s\n", (c % 0x08) ? "YES" : "NO")); DDB (cmn_err (CE_CONT, "Converter resolution %d bits\n", resolutions[(c >> 4) & 0x03])); } c = INB (devc->osdev, devc->ccs_base + 0x07); DDB (cmn_err (CE_CONT, "Internal S/PDIF out implemented: %s\n", (c & 0x40) ? "YES" : "NO")); DDB (cmn_err (CE_CONT, "Internal S/PDIF out enabled: %s\n", (c & 0x80) ? "YES" : "NO")); DDB (cmn_err (CE_CONT, "External S/PDIF out implemented: %s\n", (c & 0x01) ? "YES" : "NO")); DDB (cmn_err (CE_CONT, "S/PDIF input present: %s\n", (c & 0x02) ? "YES" : "NO")); DDB (cmn_err (CE_CONT, "S/PDIF chip IDs %x\n", (c >> 2) & 0x0f)); #endif return 1; } /*ARGSUSED*/ static void dump_regs (envy24ht_devc * devc, char *lbl) { #if 0 int i; cmn_err (CE_CONT, "\nDump registers: %s\n", lbl); for (i = 0; i < 0x20; i += 4) { if (!(i % (8 * 4))) cmn_err (CE_CONT, "\nCCS%02x: ", i); cmn_err (CE_CONT, "%08x ", INL (devc->osdev, devc->ccs_base + i)); } cmn_err (CE_CONT, "\n"); for (i = 0; i < 0x80; i += 4) { if (!(i % (8 * 4))) cmn_err (CE_CONT, "\nMT%02x: ", i); cmn_err (CE_CONT, "%08x ", INL (devc->osdev, devc->mt_base + i)); } cmn_err (CE_CONT, "\n"); #endif } static int verify_rate (envy24ht_devc * devc, int arg) { if (devc->codec_type == CODEC_AC97 && arg > 48000) arg = 48000; if (arg > 96000 && !(devc->model_data->flags & MF_192K)) arg = 96000; return arg; } static int envy24ht_set_rate (int dev, int arg) { envy24ht_devc *devc = audio_engines[dev]->devc; envy24ht_portc *portc = audio_engines[dev]->portc; int i, ix, diff, bestdiff; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (arg == 0) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->pending_speed; } arg = verify_rate (devc, arg);
Don't permit changing the sampling rate if we have multiple clients.
if (devc->open_count != 1 || devc->ratelock) { DDB (cmn_err (CE_CONT, "Can't set speed: open_count %d, ratelock %d\n", devc->open_count, devc->ratelock)); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); if (arg != devc->pending_speed) { audio_engines[dev]->fixed_rate = devc->speed; audio_engines[dev]->min_rate = devc->speed; audio_engines[dev]->max_rate = devc->speed; audio_engines[dev]->flags |= ADEV_FIXEDRATE; } else { audio_engines[dev]->min_rate = 8000; audio_engines[dev]->max_rate = 192000; audio_engines[dev]->flags &= ~ADEV_FIXEDRATE; } return devc->pending_speed; } if (portc->dev_flags & DF_SPDIF) { /* Allow only supported S/PDIF rates */ if (arg < 32000) arg = 32000; if (arg > 96000) arg = 96000; } ix = 9; bestdiff = 1000000; i = 0; audio_engines[dev]->flags &= ~ADEV_FIXEDRATE; while (speed_tab[i].speed != -1) { diff = speed_tab[i].speed - arg; if (diff < 0) diff = -diff; if (diff < bestdiff) { ix = i; bestdiff = diff; } i++; } devc->pending_speed = speed_tab[ix].speed; devc->pending_speed_sel = ix; /*cmn_err(CE_CONT, "Requested sampling rate %d, got %d\n", arg, devc->pending_speed); */ //setup_sample_rate (devc); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return devc->pending_speed; } static short envy24ht_set_channels (int dev, short arg) { envy24ht_portc *portc = audio_engines[dev]->portc; envy24ht_devc *devc = audio_engines[dev]->devc; oss_native_word flags; if (arg == 0) return portc->channels; if (portc->dev_flags & DF_MULTICH) { int n = 2, ch, i, mask; if (arg < 2) arg = 2; arg = ((arg + 1) / 2) * 2; /* Round to even number of channels */ if (arg > devc->model_data->nr_outs) arg = devc->model_data->nr_outs; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); devc->busy_play_channels &= ~portc->used_chmask; for (ch = 2; ch <= arg; ch += 2) { mask = 0; for (i = 0; i < ch; i++) mask |= (1 << i); if (devc->busy_play_channels & mask) break; n = ch; } portc->channels = n; portc->used_chmask = 0; for (i = 0; i < n; i++) portc->used_chmask |= (1 << i); devc->busy_play_channels |= portc->used_chmask; /* MT19: Channel allocation */ OUTB (devc->osdev, 4 - n / 2, devc->mt_base + 0x19); /* cmn_err(CE_CONT, "%d channels: MT19=%02x\n", n, INB(devc->osdev, devc->mt_base+0x19)); */ if (portc->channels == 6) { /* The fragment size must be a multiple of 6 */ audio_engines[dev]->min_block = 4 * 288; audio_engines[dev]->max_block = 4 * 288; } else { audio_engines[dev]->min_block = 0; audio_engines[dev]->max_block = 0; } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return portc->channels; } return portc->channels = 2; } /*ARGSUSED*/ static int ac3_write (adev_t * adev, dmap_t * dmap, void *frombuf, void *tobuf, int maxspace, int *fromlen, int *tolen) {
This routine takes AC3 input 16 bits at time and packs them to 32 bit words.
int i, l; unsigned short *ip; unsigned int *op; l = *fromlen * 2; if (l > maxspace) { l = maxspace; } *tolen = l; *fromlen = l / 2; l /= 4; ip = frombuf; op = tobuf; for (i = 0; i < l; i++) { *op++ = (*ip++) << 16; } return 0; } static unsigned int envy24ht_set_format (int dev, unsigned int arg) { envy24ht_portc *portc = audio_engines[dev]->portc; if (arg == 0) return portc->fmt; if (arg == AFMT_AC3 && (portc->dev_flags & DF_AC3)) { audio_engines[dev]->dmap_out->device_write = ac3_write; return portc->fmt = AFMT_AC3; } audio_engines[dev]->dmap_out->device_write = NULL; return portc->fmt = SUPPORTED_FORMAT; } static int envy24ht_ioctl (int dev, unsigned int cmd, ioctl_arg arg) { envy24ht_portc *portc = audio_engines[dev]->portc; envy24ht_devc *devc = audio_engines[dev]->devc; switch (cmd) { case SNDCTL_DSP_GET_CHNORDER: *(oss_uint64_t *) arg = CHNORDER_NORMAL; return 0; } if (portc->dev_flags & DF_SPDIF) { int ret; ret = oss_spdif_ioctl (&devc->spdc, portc->open_mode, cmd, arg); if (ret != SPDIF_NOIOCTL) return ret; } if (devc->auxdrv->audio_ioctl) return devc->auxdrv->audio_ioctl (devc, portc, cmd, arg); return OSS_EINVAL; } static void envy24ht_trigger (int dev, int state); static void envy24ht_reset (int dev) { envy24ht_trigger (dev, 0); } /*ARGSUSED*/ static int envy24ht_open_input (int dev, int mode, int open_flags) { envy24ht_portc *portc = audio_engines[dev]->portc; envy24ht_devc *devc = audio_engines[dev]->devc; adev_p adev = audio_engines[dev]; oss_native_word flags; if (mode & OPEN_WRITE) { cmn_err (CE_CONT, "Playback is not possible with %s\n", adev->devnode); return OSS_ENOTSUP; } MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode || (devc->busy_rec_channels & portc->chmask)) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } portc->open_mode = mode; devc->open_count++; if (devc->open_count == 1) { devc->pending_speed_sel = devc->configured_rate_sel; } if (portc->dev_flags & DF_SPDIF) oss_spdif_open (&devc->spdc, mode); portc->used_chmask = portc->chmask; devc->busy_rec_channels |= portc->chmask; if (!devc->use_src) adev->flags |= ADEV_NOSRC; else adev->flags &= ~ADEV_NOSRC; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static int envy24ht_open_output (int dev, int mode, int open_flags) { envy24ht_portc *portc = audio_engines[dev]->portc; envy24ht_devc *devc = audio_engines[dev]->devc; oss_native_word flags; adev_p adev = audio_engines[dev]; if (mode & OPEN_READ) { cmn_err (CE_CONT, "Recording is not possible with %s\n", adev->devnode); return OSS_ENOTSUP; } MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode || (devc->busy_play_channels & portc->chmask)) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } portc->open_mode = mode; portc->used_chmask = portc->chmask; devc->busy_play_channels |= portc->chmask; audio_engines[dev]->dmap_out->device_write = NULL; devc->open_count++; if (devc->open_count == 1) { devc->pending_speed_sel = devc->configured_rate_sel; } if (portc->dev_flags & DF_SPDIF) oss_spdif_open (&devc->spdc, mode); if (!devc->use_src) adev->flags |= ADEV_NOSRC; else adev->flags &= ~ADEV_NOSRC; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } static void envy24ht_close (int dev, int mode) { envy24ht_portc *portc = audio_engines[dev]->portc; envy24ht_devc *devc = audio_engines[dev]->devc; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); devc->open_count--; if (portc->open_mode & OPEN_READ) devc->busy_rec_channels &= ~portc->used_chmask; if (portc->open_mode & OPEN_WRITE) devc->busy_play_channels &= ~portc->used_chmask; portc->open_mode = 0; if (portc->dev_flags & DF_MULTICH) { OUTB (devc->osdev, 0x03, devc->mt_base + 0x19); /* Channel allocation */ portc->chmask = 0x003; /* Just the front channels */ } if (portc->dev_flags & DF_SPDIF) oss_spdif_close (&devc->spdc, mode); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } /*ARGSUSED*/ static void envy24ht_output_block (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { } /*ARGSUSED*/ static void envy24ht_start_input (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { } static void envy24ht_trigger (int dev, int state) { envy24ht_devc *devc = audio_engines[dev]->devc; envy24ht_portc *portc = audio_engines[dev]->portc; unsigned char enable, intrmask; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); enable = INB (devc->osdev, devc->mt_base + 0x18); intrmask = INB (devc->osdev, devc->mt_base + 0x03); if (portc->state_bits == state) /* No change */ { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return; } portc->state_bits = state; if (portc->open_mode & OPEN_WRITE) { if (state & PCM_ENABLE_OUTPUT) { enable |= portc->mask; intrmask &= ~portc->mask; } else { enable &= ~portc->mask; intrmask |= portc->mask; } } if (portc->open_mode & OPEN_READ) { if (state & PCM_ENABLE_INPUT) { enable |= portc->mask; intrmask &= ~portc->mask; } else { enable &= ~portc->mask; intrmask |= portc->mask; } } OUTB (devc->osdev, enable, devc->mt_base + 0x18); OUTB (devc->osdev, intrmask, devc->mt_base + 0x03); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); if (state) dump_regs (devc, "trigger"); } /*ARGSUSED*/ static int envy24ht_prepare_for_input (int dev, int bsize, int bcount) { envy24ht_devc *devc = audio_engines[dev]->devc; envy24ht_portc *portc = audio_engines[dev]->portc; dmap_p dmap = audio_engines[dev]->dmap_in; int buffsize, fragsize; oss_native_word flags; if (audio_engines[dev]->flags & ADEV_NOINPUT) return OSS_EACCES; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); setup_sample_rate (devc); buffsize = dmap->bytes_in_use / 4 - 1; fragsize = dmap->fragment_size / 4 - 1; OUTL (devc->osdev, dmap->dmabuf_phys, portc->base + 0x00); OUTW (devc->osdev, buffsize, portc->base + 0x04); OUTW (devc->osdev, fragsize, portc->base + 0x06); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static int envy24ht_prepare_for_output (int dev, int bsize, int bcount) { envy24ht_devc *devc = audio_engines[dev]->devc; envy24ht_portc *portc = audio_engines[dev]->portc; dmap_p dmap = audio_engines[dev]->dmap_out; int buffsize, fragsize; oss_native_word flags; if (audio_engines[dev]->flags & ADEV_NOOUTPUT) return OSS_EACCES; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); setup_sample_rate (devc); buffsize = dmap->bytes_in_use / 4 - 1; fragsize = dmap->fragment_size / 4 - 1; if (portc->dev_flags & DF_MULTICH) { /* Multi ch device */ OUTL (devc->osdev, dmap->dmabuf_phys, devc->mt_base + 0x10); OUTL (devc->osdev, buffsize, devc->mt_base + 0x14); OUTL (devc->osdev, fragsize, devc->mt_base + 0x1c); } else { OUTL (devc->osdev, dmap->dmabuf_phys, portc->base + 0x00); OUTW (devc->osdev, buffsize, portc->base + 0x04); OUTW (devc->osdev, fragsize, portc->base + 0x06); } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static int envy24ht_get_buffer_pointer (int dev, dmap_t * dmap, int direction) { envy24ht_portc *portc = audio_engines[dev]->portc; envy24ht_devc *devc; int pos; devc = audio_engines[dev]->devc; pos = (INW (devc->osdev, portc->base + 0x04) + 1) * 4; return dmap->bytes_in_use - pos; } static int envy24ht_sync_control(int dev, int event, int mode) { envy24ht_devc *devc = audio_engines[dev]->devc; envy24ht_portc *portc = audio_engines[dev]->portc; unsigned char enable, intrmask; oss_native_word flags; MUTEX_ENTER_IRQDISABLE(devc->mutex, flags); if(event == SYNC_PREPARE) { devc->syncstart_mask |= portc->mask; portc->state_bits = mode; } else if(event == SYNC_TRIGGER) { if(devc->syncstart_mask) { enable = INB (devc->osdev, devc->mt_base + 0x18); intrmask = INB (devc->osdev, devc->mt_base + 0x03); enable |= devc->syncstart_mask; intrmask &= ~devc->syncstart_mask; OUTB (devc->osdev, enable, devc->mt_base + 0x18); OUTB (devc->osdev, intrmask, devc->mt_base + 0x03); devc->syncstart_mask = 0; } } MUTEX_EXIT_IRQRESTORE(devc->mutex, flags); return 0; } #if 0 static int envy24ht_check_output (int dev) { int pos; envy24ht_devc *devc = audio_engines[dev]->devc; pos = envy24ht_get_buffer_pointer (dev, audio_engines[dev]->dmap_out, 0); cmn_err (CE_CONT, "Envy24ht: Output timeout on device %d (%d, %02x, %02x)\n", dev, pos, INB (devc->osdev, devc->ccs_base + 0x02), INB (devc->osdev, devc-> ccs_base + 0x00)); return OSS_EIO; } #endif static audiodrv_t envy24ht_output_driver = { envy24ht_open_output, envy24ht_close, envy24ht_output_block, envy24ht_start_input, envy24ht_ioctl, envy24ht_prepare_for_input, envy24ht_prepare_for_output, envy24ht_reset, NULL, NULL, NULL, NULL, envy24ht_trigger, envy24ht_set_rate, envy24ht_set_format, envy24ht_set_channels, NULL, NULL, NULL, /* check input */ NULL, /* envy24ht_check_output */ NULL, /* envy24ht_alloc_buffer */ NULL, /* envy24ht_free_buffer */ NULL, NULL, envy24ht_get_buffer_pointer, NULL, envy24ht_sync_control }; static audiodrv_t envy24ht_input_driver = { envy24ht_open_input, envy24ht_close, envy24ht_output_block, envy24ht_start_input, envy24ht_ioctl, envy24ht_prepare_for_input, envy24ht_prepare_for_output, envy24ht_reset, NULL, NULL, NULL, NULL, envy24ht_trigger, envy24ht_set_rate, envy24ht_set_format, envy24ht_set_channels, NULL, NULL, NULL, NULL, NULL, /* envy24ht_alloc_buffer */ NULL, /* envy24ht_free_buffer */ NULL, NULL, envy24ht_get_buffer_pointer, NULL, envy24ht_sync_control }; static const int bindings[MAX_ODEV] = { DSP_BIND_FRONT, DSP_BIND_CENTER_LFE, DSP_BIND_SURR, DSP_BIND_REAR }; static int init_play_device (envy24ht_devc * devc, int chmask, int offset, unsigned char mask, char *name, int dev_flags, char *port_id, char *devfile_name) { int opts, dev, formats; char tmp[80]; envy24ht_portc *portc = NULL; int i; adev_p adev; sprintf (tmp, "%s %s out", devc->model_data->product, name); if (devc->nr_outdevs >= MAX_ODEV) { cmn_err (CE_CONT, "Envy24ht: Too many audio devices\n"); return OSS_ENXIO; } opts = ADEV_AUTOMODE | ADEV_NOINPUT; if (dev_flags & DF_SPDIF) opts |= ADEV_SPECIAL; formats = SUPPORTED_FORMAT; if (dev_flags & DF_AC3) formats |= AFMT_AC3; if (dev_flags & DF_MULTICH) opts |= ADEV_COLD; else opts |= ADEV_SPECIAL; if ((dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, tmp, &envy24ht_output_driver, sizeof (audiodrv_t), opts, formats, devc, -1, devfile_name)) < 0) { return dev; } if (devc->first_dev == -1) { devc->first_dev = dev; audio_engines[dev]->outch_names = OUTCH_NAMES; } adev = audio_engines[dev]; portc = &devc->play_portc[devc->nr_outdevs]; for (i = 0; speed_tab[i].speed != -1; i++) adev->rates[adev->nrates++] = speed_tab[i].speed; portc->name = port_id; audio_engines[dev]->portc = portc; audio_engines[dev]->mixer_dev = devc->mixer_dev; audio_engines[dev]->rate_source = devc->first_dev; audio_engines[dev]->min_rate = 8000; audio_engines[dev]->max_rate = 192000; audio_engines[dev]->binding = bindings[devc->nr_outdevs]; if (dev_flags & DF_SPDIF) audio_engines[dev]->binding = DSP_BIND_SPDIF; portc->dev = dev; portc->open_mode = 0; portc->fmt = SUPPORTED_FORMAT; portc->base = devc->mt_base + offset; portc->mask = mask; portc->dev_flags = dev_flags; portc->chmask = chmask; portc->state_bits = 0; portc->direction = PCM_ENABLE_OUTPUT; audio_engines[dev]->min_channels = 2; audio_engines[dev]->max_channels = 2; if (dev_flags & DF_SPDIF) audio_engines[dev]->caps |= PCM_CAP_DIGITALOUT | DSP_CH_STEREO; else { if (dev_flags & DF_MULTICH) { audio_engines[dev]->caps |= PCM_CAP_ANALOGOUT; audio_engines[dev]->caps |= DSP_CH_STEREO; audio_engines[dev]->min_channels = 2; audio_engines[dev]->max_channels = devc->model_data->nr_outs; } else audio_engines[dev]->caps |= PCM_CAP_ANALOGOUT | DSP_CH_STEREO; } devc->nr_outdevs++; return dev; } static int init_rec_device (envy24ht_devc * devc, int chmask, int offset, unsigned char mask, char *name, int dev_flags, char *devfile_name) { int opts, dev, formats; adev_p adev; int i; envy24ht_portc *portc = NULL; char tmp[80]; sprintf (tmp, "%s %s in", devc->model_data->product, name); if (devc->nr_indevs >= MAX_IDEV) { cmn_err (CE_CONT, "Envy24ht: Too many audio devices\n"); return OSS_ENXIO; } opts = ADEV_AUTOMODE | ADEV_NOOUTPUT | ADEV_COLD; if (dev_flags & DF_SPDIF) opts |= ADEV_SPECIAL; formats = SUPPORTED_FORMAT; if (dev_flags & DF_AC3) formats |= AFMT_AC3; if ((dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, tmp, &envy24ht_input_driver, sizeof (audiodrv_t), opts, formats, devc, -1, devfile_name)) < 0) { return dev; } if (devc->first_dev == -1) devc->first_dev = dev; portc = &devc->rec_portc[devc->nr_indevs]; adev = audio_engines[dev]; for (i = 0; speed_tab[i].speed != -1; i++) adev->rates[adev->nrates++] = speed_tab[i].speed; audio_engines[dev]->portc = portc; audio_engines[dev]->mixer_dev = devc->mixer_dev; audio_engines[dev]->rate_source = devc->first_dev; audio_engines[dev]->min_rate = 8000; audio_engines[dev]->max_rate = 192000; portc->dev = dev; portc->name = "rec"; portc->open_mode = 0; portc->base = devc->mt_base + offset; portc->mask = mask; portc->state_bits = 0; portc->fmt = SUPPORTED_FORMAT; portc->dev_flags = dev_flags; portc->chmask = chmask; portc->direction = PCM_ENABLE_INPUT; if (dev_flags & DF_SPDIF) audio_engines[dev]->caps |= PCM_CAP_DIGITALIN | DSP_CH_STEREO; else audio_engines[dev]->caps |= PCM_CAP_ANALOGIN | DSP_CH_STEREO; devc->nr_indevs++; return dev; } static void init_devices (envy24ht_devc * devc) { int front_engine, rec_engine; OUTB (devc->osdev, 0x03, devc->mt_base + 0x19); /* Channel allocation */ OUTB (devc->osdev, 0x00, devc->mt_base + 0x1b); /* Unpause ALL channels */ devc->first_dev = -1; front_engine=init_play_device (devc, 0x003, 0x10, 0x01, devc->channel_names[0], DF_MULTICH, "front", ""); if (devc->model_data->nr_outs > 2) init_play_device (devc, 0x00c, 0x70, 0x10, devc->channel_names[1], 0, "C/LFE", ""); if (devc->model_data->nr_outs > 4) init_play_device (devc, 0x030, 0x60, 0x20, devc->channel_names[2], 0, "side", ""); if (devc->model_data->nr_outs > 6) init_play_device (devc, 0x0c0, 0x50, 0x40, devc->channel_names[3], 0, "rear", ""); if (devc->model_data->flags & MF_SPDIFOUT) { init_play_device (devc, 0x300, 0x40, 0x80, "digital", DF_SPDIF | DF_AC3, "spdif", "spdout"); } rec_engine = init_rec_device (devc, 0x003, 0x20, 0x02, "analog", 0, ""); if (devc->model_data->flags & MF_SPDIFIN) { init_rec_device (devc, 0x00c, 0x30, 0x04, "digital", DF_SPDIF, "spdin"); } #ifdef CONFIG_OSS_VMIX if (rec_engine < 0) rec_engine = -1; /* Not available */ if (front_engine >= 0) vmix_attach_audiodev(devc->osdev, front_engine, rec_engine, 0); #endif } static void install_ac97_mixer (envy24ht_devc * devc) { int tmp; tmp = 0; DDB (cmn_err (CE_CONT, "Installing AC97 mixer\n")); devc->mixer_dev = ac97_install (&devc->ac97devc, devc->model_data->product, ac97_read, ac97_write, devc, devc->osdev); if (devc->mixer_dev < 0) { cmn_err (CE_CONT, "Envy24ht: Mixer install failed\n"); return; } ac97_init_ext (devc->mixer_dev, &devc->ac97devc, envy24ht_mix_init, 50); #if 1 /* AD1616 specific stuff. Check this if there is some other AC97 chip */ /* Maybe this should be moved to ac97.c in a way or another */ /* Turn surround dacs ON */ tmp = ac97_read (devc, 0x2a); tmp &= ~0x3800; ac97_write (devc, 0x2a, tmp); tmp = ac97_read (devc, 0x5a); tmp &= ~0x8000; tmp |= 0x1800; ac97_write (devc, 0x5a, tmp); #endif #if 0 for (tmp = 0; tmp < 0x3f; tmp += 2) cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp)); for (tmp = 0x5a; tmp < 0x5d; tmp += 2) cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp)); for (tmp = 0x7a; tmp < 0x7f; tmp += 2) cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp)); #endif } int oss_envy24ht_attach (oss_device_t * osdev) { envy24ht_devc *devc; extern int envy24ht_model; unsigned char pci_irq_line; unsigned short pci_command, vendor, device; unsigned int subvendor; unsigned int pci_ioaddr, pci_ioaddr1; int i, err; char *name = "Generic ENVY24HT"; DDB (cmn_err (CE_CONT, "Entered Envy24HT probe routine\n")); 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_ENVY24HT_ID) return 0; if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) { cmn_err (CE_WARN, "Out of memory\n"); return 0; } devc->osdev = osdev; osdev->devc = devc; MUTEX_INIT (osdev, devc->mutex, MH_DRV); MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1); pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr); pci_read_config_dword (osdev, PCI_BASE_ADDRESS_1, &pci_ioaddr1); 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); DDB (cmn_err (CE_CONT, "Device found at I/O %x, %x\n", pci_ioaddr & ~3, pci_ioaddr1 & ~3)); devc->subvendor = 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, 1, pci_ioaddr1) & ~0x3; DDB (cmn_err (CE_CONT, "MT base %x/%lx\n", pci_ioaddr1, devc->mt_base)); /* Reset the chip */ OUTB (devc->osdev, 0x81, devc->ccs_base + 0x00); oss_udelay (1000); /* Release reset */ OUTB (devc->osdev, 0x00, devc->ccs_base + 0x00); oss_udelay (1000); pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; pci_write_config_word (osdev, PCI_COMMAND, pci_command); devc->nr_outdevs = devc->nr_indevs = 0; i = 0; if ((envy24ht_model > -1) && (envy24ht_model < (sizeof (models) / sizeof (card_spec)) - 1)) i = envy24ht_model; else while (models[i].svid != 0) { if (models[i].svid == subvendor) { name = models[i].product; devc->model_data = &models[i]; DDB (cmn_err (CE_CONT, "Card id '%s'\n", name)); break; } i++; } if (models[i].svid == 0) { cmn_err (CE_CONT, "Unknown device ID (%08x).\n", subvendor); cmn_err (CE_CONT, "This card may not be supported (yet).\n"); i = 0; /* Assume AC97 based Envy23PT */ } oss_register_device (osdev, name); if (devc->model_data == NULL) { cmn_err (CE_CONT, "Envy24ht: This card was not recognized: %08x\n", subvendor); return 0; } /* Disable all interrupts */ OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01); OUTB (devc->osdev, 0xff, devc->mt_base + 0x03); if (devc->model_data->flags & MF_ENVY24PT) { devc->codec_type = CODEC_AC97; envy24pt_init (devc); } else if (devc->model_data->svid == SSID_JULIA) { julia_eeprom_init (devc); } else load_eeprom (devc); devc->irq = pci_irq_line; if ((err = oss_register_interrupts (devc->osdev, 0, envy24htintr, NULL)) < 0) { cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); return 0; } i = 0; devc->max_ratesel = 0; while (speed_tab[i].speed != -1) { int rate = speed_tab[i].speed; if (verify_rate (devc, rate) == rate) devc->max_ratesel = i; i++; } OUTB (devc->osdev, ~0x10, devc->ccs_base + 0x01); /* Enable audio interrupts */ if (devc->model_data->flags & MF_MIDI) { attach_midi (devc); } i = 0; devc->max_ratesel = 0; while (speed_tab[i].speed != -1) { int rate = speed_tab[i].speed; if (verify_rate (devc, rate) == rate) devc->max_ratesel = i; i++; } devc->syncstart_mask = 0; devc->speedbits = 0; devc->speed = 0; devc->pending_speed = 0; devc->prev_speed = 0; devc->pending_speed_sel = 9; devc->configured_rate_sel = devc->pending_speed_sel; devc->open_count = 0; memcpy (devc->channel_names, channel_names, sizeof (channel_names)); devc->nr_play_channels = 10; devc->nr_rec_channels = 10; #define setmask(m, b) m|=(1<<(b)) devc->inportmask = 0; devc->outportmask = 0; devc->busy_play_channels = 0; devc->busy_rec_channels = 0; for (i = 0; i < devc->model_data->nr_outs; i++) setmask (devc->outportmask, i); if (devc->model_data->flags & MF_SPDIFOUT) { setmask (devc->outportmask, 8); /* SPDIF */ setmask (devc->outportmask, 9); /* SPDIF */ } for (i = 0; i < devc->model_data->nr_ins; i++) setmask (devc->inportmask, i); if (devc->model_data->flags & MF_SPDIFIN) { setmask (devc->inportmask, 8); /* SPDIF */ setmask (devc->inportmask, 9); /* SPDIF */ } if (devc->model_data->auxdrv == NULL) { devc->auxdrv = &dummy_auxdrv; } else { devc->auxdrv = devc->model_data->auxdrv; if (devc->auxdrv->card_init) devc->auxdrv->card_init (devc); } if (devc->codec_type == CODEC_AC97) install_ac97_mixer (devc); else { if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, devc->osdev, devc->osdev, devc->model_data-> product, &envy24ht_mixer_driver, sizeof (mixer_driver_t), devc)) >= 0) { int n = 50; mixer_devs[devc->mixer_dev]->hw_devc = devc; mixer_ext_set_init_fn (devc->mixer_dev, envy24ht_mix_init, n); mixer_devs[devc->mixer_dev]->priority = 1; /* Possible default mixer candidate */ } } if (devc->model_data->flags & (MF_SPDIFOUT | MF_SPDIFIN)) { int err; if ((err = oss_spdif_install (&devc->spdc, devc->osdev, &default_spdif_driver, sizeof (spdif_driver_t), devc, NULL, devc->mixer_dev, SPDF_OUT, DIG_PASSTHROUGH | DIG_EXACT | DIG_CBITOUT_LIMITED | DIG_VBITOUT | DIG_PRO | DIG_CONSUMER)) != 0) { cmn_err (CE_CONT, "S/PDIF driver install failed. error %d\n", err); return 0; } } OUTB (devc->osdev, ~0x10, devc->ccs_base + 0x01); /* Enable audio interrupts */ init_devices (devc); setup_sample_rate (devc); return 1; } int oss_envy24ht_detach (oss_device_t * osdev) { envy24ht_devc *devc = osdev->devc; if (oss_disable_device (osdev) < 0) return 0; /* Disable all interrupts */ OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01); /* disable DMA interrupt mask */ OUTB (devc->osdev, 0xff, devc->mt_base + 0x00); /* 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); unload_midi (devc); if (devc->auxdrv->card_uninit) devc->auxdrv->card_uninit(devc); oss_unregister_interrupts (devc->osdev); if (devc->model_data->flags & (MF_SPDIFOUT | MF_SPDIFIN)) { oss_spdif_uninstall (&devc->spdc); } MUTEX_CLEANUP (devc->mutex); MUTEX_CLEANUP (devc->low_mutex); UNMAP_PCI_IOADDR (devc->osdev, 0); UNMAP_PCI_IOADDR (devc->osdev, 1); oss_unregister_device (osdev); return 1; }